模块"/>
从一个记忆游戏来说明Verilog——3、读入地址和数据模块
前言
我们已经在第一篇博客中谈到了我们的目标和具体的分工,也在上一篇博客中谈到了基于LSFR的随机数生成方式。接下来的目标是读入部分。
开始
在整个游戏中,读入部分虽然不是很难,也不是很重要,但其准确性还是很重要的。
要求&细节
- 我们希望的输出显示模块需要的是8个四位二进制数作为输入,所以需要让这两个模块进行对齐;
- 在输出显示中我们已经有了一个分频控制数码管使能信号和分段的时钟信号,但是我们在匹配数据还需要一个2HZ的闪烁信号(数码管控制必须要有分频),如果还是放在输出显示模块很明显不合适,所以我们需要在数据模块中直接添加使能信号控制(使能有效输出0,使能无效锁死数码管使能端。)
- 主模块准备写成类似状态机那样,一个一个状态来进行,所以我们需要判断地址的正确性,这里我们在读入地址采用返回四位的地址,最高位为0表示正确,不正确均为1000,这样我们就能控制状态的迁移了。
读入地址
先说一下读入地址吧,这个还是比较好处理的,只要S1的信号给到了我就读入,然后用case语句进行判断,判断8个输出和要传出的地址的内容即可。
功能
读入地址,地址由8为拨码开关显示,如果地址属于0-4则为正确(我们只有五个随机数),其他情况为错误。
地址正确则显示0000000+十进制地址;错误显示8个F
所以我们要输出的8个四进制数,要么是7个0+地址(正确情况),要么是8个1111(也就是0FH)
代码:
`timescale 1ns / 1ps
//读入地址并显示,自调整数码管输入
module in_place(input s1,input [7:0]sw,output reg [3:0]place,//最高位为0代表没问题output [3:0]num1_o,output [3:0]num2_o,output [3:0]num3_o,output [3:0]num4_o,output [3:0]num5_o,output [3:0]num6_o,output [3:0]num7_o,output [3:0]num8_o);reg [7:0]in ;reg [3:0]num1 = 4'b1111;reg [3:0]num2 = 4'b1111;reg [3:0]num3 = 4'b1111;reg [3:0]num4 = 4'b1111;reg [3:0]num5 = 4'b1111;reg [3:0]num6 = 4'b1111;reg [3:0]num7 = 4'b1111;reg [3:0]num8 = 4'b1111;assign num1_o = num1; //为输出变量赋初值assign num2_o = num2;assign num3_o = num3;assign num4_o = num4;assign num5_o = num5;assign num6_o = num6;assign num7_o = num7;assign num8_o = num8;always@(posedge s1)beginin <= sw;endalways@(*)//修改要输出number的值begincase(in)8'b0000_0000: beginnum1 = 4'b0000;num2 = 4'b0000;num3 = 4'b0000;num4 = 4'b0000;num5 = 4'b0000;num6 = 4'b0000;num7 = 4'b0000;num8 = 4'b0000;place <= 4'b0000;end8'b0000_0001: beginnum1 = 4'b0000;num2 = 4'b0000;num3 = 4'b0000;num4 = 4'b0000;num5 = 4'b0000;num6 = 4'b0000;num7 = 4'b0000;num8 = 4'b0001;place <= 4'b0001;end8'b0000_0010: beginnum1 = 4'b0000;num2 = 4'b0000;num3 = 4'b0000;num4 = 4'b0000;num5 = 4'b0000;num6 = 4'b0000;num7 = 4'b0000;num8 = 4'b0010;place <= 4'b0010;end8'b0000_0011: beginnum1 = 4'b0000;num2 = 4'b0000;num3 = 4'b0000;num4 = 4'b0000;num5 = 4'b0000;num6 = 4'b0000;num7 = 4'b0000;num8 = 4'b0011;place <= 4'b0011;end8'b0000_0100: beginnum1 = 4'b0000;num2 = 4'b0000;num3 = 4'b0000;num4 = 4'b0000;num5 = 4'b0000;num6 = 4'b0000;num7 = 4'b0000;num8 = 4'b0100;place <= 4'b0100;enddefaultbeginplace <= 4'b1000;num1 = 4'b1111;num2 = 4'b1111;num3 = 4'b1111;num4 = 4'b1111;num5 = 4'b1111;num6 = 4'b1111;num7 = 4'b1111;num8 = 4'b1111;end endcaseend
endmodule
你别问为什么要采用这样的结构,你也别问为什么把不多一层判断然后省一大堆的代码,当时是CV爽了现在也不想改了。(反正是说明白了,其他的就无所谓了)
模块的整体是不需要复位的,只要你输入了,主模块就有信号,我子模块就能跑。
另外我们为了让显示模块简单一点,所以就这模块中按照实际中输入的地址正确不正确都给好了对应的显示内容(错误为8个0FH,正确为7个0+十进制地址),所以采用了初始化为1111的方式(复位同理)。
读入数据
要求
有一个细节就是当我们按下S2,就说明进入了匹配状态,所以我们的输入使能信号有S2(开始)和S3(输入数据),而且这里面是需要有复位信号(读入的最后五位和输出使能都需要控制)
另外我们要输入正确的地址和正确的随机数(这些都在主模块中安排好了,传入即可)
按照我们的要求,我们是要输出两个使能信号的:
首先是闪烁的信号,之前说过在输出模块有两个分频不太好,所以在这里需要有一个按照时钟信号闪烁的使能端,方便输出模块处理;
另外一个是当我们的五位数匹配成功了,我们需要给出一个使能信号,让数码管对应显示地址-数据。
为了判断我们有几个数据是正确的,我们还添加了一个led灯显示的部分:how_many数组
(注意我们是串行输入,所以我们即使有正确的但是因为还没有进行移位,所以也不一定显示正确。)
比如随机数为12345,当我输入1234时,其实内部的数据为01234,当第五位输入了才能看到有数据匹配正确。
这东西也就图一乐,实际功效确实不怎么样。
当然了,如果真想搞建议看看kmp、bmh和最大子数组,然后用Verilog写一个算法 (bushi
代码
回归正题,先给出代码,然后我们进行分析。
`timescale 1ns / 1ps
//读入最后五位的number并判断对不对
module in_number(input [2:0]number, //拨码开关的后三位input s3, //输入使能input s_two, //开始使能input s4, //复位使能input clk,input [14:0]right_number, //正确的数据input [2:0]place, //地址output [4:0]how_many_o, //led灯输出output reg [3:0]number1, output reg [3:0]number2,output reg [3:0]number3,output reg [3:0]number4,output reg [3:0]number5,output reg [3:0]number6,output reg [3:0]number7,output reg [3:0]number8,output flash_o, //输出——闪烁使能output right_o //输出——正确使能);reg [2:0]in1 = 3'b000; //读入的内容reg [2:0]in2 = 3'b000; //将主模块的正确数据置为全1,防止开始就匹配成功reg [2:0]in3 = 3'b000;reg [2:0]in4 = 3'b000;reg [2:0]in5 = 3'b000;reg right = 1'b0; //判断输入对不对reg flash = 1'b1;reg [7:0]how_many = 8'b0000_0000;//为输出变量赋初值的方式assign right_o = right; assign flash_o = flash;assign how_many_o = how_many;wire clk_o;divider_wrong divw(clk,clk_o); //处理读入的数据always@(posedge s3 or posedge s4)beginif(s4) //读取的数据复位beginin1 <= 3'b000;in2 <= 3'b000;in3 <= 3'b000;in4 <= 3'b000;in5 <= 3'b000;endelse if(s_two) //对数据进行移位beginin5 <= in4;in4 <= in3;in3 <= in2;in2 <= in1;in1 <= number;endelse;end//处理输出——正确使能always@(posedge clk or posedge s4) //输出——正确使能的改变beginif(s4)beginright <= 1'b0;endelse if({in1,in2,in3,in4,in5} == right_number)right <= 1'b1;else;end//处理8位输出always@(posedge clk_o) beginif(s_two)begin//正确就是地址-数字(我们只需要给其中的几位就行,剩下的交给输出模块处理//即是给了也没问题,反正用不上if(right) beginnumber2 <= {1'b0,place};number4 <= {1'b0,right_number[2:0]};number5 <= {1'b0,right_number[5:3]};number6 <= {1'b0,right_number[8:6]};number7 <= {1'b0,right_number[11:9]};number8 <= {1'b0,right_number[14:12]};endelse//这里连输出都不用给了,只要有闪烁的信号并且正确使能无效,直接控制数码管即可begin flash <= ~flash;endendelse;end//控制我们的led灯输出,有几个亮的就是有几位匹配成功always@(posedge s4 or posedge clk) beginif(s4)begin //复位how_many <= 8'b0000_0000; end else //匹配beginif(in1 == right_number[14:12])how_many[0] <= 1'b1;elsehow_many[0] <= 1'b0;if(in2 == right_number[11:9])how_many[1] <= 1'b1;elsehow_many[1] <= 1'b0;if(in3 == right_number[8:6])how_many[2] <= 1'b1;elsehow_many[2] <= 1'b0;if(in4 == right_number[5:3])how_many[3] <= 1'b1;elsehow_many[3] <= 1'b0;if(in5 == right_number[2:0])how_many[4] <= 1'b1;elsehow_many[4] <= 1'b0;endend
endmodule
这里因为我们只给了部分的输出信号,所以可以有报错有信号没有驱动,这个可以不管,在注释中我们有表明。如果嫌烦也可以随便给一个赋值。
有必要说一下我们的随机数结构,in5是最高位,但是因为我们的输入随机数,他反了,所以我们采用逆向对应的方式。
至于how_many数组,就是简单匹配,没意思就不提了。
分频
这里因为是2HZ的频率,而我们的是找是108HZ,所以计数器应当数到4999_9999为一个周期。
具体的内容见分频的博客。(点击查看)
总结
这两个模块的总体开发难度不大,但是在处理复位信号以及和输出显示模块的衔接中,还是反复改了几次才敲定整体的架构,其实这样的一个项目的每一部分实现起来都不是十分困难,但是当整个合在一起,事情就变得有趣 很多。
我们虽然是自顶向下的设计,但个人还是建议想好了顶层模块的雏形(这里指我们的顶层模块和输出显示模块,都需要和多个模块进行交互),然后将需要用到的子模块敲定了,然后再搭建主模块,这样会好一点。
(一定要先想明白顶层的雏形,不然反复横跳了改的就不是一点点了,真·亿点点)
更多推荐
从一个记忆游戏来说明Verilog——3、读入地址和数据模块
发布评论