湖南大学数字逻辑CPU大作业--CPUdebug日志

前言:

这篇日志是我记录自己做CPU时候的工作进度以及思考,灵感,问题,里面记录了很多的BUG,如果遇到了一些BUG可以来这篇日志里面查找
日志的后半部分我认为是比较有营养的
这篇日志非常的长,原版我是记录在一个WORD文档里面的,这个文档可以在我发布的CPU工程文件里面找到,文档里面文字有,加粗,字体颜色,更方便阅读

标题关于控制信号的整理可以查看我的另一篇博客

https://blog.****.net/qq_43536068/article/details/103590023?utm_source=app

12/11修复了PC寄存器的问题,就是输入一个地址后只是当前那个周期输出这个addr,后续又变回来原先的addr
Ram的性质是获取上升沿前一瞬间的控制信号
取指令联合调试完成
Ram正常在上升沿读出,IR正常在下降沿写入

12/12突然想到,我应该一边联合调试各个部件,一边用自己弄的那种输入来检查自己的控制信号产生逻辑是否是正确的
联合调试的时候,是从”事件”的视角去看待某个事件(比如取指令)的控制信号都是什么,再与单个控制信号进行对照就OK了

另外,我决定直接把SM加个反相器就连接到LD_IR上面,这样子简单不易错

发现一个错误,RAM的/CS口是专业的”低电平有效”,看来cs需要直接连接到时钟,否则会造成ram在不恰当的时间向BUS输出了数据,或者从BUS读入了数据
等等…好像ram这个元件的clk输入口我已经连接了…那就不会错了

对未来的提醒:目前,c,z尚未用到,但是这两个口决定能否输出jmp和jz这二个指令,或许…这两个口可以加在指令译码器?

修正了控制信号产生逻辑的一个重大bug之后,我成功让取指令周期自动跑起来了
湖南大学数字逻辑CPU大作业--CPUdebug日志
但是好像有点不对,因为我预期的ram是”上升沿读出”的,但是很明显,现在的ram是”下降沿就输出了数据,并且保持一个周期”这不是我想要的(虽然似乎没有什么问题)
经过反复验证,我认为是ram元件本身出了问题
但是我又去我原本的ram那里进行了ram单个元件的测试,发现是有上升沿写入的性质的,但是到了我的cpu里面就失去了这个性质…

2019-12-14开始制作mov指令
先做MOVA
MOVA R1,R2是三个寄存器之间的互相移动,把数据从R1移动到R2
执行周期与此指令相关的控制信号

移位逻辑
F-BUS 1
FL-BUG 0
FR-BUS 0
ALU
S[3…0] 1111
M 0
通用寄存器组
RAA 不固定
RWBA 不固定
/WE 0
**{BUG}**发现了控制信号产生逻辑里面的一个bug,
RAA <= IR(1 downto 0);
RWBA <= IR(3 downto 2);
之前我一直以为RAA是[3 downto 2]
啊啊啊啊,这个bug不仅在控制信号产生逻辑里面有,在指令译码器里面也有啊啊啊啊啊,我死了,稍后再做修改

我猛然发现,原来RAM的CS和CLK不是一个口!!!
湖南大学数字逻辑CPU大作业--CPUdebug日志
我在增加了两个骚气的与门之后RAM能在timing仿真下按照我的意愿工作了

为什么我一把指令译码器和控制信号产生逻辑连接在一起,就tm的连取指令的执行都错误了!!!我仔细排查了一下,问题出似乎在MADD那里,因为selector输出”ZZZZZZZZ",所以我又排查了MADD的控制信号产生逻辑,似乎没问题…那么…就一定是指令译码器有问题了
湖南大学数字逻辑CPU大作业--CPUdebug日志
我的猜测被证实了,果然’是指令译码器出现了问题
在我de掉指令译码器的错误之后,”取指令”的操作变得更加诡异了
但是我似乎发现了什么
**{BUG}**我的RAM似乎是把”SM”的”上升沿”当成了”时钟上升沿”
通过图会发现,每次sm产生上升跳变,RAM就会启动读写
湖南大学数字逻辑CPU大作业--CPUdebug日志
不对,这个问题好像又和sm无关,因为我把ram的clk输入口上的反相器去掉之后,波形就变得正确了…似乎是因为我自己加的反相器出了问题
湖南大学数字逻辑CPU大作业--CPUdebug日志
湖南大学数字逻辑CPU大作业--CPUdebug日志

