“冯诺依曼结构”计算机的proteus仿真及编程的初体验

     原文发表于我的微信公众号"涛歌依旧",可以点击如下红色文字链接阅读:

   “冯诺依曼结构”计算机的proteus仿真及编程的初体验(链接)

      从微信公众号把原文直接复制过来,发现图片、表格经常丢失,只有纯文字,所以建议直接点击上述链接阅读。

      复制过来的纯文字内容如下(图片、表格丢失):
 

       

       沈从文笔下的边城,风景优美,故事凄婉。世间若有双全法, 不负亲人不负卿。

       沈从文很早就写了《边城》,所以我觉得他是中国第一个“程序员”

       编程的意思是:构思故事,然后用语言编写出故事的发展过程。

       

       本文大致分为三个部分:

“冯诺依曼结构”简介

自制简易计算机及机器语言编程

汇编语言和高级语言编程

        


一. “冯诺依曼结构”简介
       程序员每天做的事情,就是不停地给计算机喂食,喂的是0和1组成的串。如下是我们的第一个程序,体会下编程的感觉:

000000000000000100000010000000110000010000000101

       这是机器语言程序,具体功能是计算1+2+3+4+5,这个程序可以运行在我们本文自制的简单计算机上,其中0和1所在的位置,不能出错。既然程序需要运行在硬件电路上,所以我们得关注硬件电路,还是从冯诺依曼说起吧。

       冯诺依曼,被称为现代计算机之父,关于他的生平,一篇文章讲不完,故直接省略。只说一句:1903年,冯诺依曼出生在匈牙利的布达佩斯,就是在这里,刘诗雯今年首次获得了世乒赛单打冠军,十年磨一剑,真的不容易。

       冯诺依曼设计了一种计算机结构---“冯诺依曼结构”,它是现代计算机的基础, 其最基本的原理是:程序存储。

       简单看下现代计算机的组成,其中红色的五个部分就是“冯诺依曼结构”的五大部件:

       在很长一段时间内,总是搞不清楚一些基本的概念和原理, 比如什么是硬件? 硬件内部是怎样运行的?什么是软件?软件在哪里?软件究竟是怎样在硬件上跑起来的?硬件究竟是怎样执行软件的?本文会逐一给出答案。

       在本文中,我们将用proteus仿真一台简单计算机,从电路的角度理解计算机的工作原理,并给这台计算机编写程序,体会编程的感觉,体会硬件和软件之间的相互作用。

       先来看前面文章留下来的问题:自动连续加法器的实现。当时的尝试都失败了,原因是:缺少合适的器件,导致电路死循环。失败的电路图如下(gif帧率是1桢/秒,所以感觉变化不快,但实际很快):

       为了解决自动连续加法的问题,我们用proteus制作了一台最原始的计算机,其功能单一,先来一睹芳容:

       这就是一台自制的简单计算机,其组成部件和“冯诺依曼结构”基本对应,如下表所示:

冯诺依曼结构

自制计算机

CPU运算器

U3加法器

CPU控制器

U1计数器

U4触发器

U5非门

时钟信号

存储器

U2存储器

输入设备

不涉及

(因为输入内容已提前存放于U2中)

输出设备

数码管显示器

       我们还得亲自制作上述五大器件。其中,加法器、非门和数码管显示器,我们都已经介绍并使用过,其他器件暂时还没有接触过。

二. 自制简易计算机及机器语言编程

       首先看如下电路, 其特殊之处在于:U3的输出作为U2的输入,U4的输出作为U1的输入。真不知道是谁没事干,去尝试这样连接电路。但从结果来看,是挺有意思的:

       很容易分析出下表逻辑关系:

S

R

Q

Q’

1

0

1

0

0

1

0

1

0

0

保持

保持

1

1

0

