Tristate support; merge from branch.

This commit is contained in:
Lane Brooks 2009-01-06 11:03:57 -05:00 committed by Wilson Snyder
parent 6f81a9cb1e
commit 0e4f9170fa
24 changed files with 1242 additions and 8 deletions

View File

@ -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.

View File

@ -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

View File

@ -192,6 +192,7 @@ RAW_OBJS = \
V3Task.o \
V3Trace.o \
V3TraceDecl.o \
V3Tristate.o \
V3Unknown.o \
V3Unroll.o \
V3Width.o \

View File

@ -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)

View File

@ -291,6 +291,14 @@ V3Number& V3Number::setSingleBits(char value) {
return *this;
}
V3Number& V3Number::setAllBits0() {
for (int i=0; i<words(); i++) { m_value[i] = m_valueX[i]=0; }
return *this;
}
V3Number& V3Number::setAllBits1() {
for (int i=0; i<words(); i++) { m_value[i]= ~0; m_valueX[i] = 0; }
return *this;
}
V3Number& V3Number::setAllBitsX() {
for (int i=0; i<words(); i++) { m_value[i]=m_valueX[i] = ~0; }
return *this;
@ -584,6 +592,22 @@ V3Number& V3Number::opBitsXZ (const V3Number& lhs) { // 0/1->1, 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; bit<this->width(); 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; bit<this->width(); bit++) {
if (!lhs.bitIsZ(bit)) { setBit(bit,1); }
}
return *this;
}
//======================================================================
// Operators - Simple per-bit logical ops

View File

@ -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;i<words();i++) {if (m_valueX[i]) return true;} return false; }
bool hasZ() const { for(int i=0;i<words();i++) {if((~m_value[i]) & m_valueX[i]) return true;} return false;}
bool isAllZ() const { for(int i=0;i<width();i++) { if(!bitIsZ(i)){return false;} } return true; }
bool isEqZero() const;
bool isNeqZero() const;
bool isEqOne() const;
@ -158,6 +162,8 @@ public:
V3Number& opBitsNonX(const V3Number& lhs); // 0/1->1, 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

692
src/V3Tristate.cpp Normal file
View File

@ -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 <cstdio>
#include <cstdarg>
#include <unistd.h>
#include <algorithm>
#include <map>
#include "V3Global.h"
#include "V3Tristate.h"
#include "V3Ast.h"
#include "V3Const.h"
#include "V3Stats.h"
//######################################################################
typedef std::vector<AstVar*> VarVec;
typedef std::vector<AstVarRef*> RefVec;
typedef std::map<AstVar*, RefVec*> 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<AstVar*, RefVec*>(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 "<<nodep<<endl);
m_unique = 0;
VarMap* lhsmapp = new VarMap();
// expand tristate nodes and detect multiple LHS drivers for this module
TristateExpander(nodep, lhsmapp);
// iterate the children to grab any __en signals from subcells
m_modp = nodep;
nodep->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 <<endl);
}
lhsmapp->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__<<": "<<endl);
TristateVisitor visitor (nodep);
}
void V3Tristate::inoutAll(AstNetlist* nodep) {
UINFO(2,__FUNCTION__<<": "<<endl);
InoutVisitor visitor (nodep);
}

37
src/V3Tristate.h Normal file
View File

@ -0,0 +1,37 @@
// -*- C++ -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add Unknown assigns
//
// Code available from: http://www.veripool.org/verilator
//
// AUTHORS: Wilson Snyder with 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.
//
//*************************************************************************
#ifndef _V3TRISTATE_H_
#define _V3TRISTATE_H_ 1
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include "V3Ast.h"
//============================================================================
class V3Tristate {
public:
static void tristateAll(AstNetlist* nodep);
static void inoutAll(AstNetlist* nodep);
};
#endif // Guard

View File

@ -700,8 +700,15 @@ private:
}
nodep->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 "<<nodep->prettyName()<<" to inout signal "
<<" requires "<<pinwidth
<<" bits, but connection's "<<nodep->exprp()->typeName()
<<" generates "<<expwidth<<" bits.");
// otherwise would need some mess to force both sides to proper size
}
}
if (inputPin) {
// input pin is lhs, expr is rhs; resize expr to match
awidth = pinwidth;

View File

@ -79,6 +79,7 @@
#include "V3Task.h"
#include "V3Trace.h"
#include "V3TraceDecl.h"
#include "V3Tristate.h"
#include "V3Unknown.h"
#include "V3Unroll.h"
#include "V3Width.h"
@ -182,10 +183,17 @@ void process () {
V3Const::constifyAllLint(v3Global.rootp());
v3Global.rootp()->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());

View File

@ -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);}

View File

@ -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<fileline> yPOSEDGE "posedge"
%token<fileline> yPRIORITY "priority"
%token<fileline> yPROPERTY "property"
%token<fileline> yPULLDOWN "pulldown"
%token<fileline> yPULLUP "pullup"
%token<fileline> yREG "reg"
%token<fileline> ySCALARED "scalared"
%token<fileline> ySIGNED "signed"
@ -1254,6 +1257,8 @@ gateDecl<nodep>:
| yNOR delayE gateNorList ';' { $$ = $3; }
| yXOR delayE gateXorList ';' { $$ = $3; }
| yXNOR delayE gateXnorList ';' { $$ = $3; }
| yPULLUP delayE gatePullupList ';' { $$ = $3; }
| yPULLDOWN delayE gatePulldownList ';' { $$ = $3; }
;
gateBufList<nodep>:
@ -1288,6 +1293,14 @@ gateXnorList<nodep>:
gateXnor { $$ = $1; }
| gateXnorList ',' gateXnor { $$ = $1->addNext($3); }
;
gatePullupList<nodep>:
gatePullup { $$ = $1; }
| gatePullupList ',' gatePullup { $$ = $1->addNext($3); }
;
gatePulldownList<nodep>:
gatePulldown { $$ = $1; }
| gatePulldownList ',' gatePulldown { $$ = $1->addNext($3); }
;
gateBuf<assignwp>: gateIdE instRangeE '(' varRefDotBit ',' expr ')' { $$ = new AstAssignW ($3,$4,$6); $$->allowImplicit(true); }
;
@ -1305,7 +1318,10 @@ gateXor<assignwp>: gateIdE instRangeE '(' varRefDotBit ',' gateXorPinList ')' {
;
gateXnor<assignwp>: gateIdE instRangeE '(' varRefDotBit ',' gateXorPinList ')' { $$ = new AstAssignW ($3,$4,new AstNot($5,$6)); $$->allowImplicit(true); }
;
gatePullup<pullp>: gateIdE instRangeE '(' varRefDotBit ')' { $$ = new AstPull ($3, $4, true); }
;
gatePulldown<pullp>: gateIdE instRangeE '(' varRefDotBit ')' { $$ = new AstPull ($3, $4, false); }
;
gateIdE: /*empty*/ {}
| yaID {}
;

View File

@ -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;
}

20
test_regress/t/t_tri_inout.pl Executable file
View File

@ -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;

View File

@ -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

View File

@ -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;
}

20
test_regress/t/t_tri_pullup.pl Executable file
View File

@ -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;

View File

@ -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

View File

@ -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;
}

20
test_regress/t/t_tri_select.pl Executable file
View File

@ -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;

View File

@ -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

View File

@ -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;
}

20
test_regress/t/t_tristate.pl Executable file
View File

@ -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;

View File

@ -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