对当前仿真波形的解读

湖南大学数字逻辑CPU大作业--CPUdebug日志
SM的一次0加SM的一次1就是一个指令周期,也是2个时钟周期
此时我的初始化文件前三条RAM命令都是11110011,也就是MOVC 0011
由于初始情况下,寄存器C(C在作为”取地址的时候”就是11)内部值为00000000,所以此时这条指令的意义就是”把ram中地址为00000000的格子内的值送给00号寄存器”而00号寄存器
此时RAA = 11,RWBA = 00(本质上RWBA的意思是”要写入的寄存器”)
在解读波形的过程中我又发现一个错误,那就是我的通用寄存器组的控制信号WE存在错误,当第二条指令(仍然为11110011)处于”执行周期的时候”由于此时RAA = 00,但是我的A口读出的数据却是”ZZZZZZZZ”,这时we应该为1!!!

WE这个错误如果不de掉的话,MOVC指令时,C寄存器的数据根本就无法通过RAA控制信号走A口读出来

湖南大学数字逻辑CPU大作业--CPUdebug日志
关于selector_check也就是选择器的输出口的输出问题
可以明显的看见,它在前三个”指令周期”是隔一个时钟周期一次”按顺序增长”,然后一次”11110011”
原因很简单,在前三个”指令周期”中,执行的指令都是MOVC 0011,此时在”取指令周期”中,selector会选取PC送过来的值,而此时PC是一直按顺序增长的
但是在执行周期,由于是MOVC, C寄存器的值的路径是: 寄存器A口->selector 01口->RAM addr指令信号口
所以,执行周期时selector_check在正确的情况下应该会看见寄存器C中的数据

**{BUG}**于是此时我就发现我自己的通用寄存器完全没考虑RAA或者RWBA为”11”的情况

不过这个仿真图也带来了好消息,那就是我发现,ram中读出的值”11110011”(先别管他怎么读出来的啦)可以通过总线送进寄存器组的正确的寄存器里面,因为理论上,在取地址周期,we的值保持恒定为0,此时寄存器允许写入,并且此时RWBA=00,因此在”取地址周期”结束的那个下降沿,总线bus上面的值会被送进寄存器00里面***,然后在之后的”执行周期”中,RWBA恰好又是00,而WE为1,所以碰巧就把这个错误写入的”11110011”给输出到了WE_B0_CHECK*

**{BUG}**通过这段分析,我又发现了bug,那就是
1.我的we不能处理RWBA和RAA为”11”的情况
2.WE在”取地址周期”应该保持为1,否则会因为取地址周期结束的那个下降沿导致读入了总线上的值(取地址周期RAA和RWBA的控制信号都是00)

{BUG}我发现我对WE的理解仍然存在误区…WTF
我重新看了一遍MOVC的ppt,发现即使WE为0的时候,寄存器的输出口依然有数据在输出…
也就是说,不管WE是不是0,寄存器A口,B口都会读出当前RAA和RWBA指定的数据
而WE仅仅决定”能不能写入数据”

我发现我的控制信号WE还是存在问题…
现在WE的功能很明确,就是在MOVA or MOVC or ADD or SUB or OR_S or NOT_S or RSR or RSL or IN_S这些指令的”执行周期”时候WE必须为0,而其他指令的执行周期WE为1,并且在SM=0的时候WE必须为1
依据这个要求写出一个布尔表达式
湖南大学数字逻辑CPU大作业--CPUdebug日志
我明白了,之后we依然是上下上下的跳动是因为我对we布尔表达式存在问题,由于我的指令译码器的连线还没连完,并且我的RAM中是错误的指令,所以说我的WE布尔表达式不满足WE <= MOVB or JMP or JZ or JC or NOP or HALT or (not SM);
(这些指令都没有输出,所以不为we没法变为1(因为我RAM里面很多是无效的指令比如0001012这种,是完全随机的序列))