0

       有三点值得注意:

       a. 当S=0,R=0时,对或门没有任何贡献,所以不会对电路产生影响。此时,Q和Q'是相反的关系,必有一个为0,另一个为1.

       b. 当S=1,R=1时(这种输入并没有错),Q和Q'都为0,我们对这种情况不感兴趣,所以可以忽略它,故约定S和R不能同时为1。有很多书总是说:S=1,R=1是非法输入,这真是搞笑啊,是不符合要求的输入还是违法的输入?都不是。

       c. 不考虑S=1,R=1这种输入组合时,Q和Q'总是相反的。

      上图这个电路,叫RS锁存器。注意,有的书上并不区分锁存器和触发器这两个概念,其实,准确地讲:电平触发的叫锁存器(latch),上升沿/下降沿触发的叫触发器(flipflop)。

       根据约定,RS锁存器的两个输入端不能都为1,我们完全可以多加几个器件来达到这个控制目的,下图中R和S输出端的值永远不可能同时为1:

        来分析上图中的电路: 

       a. 当E=0时,无论D怎么变, 经历R和S两个与门后,R和S的输出端总是0, 所以Q和Q'总是保持原状。

       b. 当E=1时,经历R和S两个与门时,R和S输出端的值完全取决于D, 显然有Q=D.

      可以看出:E是一个控制者的角色,只有当E=1时,D的值才会保存到Q上。而当E=0时,  无论D怎么折腾,Q总是无动于衷。E是老大,官大一级压死人。所以,把E叫使能端, 把D叫数据端。

       上图这个电路,叫D锁存器,仅在E端允许时,完成锁存。

       继续来看一个更有趣的电路,把两个D锁存器串在一起, 如下图:

        来分析一下这个电路: 

       a. 上下两个D锁存器的使能端是相反的,所以,无论控制开关E是0还是1, 上下两个D锁存器必然有一个无效,导致不能把D数据保存到Q端。

       b. 当E从1变为0时,下面的D锁存器无效,因此也不能把D数据锁存到Q端。

       c. 当E从0变为1时,下面的D锁存器生效,上面的D锁存器无效,但U4的输出端之前已经获取了D端的数据, 所以趁着下面的D锁存器的生效,把U4输出端的数据保存到Q端。因此,整体看来,当E从0变化到1时,能把D数据锁存到Q端。这种锁存,只在信号发生0到1的跳变瞬间,所以叫上升沿触发锁存。

       上图中的电路,叫D触发器,仅在上升沿触发时有效。

       可是,这个D触发器有点复杂,我们来抽象一下,直接使用封装好的D触发器,如下图,可以看到:当且仅当E从0变到1时,才把D保存到Q。

      用E开关提供上升沿,需要手动掰弄开关,非常麻烦。能不能搞一个自动上升沿呢?当然可以。我们引入时钟信号,它不停地在0和1之间做变换,从而产生上升沿。时钟信号很简单,直接看图:

     曾经在知乎上看到这样一个问题:为什么CPU需要时钟才能工作?

       这个问题很好回答, 从两个方面来看:

       a. 计算机的理论模型是图灵机,而图灵机是有限状态机,需要有时钟信号驱动状态变化。

       b. 计算机中的数据需要按先后步骤进行保存和更新,以上述触发器为例,需要有上升沿信号来触发,所以需要时钟信号。

      D触发器只能触发保存1位二进制,我们可以直接用封装好的74175芯片实现4位二进制的保存,如下图:

      

      利用D触发器,可以制作其他器件,比如下图中所示的计数器:

       这个电路图很好分析。时钟周期是1秒,也就是说,每秒会有一个上升沿信号输入到U1的CLK端,于是U1的Q端就会每秒在0和1之间跳动一次,显然U1的Q'也是每秒在0和1之间跳动一次。也就是说,对于U2的CLK而言,遇到一次上升沿需要2秒,因此U2的Q在0和1之间跳动1次需要2秒,看下表:

第n秒

U1

U2

U3

U4

U4U3U2U1

0

0

0

0

0

0

1

1

0

0

0

1

2

0

1

0

0

2

3

1

1

0

0

3

4

0

0

1

0

4

5

1

0

1

0

5

6

0

1

1

0

6

7

1

1

1

0

7

8

0

0

0

1

8

9

1

0

0

1

9

10

0

1

0

1

A

11

1

1

0

1

B

12

0

0

1

1

C

13

1

0

1

1

D

14

0

1

1

1

E

15

1

1

1

1

F

       一目了然,U1的Q变化最频繁,U2的Q变化不频繁,U3的Q变化更不频繁,U4的Q最懒惰。很显然,上述电路是一个计数器,从0到F,  一个一个地计数。

      现在对上述自制的计数器进行抽象,直接使用封装好的计数器,静图如下:

       计数器很重要,若要不断地从存储中取出程序或数据,就得靠它了。所以,很多时候,计数器被用作程序计数器,即Program Counter.

       好的,到此为止,我们制作并使用过计数器、加法器、触发器、时钟信号、非门以及数码管显示器,但还剩下存储器没有制作出来,不要着急,制作存储器是我们后续文章要专门讲述的内容,暂时先直接使用存储器。

       把上述器件组装在一起,形成一台完整的计算机,如下图所示:

       这台简单计算机的电路图硬件都搭建好了,  软件在哪里呢?软件就是程序,包括指令和数据。我们把软件程序存放在存储器U2中,其中存放的程序内容是:

