数电课程模型机设计:模型机整合

内容还未完成,等待更新,其中包含高度不稳定的内容,仅供参考。
文章更新较慢,可以先在GitHub浏览工程文件。

所有代码现已开放至GitHub:https://github.com/cyp0633/ModelComputer。建议在Releases代码里下载ZIP格式的源代码,更稳定。

同时提供一些资源文件,可以从https://drive.cyp0633.icu/s/eVc6获取,包含验证用的内存初始化MIF文件、实验报告和成品模型机图(PDF格式)。

本文大概遵循老师教学视频中使用的方法,即每次搭建几个元器件,并对其做验证。

关于之前部件的内容,请翻到本文底部,浏览“数电模型机”Tag下的其他文章。

模型机整体图如下图所示,后面的过程可能会用到控制信号和数据通路间的关系。

模型机整体结构

指令计数器、多路复用器、RAM、指令寄存器

这几部分较简单,建议跟随教学视频完成。

在调试RAM的过程中,如果遇到不能在LPM_FILE中输入"./xxx.mif"从而无法读取初始化内容的情况,可以用文本编辑器(VS Code等,实在没有的话记事本也行)打开项目BDF文件,查找定位现在LPM_FILE对应值的位置,将文件路径部分替换成下面这样:

verilog
1
2
3
4
5
  (parameter
    "LPM_FILE"
    "\"./example.mif\""
    "File containing initial contents of memory array"
    (type "PARAMETER_STRING"))

如果编辑mif时提示数字过大,那多半是默认十进制,要修改为二进制,可以用文本编辑器打开mif文件,将DATA_RADIX改成BIN。

如果在Quartus 9编译后的Timing Summary中出现"Slack: Not Operational",则代表有锁存器,回去改模块的代码吧。我的代码中也有此提示,但这是故意而为之,是启停控制模块的必然结果,绕过后则不会有此提示。

通用寄存器组、ALU

  • 通用寄存器组,下降沿响应,用来保存操作数和运算结果等信息。
  • 算术逻辑单元 ALU,组合部件,用于执行加、减、与、非运算,或提供数据的通路。

搭建电路的方法,视频里已给出,所以这里主要介绍验证的方法。

这里我们只需要仿真验证通用寄存器组和ALU两个模块,所以添加引脚的时候只需要添加这两个模块有关的引脚,即CLK、M、SEL、RA、RWBA、WE、C、DREG、SREG、Z。

DREG和SREG输出主要用于截取寄存器输出到ALU输入间传送的数据,方便debug。

此外,寄存器内的数值默认一开始都是0(Undefined),除了一点一点用ALU指令自加得到想要的数值之外,也可以在Verilog内直接给寄存器赋初值(默认为8’b0),如:

verilog
1
2
3
    reg [7:0] a=8'b00110110;
    reg [7:0] b=8'b10001011;
    reg [7:0] c=8'b11001110;

根据ALU引脚关系来确定输入的m和sel值。以下的顺序并没有严格指定,但请使你的测试覆盖各种情况,以免组装完成后出现意想不到的错误,下同。

第一个上升沿,我们先执行加法。clk设为初始值1、5ns增1的波形,m=1允许运算,sel=1001代表ADD,ra和rwba输入加的寄存器地址,we=0允许数据输入。

这样,你就可以在c、z和上升沿后的dreg中看到运算结果。

然后第二个上升沿,我们执行减法。和前面差不多,只是sel=0110。减法时ALU运算的是t=b-a,对应的是dreg-sreg,也就是rwba指向的值减raa指向的值,对应关系下同。

按位与和取反也差不太多,这么搞就行了。

仿真波形如下图所示(不要在意最下面那三个REG,有点问题)。

寄存器和ALU仿真波形

波形看起来很奇怪?请不要忘了,ALU是组合部件,运算很快完成;寄存器是时序部件,更新需要等下降沿。

移位逻辑

  • 移位逻辑,组合部件,用于将输入的数据循环向左向右移,将溢出的位另输出至Cf。

在上面的结构图中,移位逻辑是放在ALU后面的。当然首先要把移位逻辑模块放到图上了。

放置移位逻辑的电路图

因为左移右移进行的时候,肯定是t=b的,ALU不需要输出Cf,所以我们可以直接用一个或门将两路数据连接起来。

也可以在t和a之间接一个输出,方便观察。

反正针脚又没怎么变,所以可以沿用寄存器和ALU的波形文件,只需加入fbus/flbus/frbus输入就行了。

在波形文件中,sel表示ALU执行计算的时候(1001/0110/1011/0101),fbus=1,flbus和frbus=0,代表移位逻辑作为数据通路,直接将输入的数据输出。

sel=1010时,代表RSR或RSL,ALU作为数据通路,让移位逻辑负责移动数据,得留两个下降沿的时间,一个给左移一个给右移。

仿真波形如下图。

加入移位逻辑后的仿真

40-60ns处,就是左移一下右移一下,恢复原来的样子。进位完全正常。

控制信号产生逻辑、SM、指令译码器、状态寄存器

  • 控制信号产生逻辑,组合部件,接收指令译码器输出,产生每个模块所需要的控制信号。
  • SM,下降沿敏感,用于区别取指令周期和执行指令周期。
  • 指令译码器,组合部件,将8位指令码映射为对应指令。
  • 状态寄存器,下降沿敏感,存储cf和zf,并在使能为1的时候改变。