现在WE的功能很明确,就是在MOVA or MOVC or ADD or SUB or OR_S or NOT_S or RSR or RSL or IN_S这些指令的”执行周期”时候WE必须为0,而其他指令的执行周期WE为1,并且在SM=0的时候WE必须为1
这个布尔函数书写十分麻烦,因为输入变量过多(16个),无法列出完整真值表,此时需要对布尔函数有更深的理解
在书写这个布尔函数的时候我对布尔函数表达式进行了更加深入的理解
首先,我们平时使用的布尔函数是”积之和”,这种表达式是”0本位”的,就是说”默认为0,有1的时候就给他添加上一个项(比如我想要ABC这一项为1,我就把这项添加到式子里面)”
但是如果我们想弄一个”1”本位的布尔表达式,那就得换成”和之积”的形式(默认为1,有0的项添加进去)
当然,本质上我现在书写的WE表达式仍然是一个”和之积”表达式,因为我是通过一个4变量卡诺图化简发现了一个小规律
现在我的WE即使是指令输入缺失也不会有错了,哈哈哈

指令中寄存器快查表
XXXX B输出口, A输出口

第一条MOVC指令终于调试完毕了,现在开始调试第二条指令,MOVA

MOVA R1,R2是三个寄存器之间的互相移动,把数据从R1移动到R2
执行周期与此指令相关的控制信号

移位逻辑
F-BUS 1
FL-BUG 0
FR-BUS 0
ALU
S[3…0] 1111
M 0
通用寄存器组
RAA 不固定
RWBA 不固定
写MOVA的时候我发现我的ALU不具备处理直接传输(s为1111)的效果,现在就加上
pf&a when “11111”,
pf&a when “01111”,
为了方便起见,我令M无论为0还是为1都可以直接传输A口读入的数据,后续可能要修改
湖南大学数字逻辑CPU大作业--CPUdebug日志
MOVA指令执行成功
话说我发现ADD,SUB,OR,NOT的执行过程基本就是和MOVA完全一样,就好像课本所说,数据都是在寄存器之间的”传输”过程中被修改了
ADD命令成功
SUB命令成功
OR成功
NOT成功

{BUG}在写左移,右移逻辑的时候我发现ALU里面藏着巨大的学问啊
在执行MOVA,NOT,RSR,RSL这四条指令时,都寄存器都需要把数据经过ALU送出去到总线上面,但是,这执行四条指令时,ALU到底分别是送出A输入口还是B输入口的指令呢?这就是学问所在了
执行NOT R1 XX时,ALU直接送出的数据应该是寄存器B口输出的数据,因为经过ALU处理后的数据还要返回给寄存器的”写入口”,而寄存器的RWBA控制信号是控制着写入口以及B输出口,而NOT指令要求处理后的数字仍然返回原来的寄存器,所以ALU送出B口
执行MOVA R1 R2时,ALU直接送出的数据是A口的数据,首先我们来解读一下MOVA指令
MOVA指令本质是这样:”MOVA RWBA口的输出 RAA口的输出”,也就是说,是把RAA口控制的输出送进RWBA口控制的那个寄存器里面,所以ALU应该直接送出A口
执行RSR指令.RSL指令就和NOT指令是同理的!

另外提醒自己一下,ADD SUB NOT OR运算都由ALU完成,运算正确与否应该在ALU的检查口进行检查,但是RSR与RSL的运算中,ALU仅仅是值传递,运算结果应该在移位逻辑的输出口进行检查

JMP指令完成,居然是一次过没有修改,真神奇
开始写JC
JC这个命令需要一个D锁存器来实现,功能就是
假如上一次运算产生了溢出/为0,那么这个值就会被送入这个锁存器里面,然后下一个指令周期如果要执行JC指令的时候,就会先判断一下这个JC指令能否被执行,如果可以被执行,那就是按照和JMP一样的执行过程,如果不符合要求,那JC的”执行周期”就什么都不做,
{DEBUG}如何阻断JC和JZ执行是个有趣的问题
我想到了一个阻断执行的思路,那就是修改LD_PC的控制信号,把
LD_PC <= JMP or JZ or JC;升级为:
LD_PC <= JMP or (Z and JZ) or (C and JC);