000000000000000100000010000000110000010000000101

        其功能是:计算1+2+3+4+5

       这台计算机工作过程的动图如下(感觉动图中的器件很模糊,大家可以对照上面的静图一起看):

       可以看到:1+2+3+4+5=FH=15

       现在最大的疑问是:为什么上面那段程序表示1+2+3+4+5呢?计算机电路又是如何执行程序的呢?我们来仔细分析一下。

       U2存储器中的程序是:

000000000000000100000010000000110000010000000101

       这个程序不直观,为便于阅读,我们来分割一下:

00000000
00000001
00000010
00000011
00000100
00000101

       可以看到,第一行是0, 第二行是1, 第三行是2, 第四行是3, 第五行是4, 第六行是5. 

       存储器U2的输入端和输出端对应的关系如下(注意:由于我们计算的数值不大,所以不需要用到A7A6A5A4和D7D6D5D4):

U2输入端

A3A2A1A0

U2输出端

D3D2D1D0

其具体内容与我们写的程序有关


0000

0000

0001

0001

0010

0010

0011

0011

0100

0100

0101

0101

       U2存储器左边的U1刚好是计数器,从0一直数到F,  因此,当计数器数到3时,U2输入端刚好就是3,而U2此处存储的数据刚好就是3.

       我们对照上面的动图,整体理解一下计算机电路图的执行流程:

        第1步:

       开始,U1的输出值0,左边数码管显示0,U2输出值为0,U3加法器的输出值0,U4的输出值是0,右边数码管显示0.

       第2步:

      时钟信号经历上升沿,U1开始计数到1,U2输出值为1,左边数码管显示1,U3加法器的输出值还是1, 但U4没有经历上升沿,故U4的输出值是0,右边数码管显示0.

        第3步:

       时钟信号经历下降沿,U1保持在1,U2输出值为1,左边数码管显示1,U3加法器的输出值还是1, U4刚好经历上升沿,故U4的输出值是1,右边数码管显示1.     

       还有很多步骤没有列出,我们把所有步骤放到表格中:

时钟信号

U1输出

U2输出

U3输出

U4输出

0

0

0

0

0

上升沿

1

1

1

0

下降沿

1

1

1

1

上升沿

2

2

3

1

下降沿

2

2

3

3

上升沿

3

3

6

3

下降沿

3

3

6

6

上升沿

4

4

A

6

下降沿

4

4

A

A

上升沿

5

5

F

A

下降沿

5

5

F

F

       对照电路图看表格,一目了然。可以看到,由于引入了U4触发器来保存数据,电路再也不会出现之前文章中遇到的死循环了。到此为止,我们的连续加法器终于实现了。

      而且,这个连续加法器是自动的,这完全是因为有了时钟信号,它不断地制造上升沿,触发着计数器往前走,去存储器获取程序(指令和数据),也触发着把U3加法器输出端的值安全地保存到U4的输出端,让它再次输入到U3参与计算。如果没有时钟信号,我们还要去一个开关一个开关地掰弄,去构造上升沿来控制电路。烦就一个字,我只说一次。

       在进行proteus仿真时,时钟信号的频率是1Hz, 也就是周期是1秒,信号为0的时间占0.5秒, 信号为1的时间占0.5秒, 1秒之内有1个上升沿和一个下降沿。我把时钟的频率调快到10Hz,发现连续加法的计算时间也变快了10倍,这是因为:时钟信号的频率快了,上升沿的次数就更频繁了,自然能更快地迫使其他器件上的值进行变化。

       我们来看如下的Intel CPU芯片,其主频是3.6GHz,这就是它的时钟频率,它每秒产生几十亿次上升沿/下降沿信息,迫使着其他器件快速变化。一般来说,在其他指标一样时,主频越大,计算机CPU的运算速度就越快,应该很容易理解这一点。

       电路是硬件,存储在U2中的信息是软件。我们买到手机(手机也是计算机)后,基本不会对硬件进行变化,但经常安装或者卸载其中的软件,而正是不同的软件, 使得手机有不同的功能。

       在不改变上述电路硬件的情况下, 我们可在U2中装入不同的软件,从而实现不同的功能。比如,我们写一个新程序:

00000000000000100000010000001000

        把程序塞到U2存储器中,重新让电路通电,呈现的结果如下:

       从动图中显而易见,这是在计算2+4+8=EH=14, 由此可见,不同的软件程序,实现了不同的功能。

三. 汇编语言和高级语言编程

      用0和1写出来的程序,叫机器语言程序,直接在硬件上运行,比起掰弄那些电路开关,已经是很大的进步了。 但是,如果一直用0和1来写程序,还不能出现一点点差错,未免太难受太憋屈了,谁受得了?

       一大串的0和1,太难懂了,它是机器世界的语言, 而人又有人的语言, 这两种语言是不相通的, 因此,我们需要探索出更好的编写程序的方法, 让人更轻松点。怎么办?

       我们回头看1+2+3+4+5的机器语言:

000000000000000100000010000000110000010000000101

       怎么降低人编写程序的难度呢?我们可以考虑这么写:

ORG  0000H
DB  00H
DB  01H
DB  02H
DB  03H
DB  04H
DB  05H
END

       这种语言叫汇编语言,看起来舒服多了。现在的问题是,需要一个工具把汇编语言转化为机器语言,这个转换的工具就叫汇编器, 如下图:

       现在编程就简单多了:先用汇编语言写程序,然后用汇编器这个工具,把汇编语言程序转化为机器语言程序。

       汇编语言解放了人,解放了生产力。自从有了你(汇编语言),世界变得好美丽。

       如果要计算2+4+8,我们再也不用与0和1这种折磨人的机器语言打交道了,直接用汇编语言来写汇编程序,如下:

ORG  0000H
DB  00H
DB  02H
DB  04H
DB  08H
END

        经汇编器工具转换后,上述的汇编语言程序变为了机器语言程序,如下:

00000000000000100000010000001000

        然后,我们自制的计算机执行这段机器语言程序,得到的结果是14.

       可是,用汇编语言还是憋屈,不好理解,不近人情。于是乎,我们需要再次抽象,越抽象越接近事物的本质,而我们的本质诉求是计算加法。为了降低编程难度,我们再次优化编程方法,采用高级语言(如C/C++语言、Java语言等)来编写程序,如下:

int a=1+2+3+4+5;
printf("%d", a);

       这样就更加清晰了。然而,电路毕竟不认识这些高级语言,电路只认识高低电平,即1和0, 所以,也需要工具来对高级语言进行转换,如下图:

       

       我们从如下表格来看机器语言、汇编语言以及高级语言的对比。显然,用高级语言编程更容易更直观:

目的

机器语言

汇编语言

高级语言

计算

1+2+3+4+5

000000000000000100000010000000110000010000000101

ORG  0000H

       DB  00H

       DB  01H

       DB  02H

       DB  03H

       DB  04H

       DB  05H

END

int a=1+2+3+4+5;

printf("%d", a);

计算

2+4+8

00000000000000100000010000001000

ORG  0000H

       DB  00H

       DB  02H

       DB  04H

       DB  08H

END

int a=2+4+8;

printf("%d", a);

        这里有两点补充说明:

        a. 上述的汇编语言很简单,不涉及到指令,只涉及到数据存放的位置。有一些朋友可能觉得上述汇编语言的指令是伪指令,但没有关系,我们依然把它当汇编语言理解,并无副作用。而且经汇编器转换后,生成的机器代码,确实可以在我们制作的计算机上运行。

        b. 上述的高级语言,经通用的编译器和汇编器转换得到的机器语言程序,没法在我们自制的计算机上运行,必须经过特定的编译器才可以,我们无需对这种特殊的编译器做进一步了解,毕竟编译器只是个转换工具。

       我们已经从原始的掰弄开关来控制电路,上升到利用机器语言0和1来控制电路,然后上升到用汇编语言来控制电路,最后上升到利用高级语言来控制电路。一步一步地,我们从纷繁复杂的电路细节中解脱出来了,这是一个不断进行抽象、不断远离具体细节、不断接近事物本质的过程,从此,也站在更高的维度上看待和处理问题。就像公司的老板一样,不需要管太多底层的繁琐细节,只需要关注高层次的本质问题。

      本文快要接近尾声了, 我们来总结一下本文内容:

      首先,介绍了“冯诺依曼结构”计算机的大致组成,并总是想着体验一下编程的感觉。

      其次,为了构建一台计算机,我们一步一步地制作器件,包括RS锁存器、D锁存器、D触发器、4位D触发器、计数器和时钟信号, 并组装成了一台完整的计算机,然后编写了机器语言程序,从掰弄开关的物理底层操作中解脱出来,初步体会到了机器语言编程的感觉。

       最后,为了让人更轻易地编程,提升编程的效率,把人真正从底层的繁琐细节中解脱出来,我们介绍了汇编语言和高级语言,并终于体会到了各层语言编程的感觉。

       还剩一个问题,本文使用了存储器ROM, 而且没有做详细介绍,实际上,ROM是只读存储器(Read Only Memory), 其中的内容一旦固定后,就不太容易更改。存储器是非常重要的,尤其是在每天产生海量数据的现代社会。在后续文章中,我们会对存储器相关的内容做更多的介绍。

        不见不散

发布了2213 篇原创文章 · 获赞 4564 · 访问量 1977万+

猜你喜欢

转载自blog.csdn.net/stpeace/article/details/103429067
今日推荐