diff --git a/Changes b/Changes index f8f67d76c..00ca8a971 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,11 @@ indicates the contributor was also the author of the fix; Thanks! * Verilator 3.700*** +** Add limited support for tristate inouts. Written by Lane Brooks. + This allows common pad ring and tristate-mux structures to be + Verilated. See the documentation for more information on supported + constructs. + ** Add --coverage_toggle for toggle coverage analysis. Running coverage now requires SystemPerl 1.301 or newer. diff --git a/bin/verilator b/bin/verilator index a5a5a02d3..15e5fd593 100755 --- a/bin/verilator +++ b/bin/verilator @@ -1495,9 +1495,24 @@ all variables to one at startup finds most problems. =head2 Tri/Inout -As a 2 state compiler, tristate and inouts are not supported. -Traditionally only chip "cores" are Verilated, the pad rings have been -written by hand in C++. +Verilator converts some simple tristate structures into two state. An assignment +of the form: + + inout driver; + wire driver = (enable) ? output_value : 1'bz; + +Will be converted to + + input driver__in; // Value being driven in from "external" drivers + output driver__en; // True if driven from this module + output driver__enout; // Value being driven from this module + +Pullup and pulldown are also supported. External logic will be needed to +combine these signals with any external drivers. + +Tristate drivers are not supported inside functions and tasks; a inout +there will be considered a two state variable that is read and written +instead of a four state variable. =head2 Functions & Tasks diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index e206355bd..ca7217512 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -192,6 +192,7 @@ RAW_OBJS = \ V3Task.o \ V3Trace.o \ V3TraceDecl.o \ + V3Tristate.o \ V3Unknown.o \ V3Unroll.o \ V3Width.o \ diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index bdd4e9b82..4a467d526 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -283,6 +283,8 @@ public: virtual string name() const { return m_name; } // * = Var name virtual bool maybePointedTo() const { return true; } AstVarType varType() const { return m_varType; } // * = Type of variable + void varType2Out() { m_tristate=0; m_input=0; m_output=1; } + void varType2In() { m_tristate=0; m_input=1; m_output=0; } string cType() const; // Return C type for declaration: bool, uint32_t, uint64_t, etc. string scType() const; // Return SysC type: bool, uint32_t, uint64_t, sc_bv void combineType(AstVarType type); @@ -955,6 +957,23 @@ public: void allowImplicit(bool flag) { m_allowImplicit = flag; } }; +struct AstPull : public AstNode { +private: + bool m_direction; +public: + AstPull(FileLine* fileline, AstNode* lhsp, bool direction) + : AstNode(fileline) { + setOp1p(lhsp); + m_direction = direction; + } + ASTNODE_NODE_FUNCS(Pull, PULL) + virtual bool same(AstNode* samep) const { + return direction()==samep->castPull()->direction(); } + void lhsp(AstNode* np) { setOp1p(np); } + AstNode* lhsp() const { return op1p()->castNode(); } // op1 = Assign to + uint32_t direction() const { return (uint32_t) m_direction; } +}; + struct AstAssignPre : public AstNodeAssign { // Like Assign, but predelayed assignment requiring special order handling AstAssignPre(FileLine* fileline, AstNode* lhsp, AstNode* rhsp) diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 2bed21a1c..992a822df 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -291,6 +291,14 @@ V3Number& V3Number::setSingleBits(char value) { return *this; } +V3Number& V3Number::setAllBits0() { + for (int i=0; i1, X/Z->0 } return *this; } +V3Number& V3Number::opBitsZ (const V3Number& lhs) { // 0/1->1, X/Z->0 + // op i, L(lhs) bit return + setZero(); + for(int bit=0; bitwidth(); bit++) { + if (lhs.bitIsZ(bit)) { setBit(bit,1); } + } + return *this; +} +V3Number& V3Number::opBitsNonZ (const V3Number& lhs) { // 0/1->1, X/Z->0 + // op i, L(lhs) bit return + setZero(); + for(int bit=0; bitwidth(); bit++) { + if (!lhs.bitIsZ(bit)) { setBit(bit,1); } + } + return *this; +} //====================================================================== // Operators - Simple per-bit logical ops diff --git a/src/V3Number.h b/src/V3Number.h index b19b4a5c9..f0859c55b 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -115,6 +115,8 @@ public: // SETTERS V3Number& setAllBitsX(); V3Number& setAllBitsZ(); + V3Number& setAllBits0(); + V3Number& setAllBits1(); V3Number& setMask(int nbits); // IE if nbits=1, then 0b1, if 2->0b11, if 3->0b111 etc // ACCESSORS @@ -129,6 +131,8 @@ public: bool isSigned() const { return m_signed; } // Only correct for parsing of numbers from strings, otherwise not used (use AstConst::isSigned()) bool isNegative() const { return bitIs1(width()-1); } bool isFourState() const { for (int i=0;i1, X/Z->0 V3Number& opBitsOne (const V3Number& lhs); // 1->1, 0/X/Z->0 V3Number& opBitsXZ (const V3Number& lhs); // 0/1->0, X/Z->1 + V3Number& opBitsZ (const V3Number& lhs); // Z->1, 0/1/X->0 + V3Number& opBitsNonZ(const V3Number& lhs); // Z->0, 0/1/X->1 // V3Number& opAssign (const V3Number& lhs); V3Number& opExtendS (const V3Number& lhs); // Sign extension diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp new file mode 100644 index 000000000..1ac0800fd --- /dev/null +++ b/src/V3Tristate.cpp @@ -0,0 +1,692 @@ +//************************************************************************* +// DESCRIPTION: Verilator: Deals with tristate logic +// +// Code available from: http://www.veripool.org/verilator +// +// AUTHORS: Lane Brooks with Wilson Snyder, Paul Wasson, Duane Gabli +// +//************************************************************************* +// +// Copyright 2003-2009 by Wilson Snyder. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU +// General Public License or the Perl Artistic License. +// +// Verilator is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//************************************************************************* +// V3Tristate's Transformations: +// +// Tristate transformations are accomplished via three operations on the tree: +// +// 1. Find 'hZ constructs and change the logic to a two state output. +// This requires creating an __en signal for the driver resolution +// logic to use. This function is *not* generic right now in that +// it can transform any logic structure. It is currently highly +// specialize to only work on assign structures where the 'hZ +// assignment is in the first level. More work needs done to make +// this work with any logic structure. +// +// 2. While walking the tree looking for 'hZ constructs, also detect +// any nets that have multiple LHS (left-hand side) drivers. These +// are the locations where driver resolution will occur. +// +// 3. Finally, make a pass through the cell pin assignments to push +// the __en pins up the hierarchy until they are finally terminated +// at the driver resolution stage. +// +// Inouts are treated differently than tristates, though they are certainly +// related. Inouts are expanded prior to tristate expansion. The inout +// expansion takes each inout port and creates an new input with a name suffix +// of __in and transforms the original port to an output port. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" +#include +#include +#include +#include +#include + +#include "V3Global.h" +#include "V3Tristate.h" +#include "V3Ast.h" +#include "V3Const.h" +#include "V3Stats.h" + +//###################################################################### + +typedef std::vector VarVec; +typedef std::vector RefVec; +typedef std::map VarMap; + +//###################################################################### + +class TristateBaseVisitor : public AstNVisitor { +public: + //int debug() { return 9; } +}; + +//###################################################################### + +class TristateExpander : public TristateBaseVisitor { + // Finds all tristate logic and creates an __en signal and removes Z's. + // Only supports limited assign statements initially. + // Also detects multiple lhs var drivers. + +private: + // NODE STATE + // Cleared on Netlist + // AstVarRef::user1p -> used to track any newly create __en signals + // AstVarRef::user2 -> already visited + // AstVarRef::user3p -> a parent SEL + // AstVar::user1p -> used to track any newly create __en signals + // AstVar::user2 -> used for pullup/pulldown direction + + // STATE + int m_unique; + AstModule* m_modp; // Current module + VarMap* m_lhsmapp; // LHS driver map + AstSel* m_sel; + + virtual void visit(AstSel* nodep, AstNUser*) { + m_sel=nodep; + nodep->iterateChildren(*this); + m_sel=NULL; + } + + //******************************************************************* + // The following visitor functions deal with detecting Z's in the + // logic, stripping the Z's out and creating an __en signal and its + // logic. + //******************************************************************* + virtual void visit(AstPull* nodep, AstNUser*) { + // replace any pullup/pulldowns with assignw logic and an __en + // signal just like it is any other tristate signal. The only + // difference is that the user2() variable on the __en signal + // will be given a pull direction--i.e. pulldown=1, pullup=2. + // This will signal the driver exansion logic to put a default + // pullup or pulldown state on the tristate bus under the high-Z + // condition when no one is driving the bus. Given the complexity + // of merging tristate drivers at any level, the current limitation + // of this implementation is that a pullup/down gets applied + // to all bits of a bus and a bus cannot have drivers in opposite + // directions on indvidual pins. + AstNode* outp = nodep->lhsp()->unlinkFrBack();; + AstVarRef* outrefp = NULL; + int width=-1; + if (outp->castVarRef()) { + outrefp = outp->castVarRef(); + } else if (outp->castSel()) { + outrefp = outp->castSel()->fromp()->castVarRef(); + width = outp->castSel()->widthConst(); + } else { + v3error("Can't find LHS varref"); + } + outrefp->lvalue(true); + AstVar* varp = outrefp->varp(); + if (width==-1) width=varp->width(); + + V3Number num0 (nodep->fileline(), width); + num0.setAllBits0(); + V3Number num1 (nodep->fileline(), width); + num1.setAllBits1(); + + AstConst* enrhsp = new AstConst(nodep->fileline(), num0); + AstVar* enp = createEnableVar(outp, outrefp, enrhsp, width, "pull"); + enp->user2(nodep->direction()+1); // record the pull direction + + AstAssignW* newassp = new AstAssignW(nodep->fileline(), outp, + new AstConst(nodep->fileline(), nodep->direction() ? num1 : num0)); + nodep->replaceWith(newassp); + nodep->deleteTree(); nodep=NULL; + newassp->iterateChildren(*this); + } + + virtual void visit(AstAssignW* nodep, AstNUser*) { + // Note: this detects and expands tristates of the forms: + // assign x = (OE) ? y : 'hZ; + // assign x = (OE) ? 'hz : y; + + // see if this a COND and seperate out the __en logic from the output logic if it is + if (nodep->rhsp()->castCond()) { + AstCond* condp = nodep->rhsp()->castCond(); + AstNode* oep = condp->condp(); + AstNode* expr1p = condp->expr1p(); + AstNode* expr2p = condp->expr2p(); + AstNode* enrhsp; + AstNode* outrhsp; + + if (expr1p->castConst() && expr1p->castConst()->num().isAllZ()) { + enrhsp = new AstNot(oep->fileline(), oep->unlinkFrBack()); + outrhsp = expr2p->unlinkFrBack(); + } else if (expr2p->castConst() && expr2p->castConst()->num().isAllZ()){ + enrhsp = oep->unlinkFrBack(); + outrhsp = expr1p->unlinkFrBack(); + } else { + // not a tristate or not in a form we recgonize, so exit and move on. + return; + } + + AstNode* outp = nodep->lhsp()->unlinkFrBack();; + AstVarRef* outrefp = NULL; + if (outp->castVarRef()) { + outrefp = outp->castVarRef(); + } else if (outp->castSel()) { + outrefp = outp->castSel()->fromp()->castVarRef(); + } else { + v3error("Can't find LHS varref"); + } + + createEnableVar(outp, outrefp, enrhsp, outrhsp->width()); + + // replace the old assign logic with the new one + AstAssignW* newassp = new AstAssignW(nodep->fileline(), outp,outrhsp); + nodep->replaceWith(newassp); + nodep->deleteTree(); nodep=NULL; + newassp->iterateChildren(*this); + + } else { + nodep->iterateChildren(*this); + } + } + + AstVar* createEnableVar(AstNode* outp, AstVarRef* outrefp, AstNode* enrhsp, int width, string suffix="") { + // this function creates an __en Var that cooresponds to + // the outp and outrefp and creates an assignw to enrhsp + AstVar* enp = new AstVar (outrefp->varp()->fileline(), + AstVarType::MODULETEMP, + outrefp->name() + "__en" + suffix + cvtToStr(m_unique++), + (width>1) ? new AstRange(outp->fileline(), width-1, 0) : (AstRange *) NULL); + + if (enp->width() != enrhsp->width()) { + if (enrhsp->width()==1) { // it seems from my futzing that the linter guarantees this condition + enrhsp = new AstReplicate(enrhsp->fileline(), enrhsp, new AstConst(enrhsp->fileline(), V3Number(enrhsp->fileline(), 32, enp->width()))); + enrhsp->width(enp->width(), enp->widthMin()); + } else { + v3error("Don't know how to deal with selection logic wider than 1 bit"); + } + } + m_modp->addStmtp(enp); + m_modp->addStmtp(new AstAssignW (enp->fileline(), + new AstVarRef (enp->fileline(), enp, true), + enrhsp)); + + outrefp->user1p(enp); // put __en signal into varref for later usage + outrefp->varp()->user1p(enp); // put __en signal into var as well in the event this is a single lhs driver and this needs passed up one level + + return enp; + } + + //********************************************************************** + + //********************************************************************** + // These functions detect all var lhs drivers and add them to the + // VarMap so that after the walk through the module we can + // expand the driver to determine any that have multiple lhs drivers. + //********************************************************************** + + virtual void visit(AstVarRef* nodep, AstNUser*) { + if (nodep->lvalue() && !nodep->user2()) { + nodep->user2(true); // mark this ref as visited + AstVar* key = nodep->varp(); + + VarMap::iterator it = m_lhsmapp->find(key); + if (it == m_lhsmapp->end()) { + // this key does not exist yet, so create it + RefVec* refs = new RefVec(); + refs->push_back(nodep); + m_lhsmapp->insert(pair(key, refs)); + } else { + (*it).second->push_back(nodep); + } + nodep->user3p(m_sel); // attach the sel to this varref + } + nodep->iterateChildren(*this); + } + + // Default - Iterate children to find all possible varrefs + virtual void visit(AstNode* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + +public: + TristateExpander(AstModule* nodep, VarMap* lhsmapp) { + m_modp = nodep; + m_lhsmapp = lhsmapp; + m_unique = 0; + m_sel = NULL; + nodep->accept(*this); // visit eveyone + } + virtual ~TristateExpander() { } +}; + +//###################################################################### + +class TristateVisitor : public TristateBaseVisitor { +private: + // NODE STATE + // NOT Cleared on Netlist + // AstVarRef::user1p -> used to track any newly create __en signals + // AstVarRef::user3p -> a parent SEL + // AstVar::user1p -> used to track any newly create __en signals + // AstVar::user2 -> used for pullup/pulldown direction + + AstUser1InUse m_inuser1; + AstUser2InUse m_inuser2; + AstUser3InUse m_inuser3; + + AstModule* m_modp; // Current module + AstCell* m_cellp; // current cell + int m_unique; + + virtual void visit(AstNetlist* nodep, AstNUser*) { + AstNode::user1ClearTree(); + nodep->iterateChildrenBackwards(*this); + } + + virtual void visit(AstModule* nodep, AstNUser*) { + UINFO(9," MOD "<iterateChildren(*this); + m_modp = NULL; + + // go through each multiple lhs driver & collapse it to a single driver + for (VarMap::iterator nextit, it=lhsmapp->begin(); it != lhsmapp->end(); it=nextit) { + nextit = it; ++nextit; + m_unique = 0; + AstVar* lhsp = (*it).first; + RefVec* refs = (*it).second; + bool isOutput = (lhsp->varType() == AstVarType::OUTPUT) && (nodep->level() > 1); // force termination at top level + + if (refs->size() < 2 && isOutput) { + // if only one driver and this is an output, then exit and + // let the driver propagate on its own. If the signals + // terminates at this level, then we need to let the + // undriven state get generated. + lhsmapp->erase(lhsp); + delete refs; + continue; + } + + + UINFO(9, " Checking " << refs->size() << " drivers for tristates signals on net " << lhsp << endl); + int pull = 0; // initially assume no pull direction + + // Now remove and multple lhs signals that do not have __en for + // all possible drivers. + bool complete = true; + int found_one = 0; + + for (RefVec::iterator ii=refs->begin(); ii != refs->end(); ++ii) { + AstVarRef* refp = (*ii); + if (!refp->user1p()) { // if no __en signal, then delete the entry + complete = false; + } else { + found_one++; + } + } + if (!complete) { + if (found_one) { + UINFO(9, " Problem mixing tristate and low-Z on " << lhsp << endl); + UINFO(9, " Found " << found_one << " __en signals from of " << refs->size() << " possible drivers" << endl); + // not sure what I should do here other than error that they are mixing low-Z and tristate drivers. + // The other scenerio, and probably more likely, is that they are using a high-Z construct that + // is not supported. Improving the high-Z detection logic will reduce the occurance of this failure. + v3error("Mixing tristate and low-Z drivers. Perhaps you are using a high-Z construct not supported"); + } else { + UINFO(9, " No tristates found on " << lhsp <erase(lhsp); + delete refs; + continue; + } + + UINFO(9, " TRISTATE LHS DRIVER FOUND:" << lhsp << endl); + + AstNode* orp = NULL,* andp = NULL,* undrivenp = NULL,* newenlogicp = NULL; + + // loop through the lhs drivers to build the driver resolution logic + for (RefVec::iterator ii=refs->begin(); ii != refs->end(); ++ii) { + AstVarRef* refp = (*ii); + int w = lhsp->width(); + int wfill = 0; // width filler when necessary due to sels + AstSel* selp = NULL; + if (refp->user3p()) { // this varref has a sel + selp = (AstSel*) refp->user3p(); + w = selp->widthConst(); + wfill = lhsp->width() - w; + } + + // create a new var for this assignment. + AstVar* enp = (AstVar*)refp->user1p(); + AstVar* newlhsp = new AstVar(lhsp->fileline(), + AstVarType::MODULETEMP, + lhsp->name()+"__lhs"+cvtToStr(m_unique++), + (w>1) ? new AstRange(nodep->fileline(), w-1, 0) : (AstRange *) NULL); + nodep->addStmtp(newlhsp); + + // now append this driver to the driver logic. + AstNode* ref1 = new AstVarRef(nodep->fileline(), newlhsp,false); + AstNode* ref2 = new AstVarRef(nodep->fileline(), enp, false); + andp = new AstAnd(nodep->fileline(), ref1, ref2); + + + AstVar* bitselp = NULL; + if (selp) { // this varref has a sel + int ws = V3Number::log2b(lhsp->width())+1; + bitselp = new AstVar(lhsp->fileline(), + AstVarType::MODULETEMP, + lhsp->name()+"__sel"+cvtToStr(m_unique-1), + (ws>1) ? new AstRange(nodep->fileline(), ws-1, 0) : (AstRange*) NULL); + // + nodep->addStmtp(bitselp); + nodep->addStmtp(new AstAssignW(lhsp->fileline(), + new AstVarRef(lhsp->fileline(), bitselp, true), + selp->lsbp()->cloneTree(false))); + andp = new AstShiftL(lhsp->fileline(), + new AstConcat(lhsp->fileline(), new AstConst(lhsp->fileline(), V3Number(lhsp->fileline(), wfill, 0)), andp), + new AstVarRef(lhsp->fileline(), bitselp, false), + lhsp->width() + ); + + selp->replaceWith(new AstVarRef(refp->fileline(), newlhsp, true)); + selp->deleteTree(); + } else { + refp->varp(newlhsp); // assign the new var to the varref + refp->name(newlhsp->name()); + } + + // or this to the others + orp = (!orp) ? andp : new AstOr(nodep->fileline(), orp, andp); + + if (isOutput) { + AstNode *en1p = new AstVarRef(nodep->fileline(), enp, false); + if (selp) { + en1p = new AstShiftL(enp->fileline(), + new AstConcat(lhsp->fileline(), new AstConst(lhsp->fileline(), V3Number(lhsp->fileline(), wfill, 0)), en1p), + new AstVarRef(lhsp->fileline(), bitselp, false), + lhsp->width() + ); + } + if (!newenlogicp) { + newenlogicp = en1p; + } else { + newenlogicp = new AstOr(nodep->fileline(), newenlogicp, en1p); + } + } else { + if (!undrivenp) { + undrivenp = new AstNot(nodep->fileline(), new AstVarRef(nodep->fileline(), enp, false)); + if (selp) + undrivenp = new AstShiftL(enp->fileline(), + new AstConcat(lhsp->fileline(), new AstConst(lhsp->fileline(), V3Number(lhsp->fileline(), wfill, 0)), undrivenp), + new AstVarRef(lhsp->fileline(), bitselp, false), + lhsp->width()); + } else { + AstNode *tmp = new AstNot(nodep->fileline(), new AstVarRef(nodep->fileline(), enp, false)); + if (selp) { + tmp = new AstShiftL(enp->fileline(), + new AstConcat(lhsp->fileline(), new AstConst(lhsp->fileline(), V3Number(lhsp->fileline(), wfill, 0)), tmp), + new AstVarRef(lhsp->fileline(), bitselp, false), + lhsp->width()); + } + undrivenp = new AstAnd(nodep->fileline(), tmp, undrivenp); + } + } + + refp->user1p(NULL); // clear the user1p() as we done with it in the VarRef at this point + + if (enp->user2()) { // if this net is pulled up/down + int newpull = enp->user2(); + if (pull == 0) { + pull = newpull; + } else if (newpull != pull) { + pull = -1; // conflict over the pull direction + } + } + } + if (isOutput) { + AstVar* newenp = new AstVar(lhsp->fileline(), + AstVarType::OUTPUT, + lhsp->name()+"__enout"+cvtToStr(m_unique++), + lhsp); + nodep->addStmtp(newenp); + nodep->addStmtp(new AstAssignW(lhsp->fileline(), + new AstVarRef(lhsp->fileline(), newenp, true), + newenlogicp)); + newenp->user2(pull); // put the pull direction in the next __en signal to pass it up + lhsp->user1p(newenp); // put the new __en signal in the var so it can be pushed up the hierarchy. + + } else { // this is the level where the signal terminates, we do final conflict resolution here + UINFO(9, " Terminating tristate logic for " << lhsp->name() << endl); + UINFO(9, " Pull direction is " << pull << " where -1=X, 0=Z, 1=low, 2=high." << endl); + // figure out what to drive when no one is driving the bus + V3Number num(nodep->fileline(), lhsp->width()); + if (pull==0) { + num.setAllBitsZ(); + } else if (pull==1) { + num.setAllBits0(); + } else if (pull==2) { + num.setAllBits1(); + } else { + num.setAllBitsX(); + } + undrivenp = new AstAnd(nodep->fileline(), undrivenp, + new AstConst(nodep->fileline(), num)); + orp = new AstOr(nodep->fileline(), orp, undrivenp); + } + nodep->addStmtp(new AstAssignW(lhsp->fileline(), + new AstVarRef(lhsp->fileline(), lhsp, true), orp)); + + // delete the map and vector list now that we have collapsed it. + lhsmapp->erase(lhsp); + delete refs; + } + delete lhsmapp; // delete the map now that we are done + nodep->user1p(NULL); + } + + virtual void visit(AstCell* nodep, AstNUser*) { + m_cellp = nodep; + nodep->iterateChildren(*this); + m_cellp = NULL; + } + + AstVarRef* findVarRef(AstPin* nodep) { + if (nodep->exprp()->castVarRef()) { + return nodep->exprp()->castVarRef(); + } else if (nodep->exprp()->castSel() && nodep->exprp()->castSel()->fromp()->castVarRef()) { + return nodep->exprp()->castSel()->fromp()->castVarRef(); + } else { + return NULL; + } + } + + virtual void visit(AstPin* nodep, AstNUser*) { + // Check to see if any output pins have __en pins and create the __en pins to match + AstVarRef* refp = findVarRef(nodep); + + if (refp && refp->lvalue() && nodep->modVarp()->user1p()) { + AstVar* enchildp = (AstVar*)nodep->modVarp()->user1p(); + UINFO(9, " Pulling __en var" << enchildp << endl); + AstVar* enp = new AstVar(enchildp->fileline(), + AstVarType::OUTPUT, + enchildp->name()+cvtToStr(m_unique++), + enchildp); + enp->user2(enchildp->user2()); + m_modp->addStmtp(enp); + AstPin* pinp = new AstPin(nodep->fileline(), + nodep->pinNum(), + enp->name(), + new AstVarRef(nodep->fileline(), enp, true)); + AstVarRef *rp = findVarRef(pinp); + rp->replaceWith(new AstVarRef(nodep->fileline(), enp, true)); + rp->deleteTree(); + pinp->width(enp->width(),enp->widthMin()); + pinp->modVarp(enchildp); + m_cellp->addPinsp(pinp); + refp->user1p(enp); + refp->varp()->user1p(enp); + } + } + + // Default: Just iterate + virtual void visit(AstNode* nodep, AstNUser*) { + return; // no need to iterate further b/c AstCell and AstPin grab everything this visitor needs to hook up newly created __en pins correctly + } + +public: + // CONSTUCTORS + TristateVisitor(AstNode* nodep) { + nodep->accept(*this); + } + virtual ~TristateVisitor() { } +}; + +//###################################################################### + +class InoutVisitor : public TristateBaseVisitor { + // This visitor walks the tree and expands inouts into two ports. + // The original port is switched to an output port and an additional + // input port is created with a name suffix of __in. All LHS logic + // is left along connected to the original port. All RHS varrefs + // that point the original port are switched to point to the __in + // input port. The expansion is accomplished in a series of three + // stages. First entire hierarchy is visited and all inout vars are + // expanded in an output and input var. Then a second pass through + // the hierarchy switches all the rhs varrefs to point to the newly + // created __in vars. Finally a third pass looks at the ports and + // creates any needed __in ports. + +private: + // NODE STATE + // Cleared on Netlist + // AstVar::user1p -> a pointer to the created input port __in + + AstUser1InUse m_inuser1; + + enum States { + CONVERT_VARS, + CONVERT_VARREFS, + CONVERT_PINS + }; + + AstModule* m_modp; // Current module + AstCell* m_cellp; // Current cell + AstNodeFTask* m_ftaskp; // Current function/task + States m_state; + + virtual void visit(AstNetlist* nodep, AstNUser*) { + m_state = CONVERT_VARS; + nodep->iterateChildren(*this); + m_state = CONVERT_VARREFS; + nodep->iterateChildren(*this); + m_state = CONVERT_PINS; + nodep->iterateChildren(*this); + } + + virtual void visit(AstModule* nodep, AstNUser*) { + m_modp = nodep; + nodep->iterateChildren(*this); + m_modp = NULL; + } + + virtual void visit(AstNodeFTask* nodep, AstNUser*) { + m_ftaskp = nodep; + nodep->iterateChildren(*this); + m_ftaskp = NULL; + } + + virtual void visit(AstVar* nodep, AstNUser*) { + if (m_state == CONVERT_VARS) { + if (nodep->isTristate() && !m_ftaskp) { + // create the input var and leave the original as the output var + AstVar* varinp = nodep->cloneTree(false)->castVar(); + varinp->name(varinp->name() + "__in"); + varinp->varType2In(); + + nodep->combineType(AstVarType::OUTPUT); + nodep->varType2Out(); + m_modp->addStmtp(varinp); + nodep->user1p(varinp); + } + } + } + + virtual void visit(AstVarRef* nodep, AstNUser*) { + if (m_state == CONVERT_VARREFS) { + if (!nodep->lvalue() && nodep->varp()->user1p()) { + nodep->varp((AstVar*) nodep->varp()->user1p()); + nodep->name(nodep->varp()->name()); + } + } + } + + virtual void visit(AstCell* nodep, AstNUser*) { + m_cellp = nodep; + nodep->iterateChildren(*this); + m_cellp = NULL; + } + + virtual void visit(AstPin* nodep, AstNUser*) { + if (m_state == CONVERT_PINS) { + if (nodep->modVarp()->user1p()) { + // create the input pin + AstVarRef* refp = nodep->exprp()->castVarRef(); + AstVar* inp; + if (refp->varp()->user1p()) { // this is a tristate + inp = (AstVar*) refp->varp()->user1p(); + } else { + inp = refp->varp(); + } + AstPin* pinp = new AstPin(nodep->fileline(), + nodep->pinNum(), + nodep->name() + "__in", + new AstVarRef(nodep->fileline(), inp, false)); + m_cellp->addPinsp(pinp); + + // now link it + pinp->modVarp((AstVar*) nodep->modVarp()->user1p()); + } + } + } + + // Default: Just iterate + virtual void visit(AstNode* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + +public: + // CONSTUCTORS + InoutVisitor(AstNode* nodep) { + m_modp = NULL; + m_cellp = NULL; + m_ftaskp = NULL; + nodep->accept(*this); + } + virtual ~InoutVisitor() { } +}; + +//###################################################################### +// Tristate class functions + +void V3Tristate::tristateAll(AstNetlist* nodep) { + UINFO(2,__FUNCTION__<<": "<width(awidth,awidth); } else { - if (nodep->modVarp()->isTristate()) nodep->v3error("Unsupported: inouts: Verilator is a 2 state simulator\n"); - // if support tristates need some mess to force both sides to proper size + if (nodep->modVarp()->isTristate()) { + if (pinwidth != expwidth) { + nodep->v3error("Unsupported: Port connection "<prettyName()<<" to inout signal " + <<" requires "<exprp()->typeName() + <<" generates "<dumpTreeFile(v3Global.debugFilename("const.tree")); + // Expand Inouts + V3Tristate::inoutAll(v3Global.rootp()); + v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("inouts.tree")); + // Remove cell arrays (must be between V3Width and scoping) V3Inst::dearrayAll(v3Global.rootp()); //v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("dearray.tree")); + V3Tristate::tristateAll(v3Global.rootp()); + v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("tristate.tree")); + // Move assignments from X into MODULE temps. // (Before flattening, so each new X variable is shared between all scopes of that module.) V3Unknown::unknownAll(v3Global.rootp()); diff --git a/src/verilog.l b/src/verilog.l index 04ebb58e2..3e8b741e1 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -213,6 +213,8 @@ escid \\[^ \t\f\r\n]+ "output" {yylval.fileline = CRELINE(); return yOUTPUT;} "parameter" {yylval.fileline = CRELINE(); return yPARAMETER;} "posedge" {yylval.fileline = CRELINE(); return yPOSEDGE;} + "pulldown" {yylval.fileline = CRELINE(); return yPULLDOWN;} + "pullup" {yylval.fileline = CRELINE(); return yPULLUP;} "reg" {yylval.fileline = CRELINE(); return yREG;} "scalared" {yylval.fileline = CRELINE(); return ySCALARED;} "specify" {yylval.fileline = CRELINE(); return ySPECIFY;} @@ -260,8 +262,6 @@ escid \\[^ \t\f\r\n]+ "notif1" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} "pmos" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} "primitive" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} - "pulldown" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} - "pullup" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} "pull0" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} "pull1" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} "rcmos" {yyerrorf("Unsupported: Verilog 1995 reserved word not implemented: %s",yytext);} diff --git a/src/verilog.y b/src/verilog.y index bdef9894b..1dd9452e7 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -111,6 +111,7 @@ class AstSenTree; AstNode* nodep; AstAssignW* assignwp; + AstPull* pullp; AstBegin* beginp; AstCase* casep; AstCaseItem* caseitemp; @@ -205,6 +206,8 @@ class AstSenTree; %token yPOSEDGE "posedge" %token yPRIORITY "priority" %token yPROPERTY "property" +%token yPULLDOWN "pulldown" +%token yPULLUP "pullup" %token yREG "reg" %token ySCALARED "scalared" %token ySIGNED "signed" @@ -1254,6 +1257,8 @@ gateDecl: | yNOR delayE gateNorList ';' { $$ = $3; } | yXOR delayE gateXorList ';' { $$ = $3; } | yXNOR delayE gateXnorList ';' { $$ = $3; } + | yPULLUP delayE gatePullupList ';' { $$ = $3; } + | yPULLDOWN delayE gatePulldownList ';' { $$ = $3; } ; gateBufList: @@ -1288,6 +1293,14 @@ gateXnorList: gateXnor { $$ = $1; } | gateXnorList ',' gateXnor { $$ = $1->addNext($3); } ; +gatePullupList: + gatePullup { $$ = $1; } + | gatePullupList ',' gatePullup { $$ = $1->addNext($3); } + ; +gatePulldownList: + gatePulldown { $$ = $1; } + | gatePulldownList ',' gatePulldown { $$ = $1->addNext($3); } + ; gateBuf: gateIdE instRangeE '(' varRefDotBit ',' expr ')' { $$ = new AstAssignW ($3,$4,$6); $$->allowImplicit(true); } ; @@ -1305,7 +1318,10 @@ gateXor: gateIdE instRangeE '(' varRefDotBit ',' gateXorPinList ')' { ; gateXnor: gateIdE instRangeE '(' varRefDotBit ',' gateXorPinList ')' { $$ = new AstAssignW ($3,$4,new AstNot($5,$6)); $$->allowImplicit(true); } ; - +gatePullup: gateIdE instRangeE '(' varRefDotBit ')' { $$ = new AstPull ($3, $4, true); } + ; +gatePulldown: gateIdE instRangeE '(' varRefDotBit ')' { $$ = new AstPull ($3, $4, false); } + ; gateIdE: /*empty*/ {} | yaID {} ; diff --git a/test_regress/t/t_tri_inout.cpp b/test_regress/t/t_tri_inout.cpp new file mode 100644 index 000000000..4581e5e15 --- /dev/null +++ b/test_regress/t/t_tri_inout.cpp @@ -0,0 +1,55 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +#include "Vt_tri_inout.h" + +Vt_tri_inout *tb = NULL; + +double sc_time_stamp() { + return 0; +} + +bool check() { + bool pass; + int Z; + if (tb->SEL) { + Z = tb->A; + } else { + Z = tb->B; + } + + if (tb->Z == tb->Y1 && tb->Z == tb->Y2 && tb->Z == Z) { + printf("PASS: "); + pass = true; + } else { + printf("FAIL: "); + pass = false; + } + printf ("SEL=%d A=%d B=%d Z=%d Y1=%d Y2=%d\n", tb->SEL, tb->A, tb->B, tb->Z, tb->Y1, tb->Y2); + return pass; +} + +int main() { + bool pass = true; + tb = new Vt_tri_inout("tb"); + + // loop through every possibility and check the result + for (tb->SEL=0; tb->SEL<2; tb->SEL++) { + for (tb->A=0; tb->A<2; tb->A++) { + for (tb->B=0; tb->B<2; tb->B++) { + tb->eval(); + if (!check()) { + pass = false; + } + } + } + } + + if(pass) { + cout << "*-* All Finished *-*" << endl; + tb->final(); + } else { + vl_fatal(__FILE__,__LINE__,"top", "Unexpected results from inout test\n"); + } + return 0; +} diff --git a/test_regress/t/t_tri_inout.pl b/test_regress/t/t_tri_inout.pl new file mode 100755 index 000000000..a2959d48c --- /dev/null +++ b/test_regress/t/t_tri_inout.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 by Wilson Snyder. This program is free software; you can +# redistribute it and/or modify it under the terms of either the GNU +# General Public License or the Perl Artistic License. + +compile ( + make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe t/$Last_Self->{name}.cpp"], + ) if $Last_Self->{v3}; + +execute ( + check_finished=>1, + ) if $Last_Self->{v3}; + +ok(1); +1; diff --git a/test_regress/t/t_tri_inout.v b/test_regress/t/t_tri_inout.v new file mode 100644 index 000000000..52ae53b39 --- /dev/null +++ b/test_regress/t/t_tri_inout.v @@ -0,0 +1,16 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +module top (input A, input B, input SEL, output Y1, output Y2, output Z); + io io1(.A(A), .OE( SEL), .Z(Z), .Y(Y1)); + pass io2(.A(B), .OE(!SEL), .Z(Z), .Y(Y2)); +endmodule + +module pass (input A, input OE, inout Z, output Y); + io io(.A(A), .OE(OE), .Z(Z), .Y(Y)); +endmodule + +module io (input A, input OE, inout Z, output Y); + assign Z = (OE) ? A : 1'bz; + assign Y = Z; +endmodule diff --git a/test_regress/t/t_tri_pullup.cpp b/test_regress/t/t_tri_pullup.cpp new file mode 100644 index 000000000..182c0f271 --- /dev/null +++ b/test_regress/t/t_tri_pullup.cpp @@ -0,0 +1,57 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +#include "Vt_tri_pullup.h" + +Vt_tri_pullup *tb = NULL; + +double sc_time_stamp() { + return 0; +} + +bool check() { + bool pass; + int Z, Y, X; + if (tb->OE) { + Z = tb->A; + Y = tb->A; + X = tb->A; + } else { + Z = 1; + Y = 0; + X = 1; + } + + if (tb->Z == Z && tb->Y == Y && tb->X == X) { + printf("PASS: "); + pass = true; + } else { + printf("FAIL: "); + pass = false; + } + printf("OE=%d A=%d X=%d Y=%d Z=%d\n", tb->OE, tb->A, tb->X, tb->Y, tb->Z); + return pass; +} + +int main() { + bool pass = true; + tb = new Vt_tri_pullup("tb"); + + // loop through every possibility and check the result + for (tb->OE=0; tb->OE<2; tb->OE++) { + for (tb->A=0; tb->A<2; tb->A++) { + tb->eval(); + if (!check()) { + pass = false; + } + } + } + + if (pass) { + cout << "*-* All Finished *-*" << endl; + tb->final(); + } else { + vl_fatal(__FILE__,__LINE__,"top", "Unexpected results from pullup test\n"); + } + return 0; +} diff --git a/test_regress/t/t_tri_pullup.pl b/test_regress/t/t_tri_pullup.pl new file mode 100755 index 000000000..9a4056310 --- /dev/null +++ b/test_regress/t/t_tri_pullup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 by Wilson Snyder. This program is free software; you can +# redistribute it and/or modify it under the terms of either the GNU +# General Public License or the Perl Artistic License. + +compile ( + make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe t/$Last_Self->{name}.cpp --debug"], + ) if $Last_Self->{v3}; + +execute ( + check_finished=>1, + ) if $Last_Self->{v3}; + +ok(1); +1; diff --git a/test_regress/t/t_tri_pullup.v b/test_regress/t/t_tri_pullup.v new file mode 100644 index 000000000..68617da5c --- /dev/null +++ b/test_regress/t/t_tri_pullup.v @@ -0,0 +1,26 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +module top (input A, input OE, output X, output Y, output Z); + + pullup p1(Z); + assign Z = OE ? A : 1'bz; + + pulldown p2(Y); + assign Y = OE ? A : 1'bz; + + pass pass(.A(A), .OE(OE), .X(X)); + pullup_module p(X); +endmodule + +module pass (input A, input OE, inout X); + io io(.A(A), .OE(OE), .X(X)); +endmodule + +module io (input A, input OE, inout X); + assign X = (OE) ? A : 1'bz; +endmodule + +module pullup_module (output X); + pullup p1(X); +endmodule diff --git a/test_regress/t/t_tri_select.cpp b/test_regress/t/t_tri_select.cpp new file mode 100644 index 000000000..eb1984db5 --- /dev/null +++ b/test_regress/t/t_tri_select.cpp @@ -0,0 +1,57 @@ +#include "Vt_tri_select.h" + +Vt_tri_select *tb = NULL; + +double sc_time_stamp() { + return 0; +} + +bool check() { + bool pass = true; + + int Y = (tb->OE1 & !tb->OE2) ? tb->A1 + : (!tb->OE1 & tb->OE2) ? tb->A2 + : (tb->OE1 & tb->OE2) ? (tb->A1 | tb->A2) + : 3; // pullup + + int W = (((tb->OE2) ? (tb->A2 & 0x1) : 0) << tb->A1) + | (((tb->OE1) ? (tb->A2 >> 1)&0x1 : 0) << tb->A2); + + if(tb->Y1 == tb->Y2 && tb->Y1 == Y && tb->W == W) { + pass = true; + printf("Pass: "); + } else { + pass = false; + printf("Fail: "); + } + + printf("Read: OE1=%d OE2=%d A1=0x%x A2=0x%x Y1=0x%x Y2=0x%x W=0x%x Expected: Y1=Y2=%d and W=0x%x\n", tb->OE1, tb->OE2, tb->A1, tb->A2, tb->Y1, tb->Y2, tb->W, Y,W); + return pass; +} + +int main() { + bool pass = true; + tb = new Vt_tri_select("tb"); + + // loop through every possibility and check the result + for (tb->OE1=0; tb->OE1<2; tb->OE1++) { + for (tb->OE2=0; tb->OE2<2; tb->OE2++) { + for (tb->A1=0; tb->A1<4; tb->A1++) { + for (tb->A2=0; tb->A2<4; tb->A2++) { + tb->eval(); + if(!check()) { + pass = false; + } + } + } + } + } + + if(pass) { + cout << "*-* All Finished *-*" << endl; + tb->final(); + } else { + vl_fatal(__FILE__,__LINE__,"top", "Unexpected results from t_tri_select\n"); + } + return 0; +} diff --git a/test_regress/t/t_tri_select.pl b/test_regress/t/t_tri_select.pl new file mode 100755 index 000000000..9a4056310 --- /dev/null +++ b/test_regress/t/t_tri_select.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 by Wilson Snyder. This program is free software; you can +# redistribute it and/or modify it under the terms of either the GNU +# General Public License or the Perl Artistic License. + +compile ( + make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe t/$Last_Self->{name}.cpp --debug"], + ) if $Last_Self->{v3}; + +execute ( + check_finished=>1, + ) if $Last_Self->{v3}; + +ok(1); +1; diff --git a/test_regress/t/t_tri_select.v b/test_regress/t/t_tri_select.v new file mode 100644 index 000000000..0f82a9385 --- /dev/null +++ b/test_regress/t/t_tri_select.v @@ -0,0 +1,38 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +`define WIDTH 2 + +module top ( + input OE1, + input OE2, + input [`WIDTH-1:0] A1, + input [`WIDTH-1:0] A2, + output [`WIDTH-1:0] Y1, + output [`WIDTH-1:0] Y2, + output [`WIDTH**2-1:0] W); + + assign W[A1] = (OE2) ? A2[0] : 1'bz; + assign W[A2] = (OE1) ? A2[1] : 1'bz; + + // have 2 different 'chips' drive the PAD to act like a bi-directional bus + wire [`WIDTH-1:0] PAD; + io_ring io_ring1(.OE(OE1), .A(A1), .Y(Y1), .PAD(PAD)); + io_ring io_ring2(.OE(OE2), .A(A2), .Y(Y2), .PAD(PAD)); + + pullup p1(PAD); +// pulldown p1(PAD); + + + wire [5:0] fill = { 4'b0, A1 }; + +endmodule + +module io_ring (input OE, input [`WIDTH-1:0] A, output [`WIDTH-1:0] Y, inout [`WIDTH-1:0] PAD); + io io[`WIDTH-1:0](.OE(OE), .I(A), .O(Y), .PAD(PAD)); +endmodule + +module io (input OE, input I, output O, inout PAD); + assign O = PAD; + assign PAD = OE ? I : 1'bz; +endmodule diff --git a/test_regress/t/t_tristate.cpp b/test_regress/t/t_tristate.cpp new file mode 100644 index 000000000..193d85e04 --- /dev/null +++ b/test_regress/t/t_tristate.cpp @@ -0,0 +1,48 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +#include "Vt_tristate.h" + +Vt_tristate *tb = NULL; + +double sc_time_stamp() { + return 0; +} + +bool check() { + bool pass; + int c = (tb->A >> tb->SEL) & 0x1; + + if(tb->Z == c && tb->Y == c && tb->X == c && tb->W == c) { + pass = true; + printf("PASS: "); + } else { + pass = false; + printf("FAIL: "); + } + printf("SEL=%d A=%d W=%d X=%d Y=%d Z=%d\n", tb->SEL, tb->A, tb->W, tb->X, tb->Y, tb->Z); + return pass; +} + +int main() { + bool pass = true; + tb = new Vt_tristate("tb"); + + // loop through every possibility and check the result + for (tb->SEL=0; tb->SEL<2; tb->SEL++) { + for (tb->A=0; tb->A<4; tb->A++) { + tb->eval(); + if(!check()) { + pass =false; + } + } + } + + if(pass) { + cout << "*-* All Finished *-*" << endl; + tb->final(); + } else { + vl_fatal(__FILE__,__LINE__,"top", "Unexpected results from tristate test\n"); + } + return 0; +} diff --git a/test_regress/t/t_tristate.pl b/test_regress/t/t_tristate.pl new file mode 100755 index 000000000..a2959d48c --- /dev/null +++ b/test_regress/t/t_tristate.pl @@ -0,0 +1,20 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 by Wilson Snyder. This program is free software; you can +# redistribute it and/or modify it under the terms of either the GNU +# General Public License or the Perl Artistic License. + +compile ( + make_top_shell => 0, + make_main => 0, + v_flags2 => ["--exe t/$Last_Self->{name}.cpp"], + ) if $Last_Self->{v3}; + +execute ( + check_finished=>1, + ) if $Last_Self->{v3}; + +ok(1); +1; diff --git a/test_regress/t/t_tristate.v b/test_regress/t/t_tristate.v new file mode 100644 index 000000000..c14a6b62b --- /dev/null +++ b/test_regress/t/t_tristate.v @@ -0,0 +1,27 @@ +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2008 by Lane Brooks + +module top (input SEL, input[1:0] A, output Z, output Y, output X, output W); + assign Z = ( SEL) ? A[1] : 1'bz; + tbuf tbuf(.A(A[0]), .OE(!SEL), .Z(Z)); +// assign Z = (!SEL) ? A[0] : 1'bz; + + tbuf mux0[1:0](.A(A), .OE({SEL,!SEL}), .Z(Y)); + + pass mux1(.A(A), .SEL(SEL), .Z(X)); + mux mux2(.A(A), .SEL(SEL), .Z(W)); +endmodule + +module pass (input[1:0] A, input SEL, output Z); + tbuf tbuf1(.A(A[1]), .OE(SEL), .Z(Z)); + tbuf tbuf0(.A(A[0]), .OE(!SEL),.Z(Z)); +endmodule + +module tbuf (input A, input OE, output Z); + assign Z = (OE) ? A : 1'bz; +endmodule + +module mux (input[1:0] A, input SEL, output Z); + assign Z = (SEL) ? A[1] : 1'bz; + assign Z = (!SEL)? A[0] : 1'bz; +endmodule