之前的RAM电路需要依靠三态门来隔离输入和输出,而现在我们直接将总线接到双向的dio口上,同时负责输入输出。所以总线上的数据要协调好。

然后就是把剩下的元件一次加进去,搭建的时候可以参考上面的模型机整体结构连接。只连上这几个元件而没有其他功能加入的电路没截图,但带有调试引脚的模型机的结构大概如下图。

整台模型机的图

记得将一些信号连接到output引脚,多留一些调试用的输出,这样便于查看各种控制信号的值和过程量,方便debug,比如控制信号、总线数据、通用寄存器输出、多路复用器输出、指令寄存器输出、指令计数器的输出。可以参考上图设置输出引脚。

整合与调试

MIF文件编写

当你认为上面的部件全都连接完成的时候,只需要时钟信号、输入和一个写得正确的MIF文件就可以让模型机跑起来了。这个MIF文件定义了RAM在开始运行时的内容,但仿真结束或FPGA板断电之后并不会被修改,因为RAM断电即丢数据的特性是by design的,真实的电脑也是这样。

随着时钟的flip-flop,指令计数器的值改变,它的一个一个值传入RAM的address端,dio端就读出了指令或是地址(JMP/JZ/JC指令)内容。如果是指令,就将其送入指令译码器,然后变成控制信号,进行下一步的操作。因为指令计数器的自增或是装载特性,模型机就可以自动寻找下一个指令执行,而指令需要在MIF文件中初始化。

一句话来概括,就是以前用控制信号或手动输入指令来控制,属于走一步教一步;现在用MIF文件让它自己读指令,相当于记住了下面的指令内容,给它一个信号它就能自己按路径跑。

MIF文件从0开始向下编写,除三种跳转指令占两位外,每个指令占一位地址。遇到跳转,则与if语句的逻辑相似。

输入与输出

正如实验四和上面我们提到的,三态门可以控制通断,从而控制输入输出。只要将输入总线和输出总线与模型机相连,然后分别用三态门(Quartus中搜索TRI)隔开,将输入输出使能控制信号作为条件,外部输入输出部分就完成了。下图中的inputdata另有输入引脚连接,图中未绘出。注意大于一位的都要用总线绘制。三态门的作用是在使能为1时原样通过数据,否则门后为高阻态。

输入输出

停机(HALT)和NOP指令

HALT指令通过将SM_EN置0,从而停止取指实现。如果要恢复运行,只需要加一个输入,将SM_EN拉到1即可。所谓的”真正的停机“,可能指的是门控时钟?没有太大必要实现。

NOP指令需要让模型机保持现在的状态两个时钟周期,然后执行下一条指令。这个时候你什么都不需要做,只需要让WE=1,禁止总线向寄存器输入数据。

调试

还记得我们创建的一大堆调试引脚吗?它们的作用就是输出过程量,在执行出错的时候找到是哪里的信号出错了,从而针对性改正。有点像打断点添加中间变量检测,不是吗?因为指令常常牵一发而动全身,我们可以找到发现异常的地方,然后逐条往回推,直到找到真正执行错的地方。至于怎么调试,就很依赖对模型机结构的熟悉程度了。

不过这里有一种情况很难查出来。如果你发现总线上输入/输出的数据并不是你想要的数据,特别是凭空不知道从哪冒出来的数据,多半是总线上输出打架了:同时只有一个部件能够在模型机总线上写入数据,很可能是移位逻辑。

标准测试指令文档

标准测试指令由老师给出,包含了一些分支,能够检验大部分指令的执行情况,所以如果你能够实现所有指令,建议使用标准指令文档。

对于电脑仿真的文档,需要预先将修改寄存器代码,将C寄存器的值初始化为1000 0000。除此之外,还需要在第3-4个时钟周期输入1000 0011,才能按照预期的情况运行。

对于下板的测试文档,需要将输入长期设为1000 0011,毕竟执行很快(3MHz),你无法在输入的一瞬间将按钮拨上去,然后瞬间拨下来。

下板测试

分配引脚

Assignments - Pin

时钟使用6MHz的CLK1,将clk分配至Pin 17。

输入使用开关,比如从高位到低位71、70、69、67、65、64、63、60,代表左边8个开关,正好对应从高位到低位8位输入。

输出使用LED灯,比如118、115、114、113、112、104、103、101,代表左边8个LED灯。如果你想用数码管,大概思路是将8位输出转BCD码,然后使用一个译码器轮流输出(因为是共阴极数码管)。

下载

连接板子,打开Programmer,在Hardware Setup中选择设备,然后点击Start即可。如下图。

下载

如果你做到现在,你就做出了一台足够优秀的模型机。我做出最终的成品图,可以下载开头链接中的文件来浏览。以下模块不会增加基本功能,也不会魔改出流水线四发射双ALU分支预测之类的骚操作,但(可能)可以增强使用体验。

附加模块设计

这部分建议参考我的设计报告和代码来研究,在文章顶部链接中。

启停控制

这是一个优先级非常高的启停控制,因为它控制着输入到模型机内的时钟。本是为按钮设计的,没写消抖,就放到开关上了。

将开始按钮作为输入,将暂停按钮作为重置。在INIT状态下,只要检测到开始按钮

**副作用:过不了Timing Requirements。**因为将时钟阻断会造成锁存,从而clock hold变为N/A。

状态显示

用于显示停机原因,HALT或是上个模块造成的Pause。

向凌老师致敬,没有她的帮助,我可能会晚很长时间才能做出像样的模型机。