JC与JZ的”不跳转情况”验证成功了

IN指令
0010 R1 XX
由于我的输入是直接送到总线上面的,这条命令就要保证在”执行周期”BUS上面不能有额外的输出!!也就是移位逻辑和RAM的输出必须关闭,这条命令好像直接让WE在执行周期为0就好了,哈哈哈
出现了一个错误,Error: The pin “RAM_CHECK[7]” has multiple drivers due to the non-tri-state driver "INPUT_TO_BUS[7]"错误:引脚“RAM_CHECK[7]”有多个驱动程序,因为非三态驱动程序“INPUT_TO_BUS[7]”
我思考一下,是因为在RAM向bus上面输出的时候,我的INPUT也在输出,所以我需要做一个元器件,在”INPUT指令的执行周期”才开放这个INPUT_TO_BUS[7…0]
嘿嘿嘿,加一个三态门就完事了,嘿嘿嘿

OHHHHHHH
{DEBUG}发现我的JC和JZ指令存在严重的BUG
湖南大学数字逻辑CPU大作业--CPUdebug日志
看这个JC指令执行周期后的下降沿,PC莫名其妙变成了带个X?!
那一刻,我突然想起了我的PC,在这种JC指令无法执行的情况下,仍然会保持当前的这个地址不变化,那么下一次,取出的仍然是这个指令(因为PC的地址没有变化嘛),所以说,假如JC指令无法正常执行,那么PC就需要像非JMP指令那样正常加1

所以,我需要修改IN_PC(pc的自加信号)的逻辑表达式
目的就是,当”当前是PC指令(指令译码器输出信号PC=1),并且无法执行(控制信号C=0)”时,IN_PC也要为1

{DEBUG}啊,我又发现另一个连锁的bug
湖南大学数字逻辑CPU大作业--CPUdebug日志
这个PC中”XX”的出现原因本质上是因为LD_PC <= JMP or (Z and JZ) or (C and JC);的这个控制信号逻辑书写有误,因为我的”C””Z两个值”有时候会进入了”未定义状态”,因为我的ALU并不是时时刻刻都在向外输出CF和ZF值的,所以这个问题需要解决
我决定手动写一个一位的存储器来代替D触发器
哈哈哈哈,我想到一个更加巧妙的办法处理这个错误,我修改了ALU的代码,让ALU在执行非运算指令(即控制信号S[4…0]不为运算指令时)输出”temp <= “011111111””
由于我后续的命令是这个样子:
T <= temp(7 downto 0);
cf <= temp(8);
zf <= not(temp(8) or temp(7) or temp(6) or temp(5) or temp(4) or temp(3) or temp(2) or temp(1) or temp(0));
也就是说,我的cf和zf都会默认为0,而且是时时刻刻都有值的,就不会出现”未定义”的LD_PC了
呼呼,这才彻底DE完所有和JMP,JC,JZ指令有关系的bug,可以继续写输入IN指令了
复制黏贴一下上面写过的思考
IN指令
0010 R1 XX
由于我的输入是直接送到总线上面的,这条命令就要保证在”执行周期”BUS上面不能有额外的输出!!也就是移位逻辑和RAM的输出必须关闭,这条命令好像直接让WE在执行周期为0就好了,哈哈哈
出现了一个错误,Error: The pin “RAM_CHECK[7]” has multiple drivers due to the non-tri-state driver "INPUT_TO_BUS[7]"错误:引脚“RAM_CHECK[7]”有多个驱动程序,因为非三态驱动程序“INPUT_TO_BUS[7]”
我思考一下,是因为在RAM向bus上面输出的时候,我的INPUT也在输出,所以我需要做一个元器件,在”INPUT指令的执行周期”才开放这个INPUT_TO_BUS[7…0]
嘿嘿嘿,加一个三态门就完事了,嘿嘿嘿
完成,输入IN指令完成!!!
湖南大学数字逻辑CPU大作业--CPUdebug日志
啦啦啦啦啦啦

