diff --git a/Changes b/Changes index 44ecd6f8c..93f8adc36 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ indicates the contributor was also the author of the fix; Thanks! ** Support '{} assignment pattern on arrays, bug355. +** Support streaming operators, bug649. [Glen Gibb] + *** Add --no-trace-params. *** Add assertions on 'unique if', bug725. [Jeff Bush] diff --git a/include/verilated.h b/include/verilated.h index f0afc4273..861cbc993 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -1358,6 +1358,122 @@ static inline WDataOutP VL_REPLICATE_WWI(int obits, int lbits, int, WDataOutP ow return(owp); } +// Left stream operator. Output will always be clean. LHS and RHS must be clean. +// Special "fast" versions for slice sizes that are a power of 2. These use +// shifts and masks to execute faster than the slower for-loop approach where a +// subset of bits is copied in during each iteration. +static inline IData VL_STREAML_FAST_III(int, int lbits, int, IData ld, IData rd_log2) { + // Pre-shift bits in most-significant slice: + // + // If lbits is not a multiple of the slice size (i.e., lbits % rd != 0), + // then we end up with a "gap" in our reversed result. For example, if we + // have a 5-bit Verlilog signal (lbits=5) in an 8-bit C data type: + // + // ld = ---43210 + // + // (where numbers are the Verilog signal bit numbers and '-' is an unused bit). + // Executing the switch statement below with a slice size of two (rd=2, + // rd_log2=1) produces: + // + // ret = 1032-400 + // + // Pre-shifting the bits in the most-significant slice allows us to avoid + // this gap in the shuffled data: + // + // ld_adjusted = --4-3210 + // ret = 10324--- + IData ret = ld; + if (rd_log2) { + vluint32_t lbitsFloor = lbits & ~VL_MASK_I(rd_log2); // max multiple of rd <= lbits + vluint32_t lbitsRem = lbits - lbitsFloor; // number of bits in most-sig slice (MSS) + IData msbMask = VL_MASK_I(lbitsRem) << lbitsFloor; // mask to sel only bits in MSS + ret = (ret & ~msbMask) | ((ret & msbMask) << ((VL_UL(1) << rd_log2) - lbitsRem)); + } + switch (rd_log2) { + case 0: + ret = ((ret >> 1) & VL_UL(0x55555555)) | ((ret & VL_UL(0x55555555)) << 1); // FALLTHRU + case 1: + ret = ((ret >> 2) & VL_UL(0x33333333)) | ((ret & VL_UL(0x33333333)) << 2); // FALLTHRU + case 2: + ret = ((ret >> 4) & VL_UL(0x0f0f0f0f)) | ((ret & VL_UL(0x0f0f0f0f)) << 4); // FALLTHRU + case 3: + ret = ((ret >> 8) & VL_UL(0x00ff00ff)) | ((ret & VL_UL(0x00ff00ff)) << 8); // FALLTHRU + case 4: + ret = ((ret >> 16) | (ret << 16)); + } + return ret >> (VL_WORDSIZE - lbits); +} + +static inline QData VL_STREAML_FAST_QQI(int, int lbits, int, QData ld, IData rd_log2) { + // Pre-shift bits in most-significant slice (see comment in VL_STREAML_FAST_III) + QData ret = ld; + if (rd_log2) { + vluint32_t lbitsFloor = lbits & ~VL_MASK_I(rd_log2); + vluint32_t lbitsRem = lbits - lbitsFloor; + QData msbMask = VL_MASK_Q(lbitsRem) << lbitsFloor; + ret = (ret & ~msbMask) | ((ret & msbMask) << ((VL_ULL(1) << rd_log2) - lbitsRem)); + } + switch (rd_log2) { + case 0: + ret = ((ret >> 1) & VL_ULL(0x5555555555555555)) | ((ret & VL_ULL(0x5555555555555555)) << 1); // FALLTHRU + case 1: + ret = ((ret >> 2) & VL_ULL(0x3333333333333333)) | ((ret & VL_ULL(0x3333333333333333)) << 2); // FALLTHRU + case 2: + ret = ((ret >> 4) & VL_ULL(0x0f0f0f0f0f0f0f0f)) | ((ret & VL_ULL(0x0f0f0f0f0f0f0f0f)) << 4); // FALLTHRU + case 3: + ret = ((ret >> 8) & VL_ULL(0x00ff00ff00ff00ff)) | ((ret & VL_ULL(0x00ff00ff00ff00ff)) << 8); // FALLTHRU + case 4: + ret = ((ret >> 16) & VL_ULL(0x0000ffff0000ffff)) | ((ret & VL_ULL(0x0000ffff0000ffff)) << 16); // FALLTHRU + case 5: + ret = ((ret >> 32) | (ret << 32)); + } + return ret >> (VL_QUADSIZE - lbits); +} + +// Regular "slow" streaming operators +static inline IData VL_STREAML_III(int, int lbits, int, IData ld, IData rd) { + IData ret = 0; + // Slice size should never exceed the lhs width + int ssize = ((int)rd < lbits) ? ((int)rd) : lbits; + IData mask = VL_MASK_I(rd); + for (int istart=0; istart 0 ? ostart : 0; + ret |= ((ld >> istart) & mask) << ostart; + } + return ret; +} + +static inline QData VL_STREAML_QQI(int, int lbits, int, QData ld, IData rd) { + QData ret = 0; + // Slice size should never exceed the lhs width + int ssize = ((int)rd < lbits) ? ((int)rd) : lbits; + QData mask = VL_MASK_Q(rd); + for (int istart=0; istart 0 ? ostart : 0; + ret |= ((ld >> istart) & mask) << ostart; + } + return ret; +} + +static inline WDataOutP VL_STREAML_WWI(int, int lbits, int, WDataOutP owp, WDataInP lwp, IData rd) { + VL_ZERO_RESET_W(lbits, owp); + // Slice size should never exceed the lhs width + int ssize = ((int)rd < lbits) ? ((int)rd) : lbits; + for (int istart=0; istart 0 ? ostart : 0; + for (int sbit=0; sbit> VL_BITBIT_I(istart+sbit)) & 1) << VL_BITBIT_I(ostart+sbit); + owp[VL_BITWORD_I(ostart+sbit)] |= bit; + } + } + return owp; +} + // Because concats are common and wide, it's valuable to always have a clean output. // Thus we specify inputs must be clean, so we don't need to clean the output. // Note the bit shifts are always constants, so the adds in these constify out. diff --git a/src/V3Ast.h b/src/V3Ast.h index a825b865a..88ee7ba64 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1676,6 +1676,16 @@ struct AstNodeSel : public AstNodeBiop { virtual bool hasDType() const { return true; } }; +struct AstNodeStream : public AstNodeBiop { + // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() + AstNodeStream(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeBiop(fl, lhsp, rhsp) { + if (lhsp->dtypep()) { + dtypeSetLogicSized(lhsp->dtypep()->width(), lhsp->dtypep()->width(), AstNumeric::UNSIGNED); + } + } + ASTNODE_BASE_FUNCS(NodeStream) +}; + //###################################################################### // Tasks/functions common handling diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index e755972b5..6b8173b7d 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -4175,6 +4175,30 @@ struct AstReplicate : public AstNodeBiop { virtual bool sizeMattersLhs() {return false;} virtual bool sizeMattersRhs() {return false;} virtual int instrCount() const { return widthInstrs()*2; } }; +struct AstStreamL : public AstNodeStream { + // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() + AstStreamL(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeStream(fl, lhsp, rhsp) {} + ASTNODE_NODE_FUNCS(StreamL, STREAML) + virtual string emitVerilog() { return "%f{ << %r %k{%l} }"; } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opStreamL(lhs,rhs); } + virtual string emitC() { return "VL_STREAML_%nq%lq%rq(%nw,%lw,%rw, %P, %li, %ri)"; } + virtual bool cleanOut() {return true;} + virtual bool cleanLhs() {return true;} virtual bool cleanRhs() {return true;} + virtual bool sizeMattersLhs() {return true;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return widthInstrs()*2; } +}; +struct AstStreamR : public AstNodeStream { + // Verilog {rhs{lhs}} - Note rhsp() is the slice size, not the lhsp() + AstStreamR(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeStream(fl, lhsp, rhsp) {} + ASTNODE_NODE_FUNCS(StreamR, STREAMR) + virtual string emitVerilog() { return "%f{ >> %r %k{%l} }"; } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opAssign(lhs); } + virtual string emitC() { return isWide() ? "VL_ASSIGN_W(%nw, %P, %li)" : "%li"; } + virtual bool cleanOut() {return false;} + virtual bool cleanLhs() {return false;} virtual bool cleanRhs() {return false;} + virtual bool sizeMattersLhs() {return true;} virtual bool sizeMattersRhs() {return false;} + virtual int instrCount() const { return widthInstrs()*2; } +}; struct AstBufIf1 : public AstNodeBiop { // lhs is enable, rhs is data to drive // Note unlike the Verilog bufif1() UDP, this allows any width; each lhsp bit enables respective rhsp bit diff --git a/src/V3Const.cpp b/src/V3Const.cpp index bc4e541ff..559094077 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -869,10 +869,10 @@ private: } if (debug()>=9) nodep->dumpTree(cout," Ass_old: "); // Unlink the stuff - AstNode* lc1p = nodep->lhsp()->castConcat()->lhsp()->unlinkFrBack(); - AstNode* lc2p = nodep->lhsp()->castConcat()->rhsp()->unlinkFrBack(); - AstNode* conp = nodep->lhsp()->castConcat()->unlinkFrBack(); - AstNode* rhsp = nodep->rhsp()->unlinkFrBack(); + AstNode* lc1p = nodep->lhsp()->castConcat()->lhsp()->unlinkFrBack(); + AstNode* lc2p = nodep->lhsp()->castConcat()->rhsp()->unlinkFrBack(); + AstNode* conp = nodep->lhsp()->castConcat()->unlinkFrBack(); + AstNode* rhsp = nodep->rhsp()->unlinkFrBack(); AstNode* rhs2p = rhsp->cloneTree(false); // Calc widths int lsb2 = 0; @@ -941,6 +941,64 @@ private: // Further reduce, either node may have more reductions. return true; } + else if (m_doV && nodep->rhsp()->castStreamR()) { + // The right-streaming operator on rhs of assignment does not + // change the order of bits. Eliminate stream but keep its lhsp + // Unlink the stuff + AstNode* srcp = nodep->rhsp()->castStreamR()->lhsp()->unlinkFrBack(); + AstNode* sizep = nodep->rhsp()->castStreamR()->rhsp()->unlinkFrBack(); + AstNode* streamp = nodep->rhsp()->castStreamR()->unlinkFrBack(); + nodep->rhsp(srcp); + // Cleanup + sizep->deleteTree(); sizep=NULL; + streamp->deleteTree(); streamp=NULL; + // Further reduce, any of the nodes may have more reductions. + return true; + } + else if (m_doV && nodep->lhsp()->castStreamL()) { + // Push the stream operator to the rhs of the assignment statement + int dWidth = nodep->lhsp()->castStreamL()->lhsp()->width(); + int sWidth = nodep->rhsp()->width(); + // Unlink the stuff + AstNode* dstp = nodep->lhsp()->castStreamL()->lhsp()->unlinkFrBack(); + AstNode* streamp = nodep->lhsp()->castStreamL()->unlinkFrBack(); + AstNode* srcp = nodep->rhsp()->unlinkFrBack(); + // Connect the rhs to the stream operator and update its width + streamp->castStreamL()->lhsp(srcp); + streamp->dtypeSetLogicSized((srcp->width()), + (srcp->widthMin()), + AstNumeric::UNSIGNED); + // Shrink the RHS if necessary + if (sWidth > dWidth) { + streamp = new AstSel(streamp->fileline(), streamp, sWidth-dWidth, dWidth); + } + // Link the nodes back in + nodep->lhsp(dstp); + nodep->rhsp(streamp); + return true; + } + else if (m_doV && nodep->lhsp()->castStreamR()) { + // The right stream operator on lhs of assignment statement does + // not reorder bits. However, if the rhs is wider than the lhs, + // then we select bits from the left-most, not the right-most. + int dWidth = nodep->lhsp()->castStreamR()->lhsp()->width(); + int sWidth = nodep->rhsp()->width(); + // Unlink the stuff + AstNode* dstp = nodep->lhsp()->castStreamR()->lhsp()->unlinkFrBack(); + AstNode* sizep = nodep->lhsp()->castStreamR()->rhsp()->unlinkFrBack(); + AstNode* streamp = nodep->lhsp()->castStreamR()->unlinkFrBack(); + AstNode* srcp = nodep->rhsp()->unlinkFrBack(); + if (sWidth > dWidth) { + srcp = new AstSel(streamp->fileline(), srcp, sWidth-dWidth, dWidth); + } + nodep->lhsp(dstp); + nodep->rhsp(srcp); + // Cleanup + sizep->deleteTree(); sizep=NULL; + streamp->deleteTree(); streamp=NULL; + // Further reduce, any of the nodes may have more reductions. + return true; + } else if (replaceAssignMultiSel(nodep)) { return true; } diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 2c5f40d7c..9a7e2df4b 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -32,6 +32,7 @@ #include "V3String.h" #include "V3EmitC.h" #include "V3EmitCBase.h" +#include "V3Number.h" #define VL_VALUE_STRING_MAX_WIDTH 8192 // We use a static char array in VL_VALUE_STRING @@ -552,6 +553,28 @@ public: emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), NULL); } } + virtual void visit(AstStreamL* nodep, AstNUser*) { + // Attempt to use a "fast" stream function for slice size = power of 2 + if (!nodep->isWide()) { + uint32_t isPow2 = nodep->rhsp()->castConst()->num().countOnes() == 1; + uint32_t sliceSize = nodep->rhsp()->castConst()->toUInt(); + if (isPow2 && sliceSize <= (nodep->isQuad() ? sizeof(uint64_t) : sizeof(uint32_t))) { + puts("VL_STREAML_FAST_"); + emitIQW(nodep); + emitIQW(nodep->lhsp()); + puts("I("); + puts(cvtToStr(nodep->widthMin())); + puts(","+cvtToStr(nodep->lhsp()->widthMin())); + puts(","+cvtToStr(nodep->rhsp()->widthMin())); + puts(","); + nodep->lhsp()->iterateAndNext(*this); puts(", "); + uint32_t rd_log2 = V3Number::log2b(nodep->rhsp()->castConst()->toUInt()); + puts(cvtToStr(rd_log2)+")"); + return; + } + } + emitOpName(nodep, "VL_STREAML_%nq%lq%rq(%nw,%lw,%rw, %P, %li, %ri)", nodep->lhsp(), nodep->rhsp(), NULL); + } // Terminals virtual void visit(AstVarRef* nodep, AstNUser*) { puts(nodep->hiername()); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 5b083e336..8daee0f80 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -902,6 +902,23 @@ V3Number& V3Number::opRepl (const V3Number& lhs, uint32_t rhsval) { // rhs is # return *this; } +V3Number& V3Number::opStreamL (const V3Number& lhs, const V3Number& rhs) { + setZero(); + // See also error in V3Width + if (!lhs.sized()) { + m_fileline->v3warn(WIDTHCONCAT,"Unsized numbers/parameters not allowed in streams."); + } + // Slice size should never exceed the lhs width + int ssize=min(rhs.toUInt(), (unsigned)lhs.width()); + for (int istart=0; istartc()->prelim()) { + nodep->lhsp()->iterateAndNext(*this,WidthVP(ANYSIZE,0,BOTH).p()); + nodep->rhsp()->iterateAndNext(*this,WidthVP(ANYSIZE,0,BOTH).p()); + checkCvtUS(nodep->lhsp()); + checkCvtUS(nodep->rhsp()); + V3Const::constifyParamsEdit(nodep->rhsp()); // rhsp may change + AstConst* constp = nodep->rhsp()->castConst(); + AstBasicDType* basicp = nodep->rhsp()->castBasicDType(); + if (!constp && !basicp) { nodep->v3error("Slice size isn't a constant or basic data type."); return; } + if (basicp) { // Convert data type to a constant size + AstConst* newp = new AstConst(basicp->fileline(), basicp->width()); + nodep->rhsp()->replaceWith(newp); + pushDeletep(basicp); + } else { + uint32_t sliceSize = constp->toUInt(); + if (!sliceSize) { nodep->v3error("Slice size cannot be zero."); return; } + } + nodep->dtypeSetLogicSized((nodep->lhsp()->width()), + (nodep->lhsp()->widthMin()), + AstNumeric::UNSIGNED); + } + if (vup->c()->final()) { + if (!nodep->dtypep()->widthSized()) { + // See also error in V3Number + nodep->v3warn(WIDTHCONCAT,"Unsized numbers/parameters not allowed in streams."); + } + } + } virtual void visit(AstRange* nodep, AstNUser* vup) { // Real: Not allowed // Signed: unsigned output, input either @@ -1548,7 +1578,8 @@ private: //UINFO(0,"aw "<rhsp()->width()<<" m"<rhsp()->widthMin()<dtypep(); // Note assignments do not look at the LHS's sign, extend based on right only - widthCheck(nodep,"Assign RHS",nodep->rhsp(),subDTypep,EXTEND_LHS); + int lhsStream = nodep->lhsp()->castNodeStream() != NULL; + widthCheck(nodep,"Assign RHS",nodep->rhsp(),subDTypep,lhsStream?EXTEND_OFF:EXTEND_LHS); //if (debug()) nodep->dumpTree(cout," AssignOut: "); } } @@ -2329,6 +2360,7 @@ private: // It is reasonable to have sign extension with unsigned output, // for example $unsigned(a)+$signed(b), the SIGNED(B) will be unsigned dtype out UINFO(4," widthExtend_(r="<castConst(); int expWidth = expDTypep->width(); if (constp && !nodep->isSigned()) { @@ -2467,7 +2499,13 @@ private: <<" bits."); } if (bad || underp->width()!=expWidth) { - fixWidthExtend(underp, expDTypep, extendRule); underp=NULL;//Changed + // If we're in an NodeAssign, don't truncate the RHS if the LHS is + // a NodeStream. The streaming operator changes the rules regarding + // which bits to truncate. + AstNodeAssign* assignp = nodep->castNodeAssign(); + if (!assignp || !assignp->lhsp()->castNodeStream()) { + fixWidthExtend(underp, expDTypep, extendRule); underp=NULL;//Changed + } } } diff --git a/src/verilog.y b/src/verilog.y index bc77f9103..afba2e3bb 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3010,7 +3010,7 @@ exprOkLvalue: // expression that's also OK to use as a variable_lvalue | data_type assignment_pattern { $$ = $2; $2->childDTypep($1); } | assignment_pattern { $$ = $1; } // - //UNSUP streaming_concatenation { UNSUP } + | streaming_concatenation { $$ = $1; } ; fexprOkLvalue: // exprOkLValue, For use as first part of statement (disambiguates <=) @@ -3112,6 +3112,32 @@ argsDotted: // IEEE: part of list_of_arguments | '.' idAny '(' expr ')' { $$ = new AstArg($1,*$2,$4); } ; +streaming_concatenation: // ==IEEE: streaming_concatenation + // // Need to disambiguate {<< expr-{ ... expr-} stream_concat } + // // From {<< stream-{ ... stream-} } + // // Likewise simple_type's idScoped from constExpr's idScope + // // Thus we allow always any two operations. Sorry + // // IEEE: "'{' yP_SL/R stream_concatenation '}'" + // // IEEE: "'{' yP_SL/R simple_type stream_concatenation '}'" + // // IEEE: "'{' yP_SL/R constExpr stream_concatenation '}'" + '{' yP_SLEFT stream_concOrExprOrType '}' { $$ = new AstStreamL($1, $3, new AstConst($1,1)); } + | '{' yP_SRIGHT stream_concOrExprOrType '}' { $$ = new AstStreamR($1, $3, new AstConst($1,1)); } + | '{' yP_SLEFT stream_concOrExprOrType stream_concatenation '}' { $$ = new AstStreamL($1, $4, $3); } + | '{' yP_SRIGHT stream_concOrExprOrType stream_concatenation '}' { $$ = new AstStreamR($1, $4, $3); } + ; + +stream_concOrExprOrType: // IEEE: stream_concatenation | slice_size:simple_type | slice_size:constExpr + cateList { $$ = $1; } + | simple_type { $$ = $1; } + // // stream_concatenation found via cateList:stream_expr:'{-normal-concat' + // // simple_typeRef found via cateList:stream_expr:expr:id + // // constant_expression found via cateList:stream_expr:expr + ; + +stream_concatenation: // ==IEEE: stream_concatenation + '{' cateList '}' { $$ = $2; } + ; + stream_expression: // ==IEEE: stream_expression // // IEEE: array_range_expression expanded below expr { $$ = $1; } @@ -3381,6 +3407,7 @@ variable_lvalue: // IEEE: variable_lvalue or net_lvalue //UNSUP idClassSel yP_TICKBRA variable_lvalueList '}' { UNSUP } //UNSUP /**/ yP_TICKBRA variable_lvalueList '}' { UNSUP } //UNSUP streaming_concatenation { UNSUP } + | streaming_concatenation { $$ = $1; } ; variable_lvalueConcList: // IEEE: part of variable_lvalue: '{' variable_lvalue { ',' variable_lvalue } '}' diff --git a/test_regress/t/t_stream.pl b/test_regress/t/t_stream.pl new file mode 100755 index 000000000..f91289753 --- /dev/null +++ b/test_regress/t/t_stream.pl @@ -0,0 +1,18 @@ +#!/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. + +compile ( + ); + +execute ( + check_finished=>1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_stream.v b/test_regress/t/t_stream.v new file mode 100644 index 000000000..13250f81d --- /dev/null +++ b/test_regress/t/t_stream.v @@ -0,0 +1,311 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2014 by Glen Gibb. + +//module t; +module t (/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + integer cyc; initial cyc=1; + + + // The 'initial' code block below tests compilation-time + // evaluation/optimization of the stream operator. All occurences of the stream + // operator within this block are replaced prior to generation of C code. + logic [3:0] dout; + logic [31:0] dout32; + logic [10:0] dout11; + + initial begin + + // Stream operator: << + // Location: rhs of assignment + // + // Test slice sizes from 1 - 5 + dout = { << {4'b0001}}; if (dout != 4'b1000) $stop; + dout = { << 2 {4'b0001}}; if (dout != 4'b0100) $stop; + dout = { << 3 {4'b0001}}; if (dout != 4'b0010) $stop; + dout = { << 4 {4'b0001}}; if (dout != 4'b0001) $stop; + dout = { << 5 {4'b0001}}; if (dout != 4'b0001) $stop; + + // Stream operator: >> + // Location: rhs of assignment + // + // Right-streaming operator on RHS does not reorder bits + dout = { >> {4'b0001}}; if (dout != 4'b0001) $stop; + dout = { >> 2 {4'b0001}}; if (dout != 4'b0001) $stop; + dout = { >> 3 {4'b0001}}; if (dout != 4'b0001) $stop; + dout = { >> 4 {4'b0001}}; if (dout != 4'b0001) $stop; + dout = { >> 5 {4'b0001}}; if (dout != 4'b0001) $stop; + + // Stream operator: << + // Location: lhs of assignment + { << {dout}} = 4'b0001; if (dout != 4'b1000) $stop; + { << 2 {dout}} = 4'b0001; if (dout != 4'b0100) $stop; + { << 3 {dout}} = 4'b0001; if (dout != 4'b0010) $stop; + { << 4 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + { << 5 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + + // Stream operator: >> + // Location: lhs of assignment + { >> {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + { >> 2 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + { >> 3 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + { >> 4 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + { >> 5 {dout}} = 4'b0001; if (dout != 4'b0001) $stop; + + // Stream operator: << + // Location: lhs of assignment + // RHS is *wider* than LHS + /* verilator lint_off WIDTH */ + { << {dout}} = 5'b00001; if (dout != 4'b1000) $stop; + { << 2 {dout}} = 5'b00001; if (dout != 4'b0100) $stop; + { << 3 {dout}} = 5'b00001; if (dout != 4'b0010) $stop; + { << 4 {dout}} = 5'b00001; if (dout != 4'b0001) $stop; + { << 5 {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + /* verilator lint_on WIDTH */ + + // Stream operator: >> + // Location: lhs of assignment + // RHS is *wider* than LHS + /* verilator lint_off WIDTH */ + { >> {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + { >> 2 {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + { >> 3 {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + { >> 4 {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + { >> 5 {dout}} = 5'b01101; if (dout != 4'b0110) $stop; + /* verilator lint_on WIDTH */ + + // Stream operator: << + // Location: both sides of assignment + { << {dout}} = { << {4'b0001}}; if (dout != 4'b0001) $stop; + { << 2 {dout}} = { << 2 {4'b0001}}; if (dout != 4'b0001) $stop; + { << 3 {dout}} = { << 3 {4'b0001}}; if (dout != 4'b0100) $stop; + { << 4 {dout}} = { << 4 {4'b0001}}; if (dout != 4'b0001) $stop; + { << 5 {dout}} = { << 5 {4'b0001}}; if (dout != 4'b0001) $stop; + + // Stream operator: << + // Location: as an operand within a statement + // + // Test slice sizes from 1 - 5 + if (4'({ << {4'b0001}}) != 4'b1000) $stop; + if (4'({ << 2 {4'b0001}}) != 4'b0100) $stop; + if (4'({ << 3 {4'b0001}}) != 4'b0010) $stop; + if (4'({ << 4 {4'b0001}}) != 4'b0001) $stop; + if (4'({ << 5 {4'b0001}}) != 4'b0001) $stop; + + // case + dout32 = { << 3 { 32'b11010111000010100100010010010111 }}; if (dout32 != 32'he92910eb) $stop; + dout11 = { << 4 { 11'b10010010111 }}; if (dout11 != 11'h3cc) $stop; + end + + + // The two always blocks below test run-time evaluation of the stream + // operator in generated C code. + // + // Various stream operators are optimized away. Here's a brief summary: + // + // Stream op on RHS of assign + // -------------------------- + // X = { << a { Y } } --- C function evaluates stream operator + // -- if log2(a) == int --> "fast" eval func + // -- if log2(a) != int --> "slow" eval func + // X = { >> a { Y } } --- stream operator is optimized away + // + // Stream op on LHS of assign + // -------------------------- + // Note: if Y.width() > X.width, then the MSBs of Y are used, not the LSBs! + // { << a { X } } = Y --- stream operator is moved to RHS, eval as above + // { >> a { X } } = Y --- stream operator is optimized away + + logic [31:0] din_i; + logic [63:0] din_q; + logic [95:0] din_w; + + // Stream op on RHS, left-stream operator + logic [31:0] dout_rhs_ls_i; + logic [63:0] dout_rhs_ls_q; + logic [95:0] dout_rhs_ls_w; + + // Stream op on RHS, right-stream operator + logic [31:0] dout_rhs_rs_i; + logic [63:0] dout_rhs_rs_q; + logic [95:0] dout_rhs_rs_w; + + // Stream op on both sides, left-stream operator + logic [31:0] dout_bhs_ls_i; + logic [63:0] dout_bhs_ls_q; + logic [95:0] dout_bhs_ls_w; + + // Stream op on both sides, right-stream operator + logic [31:0] dout_bhs_rs_i; + logic [63:0] dout_bhs_rs_q; + logic [95:0] dout_bhs_rs_w; + + // Stream operator on LHS (with concatenation on LHS) + logic [3:0] din_lhs; + logic [1:0] dout_lhs_ls_a, dout_lhs_ls_b; + logic [1:0] dout_lhs_rs_a, dout_lhs_rs_b; + + // Addition operator on LHS, right-shift tests: + // Testing various shift sizes to exercise fast + slow funcs + logic [22:0] dout_rhs_ls_i_23_3; + logic [22:0] dout_rhs_ls_i_23_4; + + logic [36:0] dout_rhs_ls_q_37_3; + logic [36:0] dout_rhs_ls_q_37_4; + + always @* + begin + // Stream operator: << + // Location: rhs of assignment + // + // Test each data type (I, Q, W) + dout_rhs_ls_i = { << {din_i}}; + dout_rhs_ls_q = { << {din_q}}; + dout_rhs_ls_w = { << {din_w}}; + + // Stream operator: >> + // Location: rhs of assignment + dout_rhs_rs_i = { >> {din_i}}; + dout_rhs_rs_q = { >> {din_q}}; + dout_rhs_rs_w = { >> {din_w}}; + + // Stream operator: << + // Location: lhs of assignment + { << 2 {dout_lhs_ls_a, dout_lhs_ls_b}} = din_lhs; + + // Stream operator: >> + // Location: lhs of assignment + { >> 2 {dout_lhs_rs_a, dout_lhs_rs_b}} = din_lhs; + + // Stream operator: << + // Location: both sides of assignment + { << 5 {dout_bhs_ls_i}} = { << 5 {din_i}}; + { << 5 {dout_bhs_ls_q}} = { << 5 {din_q}}; + { << 5 {dout_bhs_ls_w}} = { << 5 {din_w}}; + + // Stream operator: >> + // Location: both sides of assignment + { >> 5 {dout_bhs_rs_i}} = { >> 5 {din_i}}; + { >> 5 {dout_bhs_rs_q}} = { >> 5 {din_q}}; + { >> 5 {dout_bhs_rs_w}} = { >> 5 {din_w}}; + + // Stream operator: << + // Location: both sides of assignment + { << 5 {dout_bhs_ls_i}} = { << 5 {din_i}}; + { << 5 {dout_bhs_ls_q}} = { << 5 {din_q}}; + { << 5 {dout_bhs_ls_w}} = { << 5 {din_w}}; + + // Stream operator: << + // Location: rhs of assignment + // + // Verify both fast and slow paths (fast: sliceSize = power of 2) + dout_rhs_ls_i_23_3 = { << 3 {din_i[22:0]}}; // SLOW + dout_rhs_ls_i_23_4 = { << 4 {din_i[22:0]}}; // FAST + + dout_rhs_ls_q_37_3 = { << 3 {din_q[36:0]}}; // SLOW + dout_rhs_ls_q_37_4 = { << 4 {din_q[36:0]}}; // FAST + end + + always @(posedge clk) + begin + if (cyc != 0) begin + cyc <= cyc + 1; + + if (cyc == 1) begin + din_i <= 32'h_00_00_00_01; + din_q <= 64'h_00_00_00_00_00_00_00_01; + din_w <= 96'h_00_00_00_00_00_00_00_00_00_00_00_01; + + din_lhs <= 4'b_00_01; + end + if (cyc == 2) begin + din_i <= 32'h_04_03_02_01; + din_q <= 64'h_08_07_06_05_04_03_02_01; + din_w <= 96'h_0c_0b_0a_09_08_07_06_05_04_03_02_01; + + din_lhs <= 4'b_01_11; + + if (dout_rhs_ls_i != 32'h_80_00_00_00) $stop; + if (dout_rhs_ls_q != 64'h_80_00_00_00_00_00_00_00) $stop; + if (dout_rhs_ls_w != 96'h_80_00_00_00_00_00_00_00_00_00_00_00) $stop; + + if (dout_rhs_rs_i != 32'h_00_00_00_01) $stop; + if (dout_rhs_rs_q != 64'h_00_00_00_00_00_00_00_01) $stop; + if (dout_rhs_rs_w != 96'h_00_00_00_00_00_00_00_00_00_00_00_01) $stop; + + if (dout_lhs_ls_a != 2'b_01) $stop; + if (dout_lhs_ls_b != 2'b_00) $stop; + + if (dout_lhs_rs_a != 2'b_00) $stop; + if (dout_lhs_rs_b != 2'b_01) $stop; + + if (dout_bhs_rs_i != 32'h_00_00_00_01) $stop; + if (dout_bhs_rs_q != 64'h_00_00_00_00_00_00_00_01) $stop; + if (dout_bhs_rs_w != 96'h_00_00_00_00_00_00_00_00_00_00_00_01) $stop; + + if (dout_bhs_ls_i != 32'h_00_00_00_10) $stop; + if (dout_bhs_ls_q != 64'h_00_00_00_00_00_00_01_00) $stop; + if (dout_bhs_ls_w != 96'h_00_00_00_00_00_00_00_00_00_00_00_04) $stop; + + if (dout_rhs_ls_i_23_3 != 23'h_10_00_00) $stop; + if (dout_rhs_ls_i_23_4 != 23'h_08_00_00) $stop; + + if (dout_rhs_ls_q_37_3 != 37'h_04_00_00_00_00) $stop; + if (dout_rhs_ls_q_37_4 != 37'h_02_00_00_00_00) $stop; + end + if (cyc == 3) begin + // The values below test the strange shift-merge done at the end of + // the fast stream operators. + // All-1s in the bits being streamed should end up as all-1s. + din_i <= 32'h_00_7f_ff_ff; + din_q <= 64'h_00_00_00_1f_ff_ff_ff_ff; + + if (dout_rhs_ls_i != 32'h_80_40_c0_20) $stop; + if (dout_rhs_ls_q != 64'h_80_40_c0_20_a0_60_e0_10) $stop; + if (dout_rhs_ls_w != 96'h_80_40_c0_20_a0_60_e0_10_90_50_d0_30) $stop; + + if (dout_rhs_rs_i != 32'h_04_03_02_01) $stop; + if (dout_rhs_rs_q != 64'h_08_07_06_05_04_03_02_01) $stop; + if (dout_rhs_rs_w != 96'h_0c_0b_0a_09_08_07_06_05_04_03_02_01) $stop; + + if (dout_bhs_ls_i != 32'h_40_30_00_18) $stop; + if (dout_bhs_ls_q != 64'h_06_00_c1_81_41_00_c1_80) $stop; + if (dout_bhs_ls_w != 96'h_30_2c_28_20_01_1c_1a_04_14_0c_00_06) $stop; + + if (dout_bhs_rs_i != 32'h_04_03_02_01) $stop; + if (dout_bhs_rs_q != 64'h_08_07_06_05_04_03_02_01) $stop; + if (dout_bhs_rs_w != 96'h_0c_0b_0a_09_08_07_06_05_04_03_02_01) $stop; + + if (dout_lhs_ls_a != 2'b_11) $stop; + if (dout_lhs_ls_b != 2'b_01) $stop; + + if (dout_lhs_rs_a != 2'b_01) $stop; + if (dout_lhs_rs_b != 2'b_11) $stop; + + if (dout_rhs_ls_i_23_3 != 23'h_10_08_c0) $stop; + if (dout_rhs_ls_i_23_4 != 23'h_08_10_18) $stop; + + if (dout_rhs_ls_q_37_3 != 37'h_04_02_30_10_44) $stop; + if (dout_rhs_ls_q_37_4 != 37'h_02_04_06_08_0a) $stop; + end + if (cyc == 4) begin + if (dout_rhs_ls_i_23_3 != 23'h_7f_ff_ff) $stop; + if (dout_rhs_ls_i_23_4 != 23'h_7f_ff_ff) $stop; + + if (dout_rhs_ls_q_37_3 != 37'h_1f_ff_ff_ff_ff) $stop; + if (dout_rhs_ls_q_37_4 != 37'h_1f_ff_ff_ff_ff) $stop; + end + if (cyc == 9) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + end + +endmodule diff --git a/test_regress/t/t_stream2.pl b/test_regress/t/t_stream2.pl new file mode 100755 index 000000000..f91289753 --- /dev/null +++ b/test_regress/t/t_stream2.pl @@ -0,0 +1,18 @@ +#!/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. + +compile ( + ); + +execute ( + check_finished=>1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_stream2.v b/test_regress/t/t_stream2.v new file mode 100644 index 000000000..148b28e2c --- /dev/null +++ b/test_regress/t/t_stream2.v @@ -0,0 +1,83 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2014 by Wilson Snyder. + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc=0; + reg [63:0] crc; + reg [63:0] sum; + /*AUTOWIRE*/ + // Beginning of automatic wires (for undeclared instantiated-module outputs) + wire [67:0] left; // From test of Test.v + wire [67:0] right; // From test of Test.v + // End of automatics + + wire [6:0] amt = crc[6:0]; + wire [67:0] in = {crc[3:0], crc[63:0]}; + + Test test (/*AUTOINST*/ + // Outputs + .left (left[67:0]), + .right (right[67:0]), + // Inputs + .amt (amt[6:0]), + .in (in[67:0])); + + wire [63:0] result = (left[63:0] ^ {60'h0, left[67:64]} + ^ right[63:0] ^ {60'h0, right[67:64]}); + + // Test loop + always @ (posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc==%0d crc=%x result=%x amt=%x left=%x right=%x\n", + $time, cyc, crc, result, amt, left, right); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63]^crc[2]^crc[0]}; + sum <= result ^ {sum[62:0],sum[63]^sum[2]^sum[0]}; + if (cyc==0) begin + // Setup + crc <= 64'h5aef0c8d_d70a4497; + sum <= 64'h0; + end + else if (cyc<10) begin + sum <= 64'h0; + end + else if (cyc<90) begin + end + else if (cyc==99) begin + $write("[%0t] cyc==%0d crc=%x sum=%x\n",$time, cyc, crc, sum); + if (crc !== 64'hc77bb9b3784ea091) $stop; + // What checksum will we end up with (above print should match) +`define EXPECTED_SUM 64'h0da01049b480c38a + if (sum !== `EXPECTED_SUM) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule + +module Test (/*AUTOARG*/ + // Outputs + left, right, + // Inputs + amt, in + ); + + input [6:0] amt; + input [67:0] in; + + // amt must be constant + output wire [67:0] left; + output wire [67:0] right; + assign right = { << 33 {in}}; + assign left = { >> 33 {in}}; + +endmodule diff --git a/test_regress/t/t_stream3.pl b/test_regress/t/t_stream3.pl new file mode 100755 index 000000000..f91289753 --- /dev/null +++ b/test_regress/t/t_stream3.pl @@ -0,0 +1,18 @@ +#!/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. + +compile ( + ); + +execute ( + check_finished=>1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_stream3.v b/test_regress/t/t_stream3.v new file mode 100644 index 000000000..ec0f4bce4 --- /dev/null +++ b/test_regress/t/t_stream3.v @@ -0,0 +1,99 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2014 by Wilson Snyder. + +`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; + reg [63:0] crc; + reg [63:0] sum; + /*AUTOWIRE*/ + + generate + for (genvar width=1; width<=16; width++) begin + for (genvar amt=1; amt<=width; amt++) begin + Test #(.WIDTH(width), + .AMT(amt)) + test (.ins(crc[width-1:0])); + end + end + endgenerate + + // Test loop + always @ (posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc==%0d crc=%x\n", + $time, cyc, crc); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63]^crc[2]^crc[0]}; + if (cyc==0) begin + // Setup + crc <= 64'h5aef0c8d_d70a4497; + sum <= 64'h0; + end + else if (cyc<10) begin + sum <= 64'h0; + end + else if (cyc<90) begin + end + else if (cyc==99) begin + $write("[%0t] cyc==%0d crc=%x sum=%x\n",$time, cyc, crc, sum); + if (crc !== 64'hc77bb9b3784ea091) $stop; + // What checksum will we end up with (above print should match) +`define EXPECTED_SUM 64'h0 + if (sum !== `EXPECTED_SUM) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule + +module Test (/*AUTOARG*/ + // Inputs + ins + ); + + parameter WIDTH = 1; + parameter AMT = 1; + + input [WIDTH-1:0] ins; + reg [WIDTH-1:0] got; + reg [WIDTH-1:0] expec; + int istart; + int bitn; + int ostart; + + always @* begin + got = { << AMT {ins}}; + + // Note always starts with right-most bit + expec = 0; + for (istart=0; istart= 0 + && (ostart+bitn) < WIDTH + && (ostart+bitn) >= 0) begin + expec[ostart+bitn] = ins[istart+bitn]; + end + end + end + +`ifdef TEST_VERBOSE + $write("[%0t] exp %0d'b%b got %0d'b%b = { << %0d { %0d'b%b }}\n", $time, WIDTH, expec, WIDTH, got, AMT, WIDTH, ins); +`endif + `checkh(got, expec); + end + +endmodule