【文件属性】:
文件名称:基于c++数字逻辑电子仿真器
文件大小:295KB
文件格式:RAR
更新时间:2011-12-07 11:15:02
数字逻辑 电子仿真器
定义数据结构如下:
typedef struct tagMyNode
{
Mytype type; //元件类型
MySubtype Subtype; //元件子类型
tagMyNode* input1; //输入端1
tagMyNode* input2; //输入端1
tagMyNode* output1; //输出端1
UINT input1value; //输入端input1的值
UINT input2value; //输入端input2的值
UINT output1value; //输出端output1的值
int inputs; //当前已经有几个输入端有值
int number; //对于输入结点的序号
CPoint Orgpoint; //记录元件左上角位置
int width; //记录元件宽度
int height; //记录元件高度
}MyNode;
元件类型:元件类型Mytype type中Mytype是一个枚举类型定义如下:
enum Mytype
{
Node, //结点
Gate, //门
};
分为两种类型:Node结点和Gate门。
元件子类型:元件子类型MySubtype Subtype中MySubtype也是一个枚举类
型,定义如下:
enum MySubtype
{
Input, //输入端
Output, //输出端
ANDGate, //与门
ORGate, //或门
NOTGate, //非门
NORGate, //或非门
NANDGate, //与非门
XORGate, //异或门
};
指针连接: tagMyNode* input1;
tagMyNode* input2;
tagMyNode* output1
是指向此结点的指针。由于元件之间是要相互连接的,于是设置这几个指针用于元件之间的连接。其中特殊情况有:
非门:由于非门只有一个输入端,所以非门不用tagMyNode* input2;
输入结点:输入结点只有一个链接端(这里称之为触点),采用tagMyNode* output1
输出结点:同输入结点,只有一个触点,采用tagMyNode* input1;
保存触点值:由于要进行仿真计算,所以还需保存各个触点的值:
UINT input1value;
UINT input2value;
UINT output1value;
同指针连接,有3种特殊情况:
非门:不用UINT input2value;
输入结点:采用UINT output1value;
输出结点:采用UINT input1value;
进位标志:int inputs;
在进行仿真计算时,要用进位标志辅助计算。如与门只有在两个输入端都有值时,即inputs==2时,才能进位。
输入结点序号:int number;
每个输入结点都有不同的序号,从1开始递增。
元件位置和大小:
CPoint Orgpoint;
int width;
int height;
Orgpoint用于记录元件在视图中左上角的坐标
width用于记录元件宽度
height用于记录元件高度
电路图编辑模块
电路图编辑模块又分为两个子模块:鼠标放置元件模块,鼠标连接元件模块
首先在工具栏中可以选择这两种状态,如图4
图4
在按钮上单击可以切换状态。
定义一个枚举类型MyStatus来记录当前状态:
enum MyStatus
{
NONE, //鼠标连接元件状态
ANDGATE,
ORGATE,
NOTGATE,
NORGATE,
NANDGATE,
XORGATE,
NODEINPUT,
NODEOUTPUT
};
MyStatus Status;
其中:NONE为鼠标连接状态,其他为鼠标放置状态。
鼠标放置元件模块
其算法如图5:
图5
DrawObject函数:
首先根据Status的状态,即六个门,两个端结点。共8种来调用DrawObject函数
引入准备好的八张位图(六个门,两个端)
CBitmap MyBitMap;
MyBitMap.LoadBitmap (nID);
将引入的位图拷贝入窗体窗户区
BITMAP bmpInfo;
MyBitMap.GetBitmap (&bmpInfo);
pOldBitmap=dc.SelectObject (&MyBitMap);
ClientDC.BitBlt (point.x ,point.y,bmpInfo.bmWidth ,bmpInfo.bmHeight,&dc,0,0,SRCAND);
dc.SelectObject (pOldBitmap);
用全局变量bmWidth和bmHeight来保存元件的宽度和高度
bmWidth=bmpInfo.bmWidth ;
bmHeight=bmpInfo.bmHeight ;
CreateMyObject函数
函数声明为:CreateMyObject(Mytype type, MySubtype Subtype, CPoint point)
初始化元件
MyNode* pNode=new MyNode;
pNode->type =type;
pNode->Subtype =Subtype;
pNode->input1 =0;
pNode->input2 =0;
pNode->output1 =0;
pNode->output2 =0;
pNode->Orgpoint =point;
pNode->width =bmWidth;
pNode->height =bmHeight;
pNode->input1value =0;
pNode->input2value =0;
pNode->output1value =0;
pNode->inputs =0;
如果创建的元件为输入结点,则要创建并画输入结点前的序号,这里
采用一个全局数组CArray numpoint来记录结点前序号。
if(Subtype==Input)
{
//当创建Input时加入点到numpoint数组中
numpoint.Add (CPoint(point.x-15,point.y));
pNode->number =numpoint.GetSize ();
//创建时重绘序号
redrawnum();
}
而redrawnum()函数就是将所有输入结点前的序号重绘。
最后将元件加入到全局链表CList MyList中。
MyList.AddTail (pNode);
鼠标连接元件模块
鼠标连接元件模块分为三个过程模块:鼠标移动模块,鼠标按下模块,鼠标抬起模块。
鼠标移动模块
其算法如图6
图6
代码如下:
void CMyView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//此时必然是非画图状态,所以status==NONE;
if(Status==NONE)
{
//当前点在某个物件上吗? 并且 //当前点在该物件触点上吗?
if(IsPointInObject(point) && IsPointInPut(point))
{
//全局变量pNodeNow是在IsPointInObject()这个函数里面记录的
//circlepoint和put是在IsInInput1() IsInInput2() IsInOutput1()
//这三个函数中记录的
//判断此时触点时否己连接非常重要
if(IsPutLinked())
{
//如果此时触点己连接,则退出
return;
}
//此时鼠标移进触点
//当前是连接态吗?
if(IsLink)
{
//连接态画图
LinkStatusDraw(point);
}
//开启画圆圈态
IsDrawCircle=TRUE;
//画圆圈
DrawMyCircle();
}
else//此时鼠标移出触点
{
//如果此时已画圆圈,则要擦除圆圈
if(IsDrawCircle==TRUE)
{
EraserMyCircle();
//关闭画圆圈状态
IsDrawCircle=FALSE;
//重绘连接线
moveoutredrawline();
//重绘圆圈所在的那个物件,因为擦除圆圈的时候可能擦除了部分物件
//-------------------
redrawMyObject(pNodeNow);
//如果此时是连接状态,连接态画图
}
if(IsLink)
{
//连接态画图
LinkStatusDraw(point);
}
}
}
CView::OnMouseMove(nFlags, point);
}
两个关键状态:可连接态IsDrawCircle和正在连接态IsLink
可连接态IsDrawCircle 当且仅当鼠标移动到某个元件上的某个尚未连接的触点上,才开启可连接态IsDrawCircle。之所以取名IsDrawCircle是因为此时会在鼠标停留的尚未连接的触点上画一个黑色小圆圈。
当鼠标移动离开触点,可连接态IsDrawCircle关闭。
正在连接态IsLink 当鼠标按下(见图5)并且此时可连接态IsDrawCircle开启(为TRUE)时正在连接态IsLink开启。
判断当前点是否在某个元件函数:IsPointInObject()
其算法如图7
图7
判断当前点是否在该元件触点上函数:IsPointInPut()
其算法如图8
图8
与门与其它5个门有所不同,与门只有一个输入端,所以要分开来判断
对于输入结点,则判断当前点是否在第一个输出端触点。
对于输出结点,则判断当前点是否在第一个输入端触点。
输入结点和输出结点的这样判断,一眼看上去似乎反了,但实际上有利于整个程序的编写。可以简单地这样分类:总共只有两种端,一种输入,一种输出。
这样,我们就可以将判断触点分为三个函数:
IsInInput1()
IsInInput2()
IsInOutput1()
拿IsInInput1()来分析:
centerpoint=GetCirclePoint(Input_1);
if(IsInArea(point))
{
//说明此时就在触点Input_1,用全局变量put记录下来
put=Input_1;
//如果当前点在,则要保存触点中心点
circlepoint=centerpoint;
return TRUE;
}
else
{
//如果移出触点,肯定不要再保存中心点
return FALSE;
}
首先,调用函数GetCirclePoint()来取得当前触点的中心点。然后调用IsInArea(point)函数来判断当前点point是否在以当前触点中心点为中心的矩形区域内。如果是,则用一个全局枚举变量put来记录来前触点是两个输入端和一个输出端中哪一个。
我们看这个枚举类型:
enum Myput
{
Input_1,
Input_2,
Output_1
};
接下来用一个全局变量circlepoint来记录当前触点中心点。再返回真。
如果当前点不在以当前触点中心点为中心的矩形区域内,则返回假。这时千万不能记录当前触点中心点。这点不注意会出大错。
判断当前触点是否已连接函数:IsPutLinked()
BOOL CMyView::IsPutLinked()
{
switch(put)
{
case Input_1:
if(pNodeNow->input1 !=0)
return TRUE;
break;
case Input_2:
if(pNodeNow->input2 !=0)
return TRUE;
break;
case Output_1:
if(pNodeNow->output1 !=0)
return TRUE;
}
return FALSE;
}
这里根据全局变量put的类型和全局变量pNodeNow所指向的元件,
就可以判断当前元件的当前触点是否已连接。如果连接相应指针不为0。返回真,否则返回假。
连接态画图函数:LinkStatusDraw()
void CMyView::LinkStatusDraw(CPoint point)
{
CClientDC clientDC(this);
CPen whitepen(PS_SOLID,1,RGB(255,255,255));
CPen* pOldPen;
pOldPen=clientDC.SelectObject (&whitepen);
clientDC.MoveTo (startpoint);
clientDC.LineTo (lastpoint);
clientDC.SelectObject (pOldPen);
CPen redpen(PS_DOT ,1,RGB(255,0,0));
pOldPen=clientDC.SelectObject (&redpen);
clientDC.MoveTo (startpoint);
clientDC.LineTo (point);
clientDC.SelectObject (pOldPen);
lastpoint=point;
//重绘所有输入结点前的序号
redrawnum();
//重绘连接线
LinkLineRedraw(startpoint,point);
//重绘物件
lineRedraw(startpoint,point);
}
这里,startpoint是鼠标按下开始连接时起始元件触点中心点坐
标,lastpoint是上一次鼠标移动所停留的点。为了实在连接时鼠标移动
的动画效果,我们要先擦除上一次移动画的线(用白笔),然后再从startpoint到当前点point画线。移动时由于不信的擦除重画,可能将先前已画的元件,输入结点前的序号,和已经连接好的线擦除。于是我们需要重绘。
重绘所有输入结点前的序号
redrawnum();
void CMyView::redrawnum()
{
CClientDC dc(this);
char buffer[20];
CPoint point;
//重绘所有Input前的序号
for(int i=0;i numpoint;
而数组的下标加1就为序号。
所以每次重绘为了方便,将所有序号都重绘。
重绘连接线
void CMyView::LinkLineRedraw(CPoint startpoint, CPoint point)
{
//将起点startpoint到终点point扩充成一个矩形drawrect
CRect drawrect(startpoint,point);
//rect用于产生连接线最大矩形
CRect rect;
//rectInter用于计算两个矩形的相交区域
CRect rectInter;
//point1和point2用于产生连接线最大矩形
CPoint point1;
CPoint point2;
drawrect.NormalizeRect ();
drawrect.InflateRect (1,1);
//遍历MyPointList链表
POSITION pos=MyPointList.GetHeadPosition ();
while(pos!=0)
{
//pPointArray用于指向点数组对象首址
CArray* pPointArray=MyPointList.GetNext (pos);
point1=pPointArray->GetAt (0);
switch(pPointArray->GetSize ())
{
//分两种情况 :2个点和4,5个点的情况
case 2:
//2个点时
point2=pPointArray->GetAt (1);
break;
default:
//4,5个点时
point2=pPointArray->GetAt (3);
}
//用point1和point2设置矩形rect
rect.left =point1.x ;
rect.top =point1.y;
rect.right =point2.x;
rect.bottom =point2.y;
rect.NormalizeRect ();
rect.InflateRect (1,1);
//如果两个矩形相交,则要重绘
if(rectInter.IntersectRect (&drawrect,&rect))
{
DrawLinkLine(pPointArray);
}
}
}
主要的算法思想是:将起点startpoint到当前点point扩充成一个矩形drawrect,然后遍历连接线链表,将每根连接线扩充成一个矩形rect,再判断这两个矩形是否相交,若相交,则需要重绘这根连接线。
连接线链表声明如下:
CList*,CArray*>
MyPointList;
链表中每个结点是一个数组对象的地址,而这个数组中每个元素是一个点。这样一个数组就表示了一根连接线,而一个链表可以遍历所以连接线。
画提示连接的小圆圈函数:DrawMyCircle()
void CMyView::DrawMyCircle()
{
//此时全局变量circlepoint记录了要画圆圈的
//而pNodeNow指向了当前的物件
//将物件坐标中的circlepoint转换成VIEW中的坐标
int x,y;
x=pNodeNow->Orgpoint .x +circlepoint.x;
y=pNodeNow->Orgpoint .y +circlepoint.y;
CClientDC dc(this);
//创建一个黑色的画刷
CBrush brush(RGB(0,0,0));
//创建指针pOldBrush用于保存原来的画刷
CBrush* pOldBrush;
//将黑色的画刷选进设备装置DC,并用pOldBrush保存原来的画刷
pOldBrush=dc.SelectObject (&brush);
//画一个圆圈,圆心是(x,y)
//半径是4
dc.Ellipse (x-4,y-4,x+4,y+4);
//将原来的画刷选回
dc.SelectObject (pOldBrush);
}
由于全局变量circlepoint保存的是元件内部的相对坐标,需要将它
转换成视图中的坐标
x=pNodeNow->Orgpoint .x +circlepoint.x;
y=pNodeNow->Orgpoint .y +circlepoint.y;
以上两句完成坐标的转换。
然后以(x,y)为圆心,4为半径,画一个黑色小圆圈
dc.Ellipse (x-4,y-4,x+4,y+4)
擦除小圆圈函数:EraserMyCircle()
void CMyView::EraserMyCircle()
{
int x,y;
x=pNodeNow->Orgpoint .x +circlepoint.x;
y=pNodeNow->Orgpoint .y +circlepoint.y;
CClientDC dc(this);
CPen whitepen(PS_SOLID,1,RGB(255,255,255));
CPen* pOldPen;
pOldPen=dc.SelectObject (&whitepen);
dc.Ellipse (x-4,y-4,x+4,y+4);
dc.SelectObject (pOldPen);
}
与画小圆圈不同的是,擦除时要选择白色的笔和白色的画刷(默认)
CPen whitepen(PS_SOLID,1,RGB(255,255,255));
CPen* pOldPen;
pOldPen=dc.SelectObject (&whitepen);
以上3句选择白色的笔。
鼠标移开触点重绘连接线函数:moveoutredrawline()
为什么需要这个函数,原因是在鼠标称出触点后,此时要擦除刚才画
的小圆圈,而如果此时已经生成了连接线,则会擦除掉连接线的一小部分。于是需要这个函数。
void CMyView::moveoutredrawline()
{
int x,y;
x=pNodeNow->Orgpoint .x +circlepoint.x;
y=pNodeNow->Orgpoint .y +circlepoint.y;
CPoint point1;
CPoint point2;
point1.x=x-4;
point1.y=y-4;
point2.x=x+4;
point2.y=y+4;
LinkLineRedraw(point1,point2);
}
此时pNodeNow指向刚擦除小圆圈的元件,而circlepoint则记录着
触点中心。于是只要将以ciclepoint为中心的半径为4的矩形的左上角点和右下角点为参数调用LinkLineRedraw即可。
重绘元件函数redrawMyObject()
void CMyView::redrawMyObject(MyNode* pNode)
{
switch(pNode->Subtype )
{
case ANDGate:
DrawObject(pNode->Orgpoint ,IDB_ANDGATE);
break;
case ORGate:
DrawObject(pNode->Orgpoint,IDB_ORGATE);
break;
case NOTGate:
DrawObject(pNode->Orgpoint,IDB_NOTGATE);
break;
case NORGate:
DrawObject(pNode->Orgpoint,IDB_NORGATE);
break;
case NANDGate:
DrawObject(pNode->Orgpoint,IDB_NANDGATE);
break;
case XORGate:
DrawObject(pNode->Orgpoint,IDB_XORGATE);
break;
case Input:
DrawObject(pNode->Orgpoint,IDB_NODEINPUT);
break;
case Output:
DrawObject(pNode->Orgpoint,IDB_NODEOUTPUT);
break;
}
}
该函数参数为指向元件的指针,用于重绘所指向的元件。
鼠标按下模块
如图5
图5
前面已经分析了放置元件状态,现在看连接元件状态中的判断:
“当前点是否在某个元件未连接的触点上”其实就是判断“可连接态”IsDrawCircle是否为真。代码如下:
if(IsDrawCircle)//当前点在某个元件未连接的触点上
{
//全局变量IsLink表示开始连接状态
IsLink=TRUE;
//全局变量pNodeStart记录当前物件
pNodeStart=pNodeNow;
//全局变量startpoint记录当前触点中心坐标(注,此时要进行坐标转换
startpoint.x=pNodeNow->Orgpoint .x +circlepoint.x;
startpoint.y=pNodeNow->Orgpoint .y +circlepoint.y;
//全局变量startput记录当前触点类别:Input_1,Input_2,Output_1;
startput=put;
//lastpoint用于鼠标移动时擦除线效果
lastpoint=startpoint;
}
进行连接初始化:首先开启开始连接状态
IsLink=TRUE;
然后用全局变量pNodeStart指向当前元件
pNodeStart=pNodeNow
全局变量startpoint记录当前触点中心坐标(这时要进行坐标的转换)
startpoint.x=pNodeNow->Orgpoint .x +circlepoint.x;
startpoint.y=pNodeNow->Orgpoint .y +circlepoint.y;
全局变量startput记录当前触点类别
startput=put;
最后lastpoint用于鼠标移动时擦除线效果
lastpoint=startpoint;
鼠标抬起模块
其算法如图9
图9
代码如下:
void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if(IsLink)
{
//首先擦除从startpoint到point
CClientDC clientDC(this);
CClientDC* pDC=&clientDC;
CPen whitepen(PS_SOLID,1,RGB(255,255,255));
CPen* pOldPen;
pOldPen=clientDC.SelectObject (&whitepen);
clientDC.MoveTo (startpoint);
clientDC.LineTo (point);
clientDC.SelectObject (pOldPen);
//重绘所有输入结点前的序号
redrawnum();
//重绘连接线
LinkLineRedraw(startpoint,point);
//重绘物件
lineRedraw(startpoint,point);
if(IsDrawCircle)
{
//用全局变量pNodeCurrent记录终点连接的物体
pNodeCurrent=pNodeNow;
//用全局变量currentput记录终点连接的触点
currentput=put;
//用全局变量currentpoint记录终点触点的中心坐标
currentpoint.x=pNodeNow->Orgpoint .x +circlepoint.x;
currentpoint.y=pNodeNow->Orgpoint .y +circlepoint.y;
//IsTwoObjectsCanLink()函数判断两个物件是否能连接
if(IsTwoObjectsCanLink())
{
//先擦除圆圈
//EraserMyCircle();没有必要,只要鼠标移开时重绘连接线就可
//开始两个物件的画图连接
LineLink();
//开始真正连接:指针连接
RealLink();
}
}
//关闭连接状态:
IsLink=FALSE;
}
CView::OnLButtonUp(nFlags, point);
}
判断两个元件是否可以连接
BOOL CMyView::IsTwoObjectsCanLink()
{
//判断两个物件是否能连接
//这两个物件分别由pNodeStart和pNodeCurrent指向
//两个触点分别由startput和currentput标识
//若所指同一物件
if(pNodeStart==pNodeCurrent)
{
MessageBox("连接错误!自身物件不能相互连接");
return FALSE;
}
//输出直接结输出
if(startput==Output_1 && currentput==Output_1)
{
MessageBox("连接错误!输出端不能相互连接");
return FALSE;
}
//输入直接连接输入
if( (startput==Input_1 || startput==Input_2)
&& (currentput==Input_1||currentput==Input_2) )
{
MessageBox("连接错误!输入端不能相互连接");
return FALSE;
}
//循环连接
if( (startput==Output_1)
&&(currentput==Input_1||currentput==Input_2) )
{
if(pNodeCurrent->output1 ==pNodeStart)
{
MessageBox("连接错误!不能循环连接");
return FALSE;
}
}
if( (startput==Input_1||startput==Input_2)
&&(currentput==Output_1) )
{
if(pNodeStart->output1 ==pNodeCurrent)
{
MessageBox("连接错误!不能循环连接");
return FALSE;
}
}
//如果以上情况都不发生,表示可以连接
return TRUE;
}
用图来表示上述几种错误:
同一元件不能连接
图10
输出端不能连接输出端
图11
输入端不能连接输入端
图12
两个元件不能循环连接
图13
两个元件的画图连接:LineLink()
该函数调用了recordLine()
代码如下:
void CMyView::recordLine ()
{
//记录两个物件之间的连接线经过的关键点
//先动态生成一个数组CArray之对象
//记录下连接线的关键点,然后将这个数组对象之地址加入到
//CList*,CArray*> MyPointList中
int x0,y0,x1,y1,delta_x,delta_y;
//(x0,y0)用于记录输出端起始点坐标
//(x1,y1)用于记录输入端终点坐标
//delta_x,delta_y用于记录x和y的偏移量
//一定是从输出端向输入端画线
if(startput==Output_1)
{
x0=startpoint.x;
y0=startpoint.y;
x1=currentpoint.x;
y1=currentpoint.y;
}
else
{
x1=startpoint.x;
y1=startpoint.y;
x0=currentpoint.x;
y0=currentpoint.y;
}
delta_x=5;
//动态生成数组对象
CArray* pPointArray=new CArray;
//根据点的位置分为三种情况:2个点,4个点,5个点
if(x0Add (CPoint(x0,y0));
pPointArray->Add (CPoint(x1,y1));
}
else
{
//4个点情况
pPointArray->Add (CPoint(x0,y0));
pPointArray->Add (CPoint(x0+delta_x,y0));
pPointArray->Add (CPoint(x0+delta_x,y1));
pPointArray->Add (CPoint(x1,y1));
}
}
else if(x0==x1)
{
//两个点情况
pPointArray->Add (CPoint(x0,y0));
pPointArray->Add (CPoint(x1,y1));
}
else //x0>x1
{
//5个点情况
if(y0Add (CPoint(x0,y0));
pPointArray->Add (CPoint(x0,y0+delta_y));
pPointArray->Add (CPoint(x1-delta_x,y0+delta_y));
pPointArray->Add (CPoint(x1-delta_x,y1));
pPointArray->Add (CPoint(x1,y1));
}
//加入当前数组对象地址到MyPointList
MyPointList.AddTail (pPointArray);
//用数组中的点画线
DrawLinkLine(pPointArray);
}
首先保证从输出端向输入端画线,这样可以统一画线操作。
然后动态生成数组:
CArray* pPointArray=new CArray;
用指针pPointArray指向该数组,用于存储连接线的关键点。
连接线根据位置总共有三种线型,如下图所示:
(1)两个关键点的连接线:
图14
(2)4个关键点的连接线
图15
(3)5个关键点的连接线
图16
两个元件的指针连接:RealLink();
其代码如下:
void CMyView::RealLink()
{
//一定是输入连接输出 或 输出连接输入
if(startput==Input_1||startput==Input_2)
{
//输入连接输出
if(startput==Input_1)
{
pNodeStart->input1 =pNodeCurrent;
}
else
{
pNodeStart->input2 =pNodeCurrent;
}
pNodeCurrent->output1 =pNodeStart;
}
else//startput==Output_1
{
//输出连接输入
pNodeStart->output1 =pNodeCurrent;
if(currentput==Input_1)
{
pNodeCurrent->input1 =pNodeStart;
}
else
{
pNodeCurrent->input2 =pNodeStart;
}
}
}
指针连接只有两种情况:输入连接输出和输出连接输入。可以用下图来表示
输入端连接输出端
图17
输出端连接输入端
图18
元件库模块
代码如下:
UINT CMyView::gatefunction(MyNode *pNode)
{
UINT result;
switch(pNode->Subtype )
{
case ANDGate:
result=pNode->input1value & pNode->input2value ;
break;
case ORGate:
result=pNode->input1value | pNode->input2value ;
break;
case NOTGate:
result=pNode->input1value ;
result=1-result;
break;
case NORGate:
result=pNode->input1value | pNode->input2value ;
result=1-result;
break;
case NANDGate:
result=pNode->input1value & pNode->input2value ;
result=1-result;
break;
case XORGate:
result=pNode->input1value ^ pNode->input2value ;
}
return result;
}
这里pNode是指向当前元件的指针,根据当前元件的类型,及当前元件的输入端的值input1value和input2value(注:非门只有一个输入端)来返回元件的输出端的值。
各个门真值表如下表所示:
(1)与门
输入端1 输入端2 输出端
0 0 0
0 1 0
1 0 0
1 1 0
表1
(2)或门
输入端1 输入端2 输出端
0 0 0
0 1 1
1 0 1
1 1 1
表2
(3)非门
输入端 输出端
0 1
1 0
表3
(4)与非门
输入端1 输入端2 输出端
0 0 1
0 1 1
1 0 1
1 1 0
表4
(5)或非门
输入端1 输入端2 输出端
0 0 0
0 1 1
1 0 1
1 1 1
表5
(6)异或门
输入端1 输入端2 输出端
0 0 0
0 1 1
1 0 1
1 1 0
表6
计算结果(仿真)模块
仿真模块在整个仿真器中占有最重要的作用。
当在视图窗体上放置元件,连接元件后。接下工具栏开始按钮 开始计算结果,进行仿真。
其主要算法如图19
图19
代码如下:
void CMyView::OnBegin()
{
//开始计算,输出真值表
// TODO: Add your command handler code here
//判断是否能够连接
if(CalculateResult()==-1)
{
MessageBox("连接线路失败!请检查线路");
}
else
{
//可以连接
//调用函数计算
beginCalculate();
//生成对话框对象
CMyDialog MyDialog;
MyDialog.DoModal ();
}
}
其中判断线路是否正确调用了CalculateResult(),这是仿真中最重要的函数。它的返回值是最终输出结点的值。如果返回-1,说明线路有误。其具体的算法如图20:
CalculateResult()
图20
代码如下:
int CMyView::CalculateResult()
{
//用于从输入端开始计算输出端结果
//遍历所有输入结点
MyNode* pNode;
MyNode* pNodeNext;
POSITION pos=MyList.GetHeadPosition ();
while(pos!=0)
{
pNode=MyList.GetNext (pos);
//判断当前结点是否是输入结点
if(pNode->Subtype ==Input)
{
for(;;)
{
//判断当前的输入结点的输出端指向的结点是否为空
//如果为空,表示连接失败
if(pNode->output1 ==0)
{
//连接失败,返回-1
return -1;
}
//否则不为空
//输出到它指向结点的输入端
//此时要判断输入到哪个输入端:input1 OR input2;
pNodeNext=pNode->output1 ;
if(pNodeNext->input1 ==pNode)
{
//如果是输入到input1
pNodeNext->input1value =pNode->output1value ;
//输入的值++,如果到了2,就可以计算进位了
pNodeNext->inputs ++;
}
else
{
//如果是输入到input2
pNodeNext->input2value =pNode->output1value ;
pNodeNext->inputs ++;
}
//指针跳向下一个结点
pNode=pNodeNext;
//判断此时是否是输出结点,如果是返回输出结点的值input1value;
if(pNode->Subtype ==Output)
{
return pNode->input1value ;
}
//判断是否可以进位,对于非门,只要有一个输入值即可inputs==1
//对于其他门,要两个输入值inputs==2
if(pNode->Subtype==NOTGate)
{
//非门
if(pNode->inputs==1)
{
//可以进位
pNode->output1value =gatefunction(pNode);
}
else
{
//不能进位
break;//跳出for(;;)
}
}
else
{
//其他门
if(pNode->inputs ==2)
{
pNode->output1value =gatefunction(pNode);
}
else
{
//不能进位
break;//跳出for(;;)
}
}
//请空输入值个数inputs,以便下次计算
pNode->inputs =0;
}//for(;;)
}//判断当前结点是否是输入结点
}//while(pos!=0)
//遍历完后若没有返回,说明连接失败
return -1;
}
其算法主要思想是:遍历每一个输入结点,将输入结点的值送入到它所连接的元件的输入端,若此时该元件可以进位,则调用该元件进位函数gatefunction()计算该元件的输出端的值,再将该输出端的值送入它的下一个连接元件,再判断下一个元件能否进位,如此循环,直到输出结点。若此时不可以进位,则遍历下一个输入结点。
可以用下图来说明:
图21
假设此时输入结点遍历的顺序是1->2->3 (顺序不唯一)。假设此时1,2,3号输入结点取值0,1,0。首先将1号输入结点的值送入它所连接的或门的第一个输入端,即input1value=0。此时或门进位标志inputs= =1。于是不能进位。遍历下一个输入结点2,将2号1号输入结点的值送入它所连接的与门的第一个输入端,同样此时与门进位标志也为inputs= =1,不能进位。最后遍历到输入结点3,将值送入到与门的输入端2。由于此时有两个输入了,即与门进位标志inputs= =2,调用与门函数计算与门输出端output1value.然后将此值送入或门,同样或门进位标志inputs= =2,调用或门函数计算或门输出端值,最后送入输出结点,结束。
计算真值表:beginCalculate()
代码如下:
void CMyView::beginCalculate()
{
//计算输入结点的个数,输出真值表
n=numpoint.GetSize ();
//计算要进行循环的次数
int i;
x=1;
for(i=1;i<=n;i++)
{
x=x*2;
}
//动态生成x个字符串保存真值表
//用一个字符串格式化数据
CString str;
//用一个数组I[1]~I[n]记录每个输入结点值
UINT* I=new UINT[n+1];
//用数组J[1]~J[n]辅助计算
UINT* J=new UINT[n+1];
//初始化J[1]~J[n]
J[1]=1;
for(i=2;i<=n;i++)
{
J[i]=J[i-1]*2;
}
//进行x次循环,计算真值表
for(i=0;i>(k-1);
//将输入端1~n的值加入字符串
str.Format ("%d ",I[k]);
//
//连接起来
strs[i]=strs[i]+str;
}
//给输入结点1~n初始化值
POSITION pos=MyList.GetHeadPosition ();
MyNode* pNode;
while(pos!=0)
{
pNode=MyList.GetNext (pos);
//如果结点是输入结点
if(pNode->Subtype ==Input)
{
//结点中的pNode->number 记录了结点序号
//给结点初始化值
pNode->output1value =I[pNode->number ];
}
}
//调用函数计算,将计算结果保存
int result=CalculateResult();
//生成字符串以便输出
str.Format ("%10d",result);
strs[i]+=str;
}
}
代码分析:
(1) 首先得到输入结点的个数:n=numpoint.GetSize ();
这里numpoint是记录输入结点前序号位置点的数组,而有多少个这样的点,就有多少个输入结点。
(2)然后计算真值表的行数,因为有n个输入结点,真值表就有2^n行。
x=1;
for(i=1;i<=n;i++)
{
x=x*2;
}
这里我们用x来保存真值表的行数。
(3)产生所有的输入结点值的组合。
如果有3个输入结点,它的所有组合如下表
输入3 输入2 输入1 i
0 0 0 0
0 0 1 1
0 1 0 2
0 1 1 3
1 0 0 4
1 0 1 5
1 1 0 6
1 1 1 7
我们可以从表的第四列看出可以用数字i从0到x-1来分解出这些组合。
例如:当i为2时,它在内存中最后3位为:010。
此时 输入3=010 & 100=000,再右移2位即可得到0。
于是我们可以得到:输入1 & 001 再右移0位
输入2 & 010 再右移1位
输入1 & 100 再右移2位
我们用一个数组J[1]~J[n]来记录相与的数字。为1,2,4,......,2^(n-1)
初始化J[1]~J[n]
J[1]=1;
for(i=2;i<=n;i++)
{
J[i]=J[i-1]*2;
}
用I[1]~I[n]记录输入1~输入n
产生一行输入值:
strs.Add (CString());
for(int k=1;k<=n;k++)
{
I[k]=i & J[k];
//向右移位
I[k]=I[k]>>(k-1);
//将输入端1~n的值加入字符串
str.Format ("%d ",I[k]);
//
//连接起来
strs[i]=strs[i]+str;
}
给输入结点1~n初始化值
POSITION pos=MyList.GetHeadPosition ();
MyNode* pNode;
while(pos!=0)
{
pNode=MyList.GetNext (pos);
//如果结点是输入结点
if(pNode->Subtype ==Input)
{
//结点中的pNode->number 记录了结点序号
//给结点初始化值
pNode->output1value =I[pNode->number ];
}
}
调用函数计算,将计算结果保存
int result=CalculateResult();
最后生成字符串以便输出
str.Format ("%10d",result);
strs[i]+=str;
完成以上操作后,生成一个对话框,然后将字符串数组strs[]加入到列表框内。最终输出整个真值表。
参考文献:
1 Electronics Workbench 5.0 1992-1996 Interactive Image Technologies Ltd
2 MSDN Libary July 2000 Microsoft
【文件预览】:
数字逻辑电子仿真器
----ReadMe.txt(5KB)
----StdAfx.h(1KB)
----毕业设计数字逻辑电子仿真器.opt(64KB)
----res()
--------andgate.bmp(986B)
--------Toolbar.bmp(1KB)
--------bitmap3.bmp(2KB)
--------bmp00004.bmp(958B)
--------bitmap2.bmp(1KB)
--------毕业设计数字逻辑电子仿真器.ico(1KB)
--------bmp00003.bmp(1014B)
--------bmp00006.bmp(334B)
--------bmp00002.bmp(1006B)
--------bmp00005.bmp(1KB)
--------bmp00001.bmp(246B)
--------toolbar1.bmp(4KB)
--------bitmap1.bmp(1KB)
--------毕业设计数字逻辑电子仿真器Doc.ico(1KB)
--------毕业设计数字逻辑电子仿真器.rc2(418B)
----毕业设计数字逻辑电子仿真器.cpp(4KB)
----毕业设计数字逻辑电子仿真器.plg(1KB)
----毕业设计数字逻辑电子仿真器.rc(14KB)
----毕业设计数字逻辑电子仿真器.dsp(6KB)
----MainFrm.cpp(4KB)
----MyDialog.h(1KB)
----毕业设计数字逻辑电子仿真器View.h(4KB)
----仿真器文档.doc(383KB)
----MainFrm.h(2KB)
----MyDialog.cpp(2KB)
----毕业设计数字逻辑电子仿真器View.cpp(30KB)
----resource.h(2KB)
----毕业设计数字逻辑电子仿真器Doc.h(1KB)
----毕业设计数字逻辑电子仿真器Doc.cpp(2KB)
----毕业设计数字逻辑电子仿真器.clw(3KB)
----StdAfx.cpp(228B)
----图标()
--------与门.bmp(4KB)
--------或非门.bmp(2KB)
--------与非门.bmp(2KB)
--------非门.bmp(2KB)
--------异或门.bmp(2KB)
--------或门.bmp(2KB)
----毕业设计数字逻辑电子仿真器.dsw(577B)
----毕业设计数字逻辑电子仿真器.h(1KB)