不知道自己该干什么 就静下心来好好想想
有限状态机?
什么是有限状态机?你以为一开篇我就给你说?你想的太天真了,那我肯定会先扯一堆废话,然后再进入主题啊,好了,废话结束了。开始毒奶你!是不是刚开始接触的时候就如下图所示一毛一样
但是我必须要毒奶你一口,我这么笨的人都能搞明白了,你必然不在话下,然后就是还是得花时间去搞,你看的多自然就很清楚,还有就是实践才是检验真理得唯一标准,你要是不上手做,纸上谈兵那肯定不行对吧!下面就真的开始了
所以还是学习好,嗯,真香!
有限状态机理论理解
维基百科上解释说:表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。我觉得细品下来,好像有内味~状态机在我看来可能就是FPGA里面的一个小特色,每个产品或者说每种编程语言都有这样的东西,不可能说所有的编程语言都是一样的对吧。其次就是这个东西差不多就是把一个完整的逻辑按照某些标准将之分为几个部分酱紫,说白了就是时序逻辑和组合逻辑分开,因为到现在我们都知道了吧,FPGA比较注重资源利用这块,确实现在看来印证了前段时间在论坛上看到的那个比喻,如果说FPGA是穿着谈黄色长裙的姑凉,那么单片机就是身着紧身背心的肌肉硬汉。当然这只是玩笑话,不喜勿喷~它将时序这个部分看的比较重,除了Verilog编程不是很习惯以外,其他的都和普通编程语言差不多了。
状态机的分类
按照状态机的输出关系可以分为两类:Mealy状态机和Moore状态机,其实课件上解释的很清楚了,那我就直接贴上来了:
下图是Mealy状态机的结构图
这是Moore状态机的结构图
看图的意思就是Mealy状态机的输出模块有两个输入看到了吗,current_state和输入信号这两个,而Moore状态机的输出只有一个当前状态(current_state),这个区别就很好的帮我区分了这两个状态机,这样不至于说我们会混淆。下面还是贴一段示例代码,这个我说明清楚了是课件上的代码,因为我觉得还是很清楚很有用的。(注释纯手打,别忘了看哦)
//先写Moore状态机的吧
//定义两个寄存器用来存放当前状态和下一状态,state就是current_state
reg [1:0] state ,next_state;
always @(posedge clk)
begin
//说明状态转换关系,将之前一阶段的下一阶段赋值给当前阶段
//我感觉可以理解为将状态装在寄存器里,然后通过case语句进行匹配
state = next_state;
case (state)
2’d0: //状态0
begin
out = 1;// 输出
if (in1) next_state = 1; // 下一状态确定
else next_state = 2;
end
2’d1: //状态1
begin
if (in2) next_state = 0;
else next_state = 3;
end
endcase end
//这段是Mealy状态机的
reg [1:0] state ,next_state;//和上面的是一样的
//不同的地方开始了,由于它的输出和两个因素有关,你细品
//两个always语句都做了什么事情,因为我们知道always语句在这里面是并行执行的,其实有没有将一件事情按照不同的条件划分为了几个部分,但是他们又是同时执行或者说监视的呢
always @(posedge clk)
if(reset)
state <=idle; //复位到初始阶段,这个idle和上面的st0 st1一样的
else
state <= next_state;
always @(state or in1 or in2)
begin
case (state)
2’d0:
begin
out <= in1 & in2; // 输出
if (in1) next_state <= 1; // 下一状态确定
else next_state <= 2;
end
2’d1:
begin
out <= ~in2;
if (in2) next_state <= 2;
else next_state <= 3;
end
endcase
状态机的写法
其实你去搜一下或者巴拉巴拉的,可能是因为现在还没学到后面,所以我说的如果错了,请大家见谅,状态机的写法据说会分为四种:一段式、两段式、三段式以及四段式。 顾名思义,将整个设计通过某种标准分为一段一段的,然后正确实现功能的方法,异同优弊显然还是有点大的…
一段式自然就是通过一段逻辑表达将整个功能涵盖进去,整个状态机的状态转移、转移条件、对应状态的输出都写在一个always块里,但是这样,我们不难联想到平时我们写代码的时候,如果一个项目比较大,例如说涉及到很多东西,然后你还一定要将他放到一个文件里面,小朋友,这个问号你清楚了吗?
两段式,这个写法,其实我试了这几种,我觉得两段式比较适合我,状态机由两个always语句构成,第一块只谈状态转移,咱这后面的就是补全逻辑,将次态和输出写出来。刚开始就感觉要不还是再分一下,但是写过一次之后就感觉两段式真香,原理都是这样,我何必再去浪费我自己的时间再去写一次呢,或者说后面的三段式四段式有一个弊端,就是如果状态太多了,有的时候是不太会去照着状态图写的,两段式直接一次性写好它不香吗,有时候还得翻上面的代码,翻起来还是有点麻烦我觉得。
三段式,这个写法是大家平时最推广的吧,因为我看了很多状态机的代码都是用的三段式,或者说有一个说法我也不知道是不是对的,因为我没去探讨,只是在论坛上看到有这么个说法,就是说三段式并不是由三个always块构成的,而是第一段+第二段+“第三段”,前两段都是可以一个always语句可以实现的,但是这第三段可能有很多的always语句,说错了别喷我,后面确定了我会回来改的,或者兄弟你知道的话,主页邮箱联系我一下,咱立马改!这里需要注意一下的是这三段分为时序逻辑(状态转移块)、组合逻辑(转移条件块)和时序逻辑(对应状态的输出),因此在第一段和第三段里面,赋值需要用非阻塞赋值,你知道为什么吗?不知道就思考一下,兄弟~ 这个记住了哦!然后第二段用阻塞赋值哦~
四段式,四段式不是指三个always代码,而是四段程序:第一段,同步时序的always模块,格式化描述次态迁移到现态寄存器;第二段,组合逻辑的always模块,描述状态转移条件判断;第三段,用assign定义转移条件。注意条件一定要加上现态;第四段,设计输出信号。规范要求一个always设计一个信号,因此有多少个输出信号,就有多少个always。四段式解析引用自http://blog.sina.com.cn/s/blog_15430b9220102x7ln.html
有限状态机实例操作加深理解
下面拿几个栗子来实践一下,分别是当前课件上(不知道后续还有没有课件)一个没有完成的栗子和课后习题
1、用状态机设计交通灯控制器
有一条主干道和一条支干道的回合点形成了一个十字交叉路口,主干道为东西方向,支干道为南北方向。为确保车辆安全,并迅速的通行,在交叉通道的每个入口处设置了红、绿、黄三色信号
要求:oh 对不起,我真的不想打字了,贴图比较直接
大致就是这个样子了,状态图我直接就用现成的了,平时如果需要自己画这种状态图可以用 visio 和 rose ,但是其实 word 、 wps 和其他在线转换工具我记得也是可以画的,所以这个问题倒是也不大。当然,我们还是必须得会手画,毕竟这玩意数电学过的,当时不是画的计数器巴拉巴拉的吗。
先把代码给贴上来,这个代码我写的很繁琐,对不起大家,但是优化的方案我之后再修改吧,今天就到这里,这儿还差一个数码管驱动模块,然后再通过一个顶层文件连接起来,就完成了,但是我懒啊,有什么办法hahahahaahaaha驱动怎么写,可以去看一下上篇文章或者看看实验二哦 ~ 激励我也贴上吧,我是真的懒惰我发现,写个激励也是这么漫不经心,oh,我这该死的无处安放的魅力
之前部署的代码块出现了换行无效,挤成一坨的情况,猜想可能是由于代码长度超出网页默认代码块长度?但是转念一想不对,仔细看了看,发现是由于没有 声明代码格式 的问题,这里也要说一下,因为现在代码是放在自己搭建的网站上的,模板上集成的资源是不如csdn等平台那样丰富的,所以在这些细节上还是需要注意一些,至少现在我需要这样,在代码段符号旁边声明代码类型,可是markdown并不支持Verilog等语言,所以就用了cpp格式。目前缩进这个问题我还没有找到解决办法,但是复制粘贴后缩进是没有问题的,这里我需要解释一下,我这里就是通过三段式的写法完成这个状态机,但是我真的很想用两段式,本来后面的倒计时的部分是想用模块来写的或者写个task,但是这个后面有时间再去写吧,温馨提示一句,常见的模块你如果写好了,完全可以封装一下之后直接拿来用,或者你要是有新的想法,欸~这个代码我写的好像有点水平,封装成自己的黑盒子,多帅哦
module traffic_control(clock,reset,sensor1,LED,time1_H,time1_L,time2_H,time2_L);
input clock,reset,sensor1;
output reg [5:0] LED;
output reg [3:0] time1_H,time1_L,time2_H,time2_L;
reg [2:0] sensor2 = 3'b000;
reg [2:0] current_state,next_state;//状态寄存器
parameter st0=0,st1=1,st2=2,st3=3,st4=4;
//三段式状态机描述方式:将时序逻辑与组合逻辑分开进行描述
//第一段描述时序逻辑--->进行当前状态与下一状态的切换
always @(posedge clock or negedge reset)
begin
if(!reset)
current_state <= st1;
else
current_state <= next_state;
end
/*
第二段描述组合逻辑-->实现各个输入、输出和状态判断的逻辑描述
此处的敏感电平信号current_state --> 现态
sensor1 --> 传感器1(可以理解为特殊情况到来的信号标志)
sensor2 --> 传感器2(一个3位的信号,用来检测交通各状态完成情况,若为111则表示三个延时都结束了)
*/
always @(current_state or sensor1 or sensor2 or posedge clock)
begin
//初始化
//通过一个case语句来实现状态判断以及逻辑实现
//第一个if都先判断是否有特殊情况
case(current_state)
st0://状态0 主干道红灯亮,支干道红灯亮
begin
if(sensor1)
begin
next_state = st0;
end
else
begin
next_state = st1;
end
end
st1://状态1 主干道绿灯亮 支干道红灯亮,时间35秒
begin
if(sensor1)
begin
next_state = st0;
end
else if(sensor2[2])//判断状态1是否结束,是则执行下一状态(状态2)
begin
next_state = st2;
end
else
begin
next_state = st3;
end
end
st2://状态2 主干道黄灯亮 支干道红灯亮,时间5秒
begin
if(sensor1)
begin
next_state = st0;
end
else if(sensor2[1])//判断状态2是否结束,是则执行下一状态(状态3)
begin
next_state = st3;
end
else
begin
next_state = st2;
end
end
st3://状态3 主干道红灯亮 支干道绿灯亮,时间25秒
begin
if(sensor1)
begin
next_state = st0;
end
else if(sensor2[0])//判断状态3是否结束,是则执行下一状态(状态4)
begin
next_state = st4;
end
else
begin
next_state = st3;
end
end
st4://状态4 主干道红灯亮 支干道黄灯亮,时间5秒
begin
if(sensor1)
begin
next_state = st0;
end
else if(sensor2[1])//判断状态4是否结束,是则执行下一状态(状态1)
begin
next_state = st1;
end
else
begin
next_state = st3;
end
end
endcase
end
//第三段描述状态输出,同步时序always块
//LED状态显示与倒计时
always @(posedge clock or negedge reset)
begin
LED <= 0;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
if(!reset)
begin
sensor2 <= 0;
LED <= 0;
end
else
begin
case(current_state)
st0:
begin
LED <= 6'b100100;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
end
st1:
begin
LED <= 6'b010100;
time1_H <= 3;
time1_L <= 5;
time2_H <= 3;
time2_L <= 5;
if(!reset)
begin
LED <= 6'b100100;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
end
else
begin
if(time1_L == 0 && time2_L == 0)
begin
if(time1_H == 0 && time2_H == 0)
begin
sensor2 <= 3'b100;
end
else
begin
time1_L <= 9;
time2_L <= 9;
time1_H <= time1_H - 1;
time2_H <= time2_H - 1;
end
end
else
begin
time1_L <= time1_L - 1;
time2_L <= time2_L - 1;
end
end
end
st2:
begin
LED <= 6'b001100;
time1_H <= 0;
time1_L <= 5;
time2_H <= 0;
time2_L <= 5;
sensor2 <= 3'b010;
if(!reset)
begin
LED <= 6'b100100;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
end
else
begin
if(time1_L == 0 && time2_L == 0)
begin
if(time1_H == 0 && time2_H == 0)
begin
sensor2 <= 3'b010;
end
else
begin
time1_L <= 9;
time2_L <= 9;
time1_H <= time1_H - 1;
time2_H <= time2_H - 1;
end
end
else
begin
time1_L <= time1_L - 1;
time2_L <= time2_L - 1;
end
end
end
st3:
begin
LED <= 6'b100010;
time1_H <= 2;
time1_L <= 5;
time2_H <= 2;
time2_L <= 5;
sensor2 <= 3'b001;
if(!reset)
begin
LED <= 6'b100100;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
end
else
begin
if(time1_L == 0 && time2_L == 0)
begin
if(time1_H == 0 && time2_H == 0)
begin
sensor2 <= 3'b001;
end
else
begin
time1_L <= 9;
time2_L <= 9;
time1_H <= time1_H - 1;
time2_H <= time2_H - 1;
end
end
else
begin
time1_L <= time1_L - 1;
time2_L <= time2_L - 1;
end
end
end
st4:
begin
LED <= 6'b100001;
time1_H <= 0;
time1_L <= 5;
time2_H <= 0;
time2_L <= 5;
sensor2 <= 3'b000;
if(!reset)
begin
LED <= 6'b100100;
time1_H <= 0;
time1_L <= 0;
time2_H <= 0;
time2_L <= 0;
end
else
begin
if(time1_L == 0 && time2_L == 0)
begin
if(time1_H == 0 && time2_H == 0)
begin
sensor2 <= 3'b000;
end
else
begin
time1_L <= 9;
time2_L <= 9;
time1_H <= time1_H - 1;
time2_H <= time2_H - 1;
end
end
else
begin
time1_L <= time1_L - 1;
time2_L <= time2_L - 1;
end
end
end
endcase
end
end
endmodule
这里有一个致命的问题,昨天我没有发现,仿真出了波形,瞟了一眼没看数值,但是今天整理的时候发现,我的计数器根本跑不起来,原因是什么呢,发现是时钟的问题,我这里是全局共用一个时钟信号,那么这就导致了在执行这个always块计数的期间,当脉冲再次来临,程序会直接跳出去直接赋值下一个状态。这个问题刚开始我考虑到了!但是当时没太过注意,因为当时看到一个方案–> 激励直接给脉冲就行。。。。。。但是现在看来不太行。所以,这也就是分段分多了的弊端(个人理解),因此,吸取教训做出如下修正:
别想了,我还没写,明天写,你懂的
激励我需要说一下,这个我只是测试了最基本的,明天我改完整好吧
initial begin
// Initialize Inputs
clock = 0;
reset = 1;
sensor1 = 0;
// Wait 100 ns for global reset to finish
#5 reset = 0;
#5 reset = 1;
// Add stimulus here
end
always #5 clock=~clock;
2、序列检测
设计一个状态机实现在时钟clk的控制下检测输入的串行数据是否为”1101”,当串行数据是”1101”,flag_out=a,否则flag_out=b。
要求:
a、画出状态转移图
b、并写出设计实现程序
c、写出测试代码,并给出仿真波形
这里我们可以先看一下题目,第一个想到的是什么,我最开始想到的就是c语言的一个字符一个字符检测,巴拉巴拉的,然后就是Java直接对比检测,然后我好像就有思路了,但是我还是不知道该怎么写,就在这个时候,某位好心的同学给我送来了一篇文章, 【 FPGA 】序列检测器的Moore状态机实现 ,当然了参考谁的资料我不会赖账说这是我原创的,标注还是要有的,这里面就写的确实比较清楚, 我把状态图贴上来,是借用的那位兄弟的,因为我嫌太麻烦了而且在线生成的状态机效果并不很好…
然后列出代码
`timescale 1ns / 1ps
module detect_moore(
input clk,
input clr,
input seq_in,
output reg [7:0] flag_out
);
//定义寄存器变量
reg [2:0] current_state,next_state;
//采用二进制顺序编码对状态进行声明
localparam
[2:0] s0 = 3'b000,
s1 = 3'b001,
s2 = 3'b010,
s3 = 3'b011,
s4 = 3'b100;
//第一段 时序逻辑
always @(posedge clk or posedge clr)
begin
if(clr)
current_state <= s0;
else
current_state <= next_state;
end
//第二段 组合逻辑
always @(current_state or seq_in)
begin
case(current_state)
s0:
if(seq_in == 1'b1)
next_state = s1;
else
next_state = s0;
s1:
if(seq_in == 1'b1)
next_state = s2;
else
next_state = s1;
s2:if(seq_in == 1'b0)
next_state = s3;
else
next_state = s2;
s3:if(seq_in == 1'b1)
next_state = s4;
else
next_state = s3;
s4:if(seq_in == 1'b1)
next_state = s1;
else
next_state = s4;
default:next_state = s0;
endcase
end
//第三段 时序逻辑
always @(current_state )
begin
if(current_state == s4)
flag_out <= 8'b0110_0001;
else
flag_out <= 8'b0110_0010;
end
endmodule
`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Create Date: 19:03:07 04/11/2020
// Design Name: detect_moore_tb
// Description: 激励文件
////////////////////////////////////////////////////////////////////////////////
module detect_tb;
// Inputs
reg clk;
reg clr;
reg seq_in;
reg [20:0] seq_in_mid;
reg [20:0] seq_in_box;
integer i;
// Outputs
wire [7:0] flag_out;
// Instantiate the Unit Under Test (UUT)
detect_moore uut (
.clk(clk),
.clr(clr),
.seq_in(seq_in),
.flag_out(flag_out)
);
initial begin
// Initialize Inputs
clk = 0;
clr = 1;
seq_in_mid = 21'b110111010110100101101;
#20 clr = 0;
seq_in = 0;
for(i=0;i<21;i=i+1)
begin
#10 seq_in = seq_in_mid[i];
seq_in_box[i] = seq_in_mid[i];
end
end
initial begin
$monitor ($time,"seq_in=%d",seq_in);
#800 $stop;
end
always #5 clk = ~clk;
endmodule
这里为什么flag_out要用8位宽呢,部分同学有点疑惑,因为仿真界面可以转换数值显示(ISE4.7),右键你想要改变的结果,找到Radix选项,一般都是默认的二进制,最下面有ASCLL,所以就可以显示a,b了
完成!
3、数字频率计
设计能实现自动测频十进制数字频率计,并给出测试文件与仿真波形(此题不用写在作业本上,直接列于文本中)
具体功能如下:
1)测量范围: 1Hz~9999Hz
2)测量的数值通过4个数码管显示
3)频率超过9999Hz时,溢出指示灯亮,可以作为扩大测量范围的接口
先放程序,再说问题,注释写的还挺多哈
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 23:47:41 04/11/2020
// Module Name: TESTCTL
// Description: 测频控制模块产生计数器使能、计数器清零信号和锁存器锁存信号
//////////////////////////////////////////////////////////////////////////////////
module TESTCTL(
input CLK,RST, //输入为1HZ时钟信号,复位信号
output CNT_EN,RST_CNT,LOAD//信号依次为计数器使能、计数器清零信号和锁存器锁存信号
);
reg CNT_EN,RST_CNT,LOAD;
reg [2:0] current_state,next_state;//状态信号寄存器
localparam [2:0] s0 = 3'b001,s1 = 3'b010,s2 = 3'b011,s3 = 3'b100;
always @(posedge CLK or negedge RST)
begin
if(!RST)//复位信号低电平有效,使能信号全部清零
current_state <= s0;
else
current_state <= next_state;
end
always @(current_state)
begin //遇到基准信号的下一个上升沿,状态变化一次,每次变化后状态持续1s
case(current_state)
s0:
begin//初始化
CNT_EN <= 1'b0;
RST_CNT <= 1'b0;
LOAD <= 1'b0;
next_state <= s1;
end
s1:
begin//第一个上升沿到达,开始计数,计数1个基准信号周期内待测信号的上升沿个数,此个数即为待测信号的频率
CNT_EN <= 1'b1;//计数使能信号有效
RST_CNT <= 1'b1;//计数器置位信号低电平有效
LOAD <= 1'b0;
next_state <= s2;
end
s2:
begin//第二个上升沿到达,计数完成,锁存使能信号有效,测得频率锁存至锁存器中
CNT_EN <= 1'b0;
RST_CNT <= 1'b1;
LOAD <= 1'b1;
next_state <= s3;
end
s3:
begin//第三个上升沿到达,清零使能信号有效,计数器清零,为下一次计数做准备
CNT_EN <= 1'b0;
RST_CNT <= 1'b0;
LOAD <= 1'b0;
next_state <= s1;//清零信号有效,进入下一次测量
end
default
begin
CNT_EN <= 1'b0;
RST_CNT <= 1'b0;
LOAD <= 1'b0;
next_state <= s1;
end
endcase
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 23:47:05 04/11/2020
// Module Name: Cnt10d
// Description: 模10计数器,计算分频结果,同时结果连接数码管显示
//////////////////////////////////////////////////////////////////////////////////
module Cnt10d(
input F_in,CNT_EN,RST_CNT,clear,//输入待测信号、计数使能信号、复位信号、清零信号
output [3:0] TIME_HH,TIME_H,TIME_L,TIME_LL,//输出
output CARRY_OUT//溢出指示端口
);
reg CARRY_OUT;
reg [3:0] TIME_HH,TIME_H,TIME_L,TIME_LL;
always @(posedge F_in or negedge RST_CNT)//异步复位,敏感信号为待测信号的上升沿用于计数上升沿个数
begin
if(!RST_CNT)//复位信号低电平有效,计数器输出清零
begin
TIME_HH <= 4'b0;
TIME_H <= 4'b0;
TIME_L <= 4'b0;
TIME_LL <= 4'b0;
CARRY_OUT <= 1'b0;
end
else if(clear)//锁存信号后需要一个清零信号清零计数器,为下一次计数准备
begin
TIME_HH <= 4'b0;
TIME_H <= 4'b0;
TIME_L <= 4'b0;
TIME_LL <= 4'b0;
CARRY_OUT <= 0;
end
else if(CNT_EN)//计数使能信号有效
begin
if(TIME_LL == 4'b1001)
begin
TIME_LL <= 4'b0;
TIME_L <= TIME_L + 1;
if(TIME_L == 4'b1001)
begin
TIME_L <= 4'b0;
TIME_H <= TIME_H + 1;
if(TIME_H == 4'b1001)
begin
TIME_H <= 4'b0;
TIME_HH <= TIME_HH + 1;
if(TIME_HH == 4'b1001)
begin
TIME_HH <= 4'b0;
CARRY_OUT <= 1;
end
else
TIME_HH <= TIME_HH + 1;
end
else
TIME_H <= TIME_H + 1;
end
else
TIME_L <= TIME_L + 1;
end
else
begin
TIME_LL <= TIME_LL + 1;
CARRY_OUT <= 0;
end
end
else
begin
TIME_HH <= TIME_HH;
TIME_H <= TIME_H;
TIME_L <= TIME_L;
TIME_LL <= TIME_LL;
CARRY_OUT <= CARRY_OUT;
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 00:13:15 04/12/2020
// Module Name: CNT_LED
// Description: LED驱动模块
//////////////////////////////////////////////////////////////////////////////////
module CNT_LED(
input [3:0] LED_IN,
output [6:0] LED_OUT
);
reg [6:0] LED_OUT;
always @(LED_IN)
begin
case(LED_IN)
4'b0000: LED_OUT <= 7'b0111111;
4'b0001: LED_OUT <= 7'b0000110;
4'b0010: LED_OUT <= 7'b1011011;
4'b0011: LED_OUT <= 7'b1001111;
4'b0100: LED_OUT <= 7'b1100110;
4'b0101: LED_OUT <= 7'b1101101;
4'b0110: LED_OUT <= 7'b1111101;
4'b0111: LED_OUT <= 7'b0000111;
4'b1000: LED_OUT <= 7'b1111111;
4'b1001: LED_OUT <= 7'b1101111;
default:LED_OUT<=7'b0111111;
endcase
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 23:47:53 04/11/2020
// Module Name: reg4
// Description: 锁存器模块,锁存器使能时,将计数器的输出值所存并输出
//////////////////////////////////////////////////////////////////////////////////
module reg4(
input LOAD,rst_reg,//锁存器使能信号、复位信号
input [3:0] result_hh,result_h,result_l,result_ll, //输入计数结果
output [3:0] data_hh,data_h,data_l,data_ll //输出锁存后的值 //开关
);
reg [3:0] data_hh,data_h,data_l,data_ll;
always @(negedge rst_reg or posedge LOAD)
begin
if(!rst_reg)//复位信号低电平有效,锁存器输出清零
begin
data_hh <= 4'b0;
data_h <= 4'b0;
data_l <= 4'b0;
data_ll <= 4'b0;
end
else if(LOAD)//锁存器信号高电平有效,将计数器输出锁存至锁存器
begin
data_hh <= result_hh;
data_h <= result_h;
data_l <= result_l;
data_ll <= result_ll;
end
else
begin
data_hh <= data_hh;
data_h <= data_h;
data_l <= data_l;
data_ll <= data_ll;
end
end
endmodule
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 02:20:10 04/12/2020
// Module Name: top_final
// Description: top设计
//////////////////////////////////////////////////////////////////////////////////
module top_final(
input CLK,F_in,RST,//1HZ时钟基准信号、待测信号、复位信号
output [6:0] OUT_HH,OUT_H,OUT_L,OUT_LL,//数码管显示
output [3:0] data_hh,data_h,data_l,data_ll,//锁存器显示
output CARRY_OUT,
output [2:0] current_state,next_state
);
wire [3:0] TIME_HH,TIME_H,TIME_L,TIME_LL;
wire [6:0] LED_OUT;
wire [3:0] data_hh,data_h,data_l,data_ll;
wire [2:0] current_state,next_state;
//调用分频模块---1HZ
//fre_div div_final(clk,clk_1hz);
//调用控制模块
TESTCTL Control_final(CLK,RST,CNT_EN,RST_CNT,LOAD);
//调用计数器模块
Cnt10d Count_final(F_in,CNT_EN,RST_CNT,clear,TIME_HH,TIME_H,TIME_L,TIME_LL,CARRY_OUT);
//调用LED驱动模块
CNT_LED LED_final_1(.LED_IN(TIME_HH),.LED_OUT(OUT_HH));
CNT_LED LED_final_2(.LED_IN(TIME_H),.LED_OUT(OUT_H));
CNT_LED LED_final_3(.LED_IN(TIME_L),.LED_OUT(OUT_L));
CNT_LED LED_final_4(.LED_IN(TIME_LL),.LED_OUT(OUT_LL));
//调用寄存器模块
reg4 reg4_final(LOAD,rst_reg,TIME_HH,TIME_H,TIME_L,TIME_LL,data_hh,data_h,data_l,data_ll);
endmodule
测试激励文件
`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////
// Author: wuchao
// Create Date: 02:28:28 04/12/2020
// Design Name: top_final_tb
// Description: class_7 设计激励文件
////////////////////////////////////////////////////////////////////////////////
module class_7_tb;
// Inputs
reg CLK;
reg F_in;
reg RST;
// Outputs
wire [6:0] OUT_HH;
wire [6:0] OUT_H;
wire [6:0] OUT_L;
wire [6:0] OUT_LL;
top_final uut (
.CLK(CLK),
.F_in(F_in),
.RST(RST),
.OUT_HH(OUT_HH),
.OUT_H(OUT_H),
.OUT_L(OUT_L),
.OUT_LL(OUT_LL)
);
initial begin//初始化复位
RST = 1'b0;
CLK = 1'b0;
#20 RST = 1'b1;
F_in = 1'b0;
CLK = 1'b1;
end
always #5_0000_0000 CLK = ~CLK;
always #300 F_in = ~F_in;
endmodule
简单说一下实现的功能以及期间需要注意的问题:
本设计实现的功能在于通过激励里面定义的1Hz信号上升沿来作为状态机状态转换的触发条件。状态s0是一个初始化状态,将控制器、模10计数器(频率计)、锁存器和数码管驱动全部初始化,都处于clean状态(其中涉及的变量看代码应该可以看出来);
状态s1条件为第一个信号上升沿到达,计数使能信号有效,其他信号无效,模10计数器开始计数这1Hz的信号周期内待测信号来了多少个上升沿,那么这个值就做为频率计的输出结果,这里是一个四位的计算范围(1Hz~9999Hz),如果超出了计算范围,则溢出标志位CARRY_OUT置为1,否则为0;
状态s2条件为下一次上升沿到来,锁存器加载使能信号有效,其他信号无效,停止计数并将模10计数器的输出作为结果存放进锁存器中;
那么状态s3条件还是下一次上升沿到来,清零信号有效,其他信号无效,计数器清零为下一次计数做准备(锁存器中定义了一个单独的清零信号,但是这里没有在设计里面调用,如果要做实物设计是会加进去的)。功能介绍差不多就是这个样子了,注释也写的详细
然后说一下期间需要注意的问题:第一个就是在变量命名的时候,比如说这里模块之间的调用,有时候会存在变量名搞混了的情况,因此我是这样处理的,比如说计数器清零信号和使能信号,我在控制器和计数器里面是用的一样的名字,这样的话是可以不用在top文件里面声明的,虽然这样的弊端有点大(想也想得到)但是确实我不用还去翻之前的变量定义,但是这样的情况有时候会报错说这个名字被赋予次数太多了,这个见招拆招吧,还有就是其他的变量,我认为当一个新人看到变量名就知道这是什么模块什么功能的变量,这个是特别舒服的,您可千万别什么a,b,c了…包括模块名字
第二个就是设计思路,如果拿到别人的代码,如果是我,我就先去看top文件,欸~ 理清这个设计的脉络,然后再顺着top文件看单独模块的功能,这样的话,你代码大致看一遍,这个设计你差不多原理你就明白了,那么你就可以在原来的基础上进行你自己的改变,当然这种情况只是说工程比较小的情况,如果是一个项目工程,这就是作者的事情了,你要将代码开源,注释你还是写清比较好。。。
第三个就是遇到的一个问题, 如果代码或者仿真出错了怎么办? 这没办法啊,代码出错就先根据提示修改,一般的错误我们都能处理,如果发现一个不一般的呢?那这个就得靠百度和谷歌了,并非崇洋媚外,但是我还是推荐谷歌,这两款搜索引擎是真的差别大,你体验了你就知道了。要么花点钱 科学上网 (这个可以参考主页朋友链接,他写了科学上网的教程,用起来还挺好的),要么谷歌访问助手安排起来,可以在gituhb上下载,就是速度比较慢,一个压缩包但是也不大,如果嫌麻烦就主页邮件我,我发你也可以;继续说代码的事,你在看这篇文章,说明你基础的判断是有的,哪些错误是可以直接改的这个有数吧,某些看起来就很鬼畜的错误很可能是你因为错误而产生的错误,所以告诫就是不要排除所有错误再检查;接着仿真, 波形不对?全是高阻?不确定? 别慌啊,最左边的架构栏里把你设计的关键变量拖出来,restart一下,跑一下,通过对比变量的赋值波形,大概就能找到问题出在哪儿。这里需要说明的是你拖出变量再运行,前面已经运行的时间是没有值的,只有后面有,所以建议restart。或者你直接单步运行,一步一步的看看是哪儿逻辑跳转错误了。(这里我踩了一个坑,需要说明一下,因为我们都知道always语句是并行的,但是我们的单步一次只能指一行对不对,之前我以为会有区别,但是运行顺序是没有区别的,还有就是在状态机触发条件那儿,因为我不同模块里面都写了always块,但是我单步的时候发现我始终在一个文件运行,逻辑跳不出去?但是后来我发现了,逻辑是并行执行的,看似没跳出去,但是其他的always都在执行)
第四个是昨天晚上同学找我debug的时候发现的一个问题,top文件的那个声明wire类型的变量你们认为是怎么回事?默认的output都是wire型,因为你作为 单个 模块的输出,只能用wire,reg是不被允许的,那我们之前为什么可以用reg?那是因为要么你重新定义了一个reg类型的变量(寄存器)将输出的值装起来了,要么就是你这个模块与其他模块联合的时候这个输出作为了模块的内部输出,这样的话reg类型是被允许的。而这里的wire其实就是内部连线的意思,当你调用模块的输入输出与你写的模块输入输出不一样时,这个时候就将原来的输入输出通过一个wire来声明一下,这样的话,模块与模块之间的连接关系就可以确定了,否则可能会报错。(比如计数器与数码管驱动你要不是不声明wire的话,你数码管的输出是不可能与计数器连接起来的,除非你直接赋值)
对不起大家!!!!!!这里有一点没有提醒出来,就是如果没有改规则是运行不了我这个代码的,因为有一个致命的问题,关于编程风格,一般来说都是这样的
module 模块名字(a,b,c);
input a,b;
output c;
reg a,b,c;
对不对,但是我这里是这样的
module 模块名字(input a,b,output c);
reg a,b,c;
所以运行的时候会提示你这个错误 illegal redeclaration of “a” 巴拉巴拉的,翻译一下就是你这个玩意非法重新声明了,这个之前遇到过也改过,但是我是直接改了规则,允许这样操作,我忘记了这个所以导致了一系列的事情,oh 无情!但是现在问题解决了就行了,对吧
然后这里有一个很有意思的事情,当然也是我的问题,为啥写个代码我变成了这个样子,是因为无意之间我觉得这样的写法好像还挺舒服的(个人看法),所以就有了这样的结果,才有了改规则,才有了这档子事,形象彻底垮了。。。而且我甚至开始以为这样的写法和系统生成的写法相差无几,但是今天我去试了一下,发现虽然都是将变量写在括号里,但是它是这样的
module 模块名字(
input a,
input b,
output c
);
reg a,b,c;
和第二种的写法你看出什么了吗,没错,原因就在这个地方
感谢各位看官老爷的耐心~真的还是挺开心的,因为有人看到了这个位置,哈哈哈哈哈哈哈哈哈