**{DEBUG}**我又发现JC指令的bug

关于指令的时钟周期思考:

因为我的C信号是存储在一个D触发器里面的,所以这个信号只能保存一个时钟周期,但是,一条指令从取地址到执行完毕需要一共4个时钟周期(注意,很多指令真正”执行完毕是在”执行周期”结束的那个下降沿(因为比如JMP命令要写入PC,而PC和WE都是下降沿写入的)”),SM=0占用2个时钟周期,SM=1占用2个时钟周期,PC恰好会在SM信号的上升沿进行自加(这个自加的时间结点其实和SM是没有直接联系的,是由控制信号产生逻辑进行控制的,只是恰好满足这个特性罢了)
JC指令的功能是”假如JC指令的前一条ADD或者SUB指令是产生了溢出进位/溢出借位,那么这时JC指令可以进行跳转,否则不行”,因此,我的C信号必须储存至少得从ALU(ALU是个非钟控元件,寄存器RWBA和RAA输出什么他就会瞬间运算什么并输出,ALU的输出持续时间取决于寄存器A口和B口输出的持续时间有多久)到下一条指令的”执行周期开始”,这个时间刚好就是3个周期,所以我就把C信号用三个一组的”移位寄存器”进行存储,这样就能存储3个时钟周期了
解决方案如图
湖南大学数字逻辑CPU大作业--CPUdebug日志
湖南大学数字逻辑CPU大作业--CPUdebug日志
NOP指令,听起来这么玄乎,其实就是”NO Operation”(不进行任何操作)的意思,实现起来非常,非常简单

至此为止我来讲一讲我的RAM中存储的命令吧

第一行命令(RAM中地址从”0000 0000”至”0000 1000”)依次测试了
1.MOVC(把RAM 00000000中的数据移到00号寄存器中) 2.MOVC(把RAM 00000000中的数据移到01号寄存器中) 3.ADD 00 01 4.SUB 00 01 5.MOVA 00 01 6.OR 00 01
7.NOT 00 XX 8.RSR 00 XX 9.RSL 00 XX
第二行命令(RAM中地址从”0000 1001”至”0000 1010”)的这两个空格内,第一个空格是JMP跳转指令,第二个空格是存储着要跳转到的位置,也就是00010010(其实就是mif文件第三行开头)(这个JMP指令可以成功执行)
第三行命令(RAM中地址从”0001 0010”开始)
1.JZ指令(此次执行不成功) 2.JC指令(此次执行不成功) 3.IN 00 XX(成功向00号寄存器写入数字”100000000”) 4.IN 01 XX(成功向01号寄存器写入数字”100000000”)
5.ADD 00 01(运算产生进位,所以控制信号C将会持续3个时钟周期为”1”) 6.JC指令(此次成功执行并跳转至”00011011”处)
第四行(RAM中地址从”0001 1011”开始)
1.NOP{01110000} 2.IN 10 XX{00101000}(通过外部输入(在波形里面)向10号寄存器(C寄存器)中写入数值”00011111”(其实刚好就是RAM中指令MOVB后面一个格子的地址)) 3.IN 00 XX{00100000}(向00号寄存器中写入数值”0111 0000”(其实就是NOP指令)) 4.MOVB M 00{11111100}(向RAM中地址为”10号寄存器(C寄存器)中所存地址”的存储单元写入00号寄存器的值)(此时这个格子刚好就是00011111地址,下一指令就是从这个地址里面取出来的) 5.第五个格子刚好就执行了NOP命令 6.OUT 00 XX{01000000}
7.IN 00 XX{00100000}(向00号寄存器中写入数值”00000000”)
8.IN 00 XX{00100100}(向01号寄存器中写入数值”00000000”)
9.ADD 00 01 {10010001}
第五行(RAM中地址从”0010 0100”开始)
1.JZ{00010001}(跳转至第六行开头,即地址”0010 1101”)
第六行(RAM中地址从”0010 1101”开始)
1.HALT{100000000}