文章目录
相关资源网站(小梅哥FPGA)
本篇文章使用的开发板为:
小梅哥 Xilinx FPGA
型号:XC7A35T
芯片型号:XC7A35TFGG484-2
学习文章推荐
以下所有文章内容,均来自小梅哥FPGA教学视频,这个文章只是针对每个教学视频的个人理解总结。
一、整个工程的流程
写一套硬件描述语言,能够在指定的硬件平台上实现相应的功能
- 设计定义(让LED一秒闪烁一次)
- 设计输入(编写逻辑(使用Verilog代码描述逻辑),画逻辑图,使用IP盒(即提供的成品功能器件))
- 综合工具(由专业的EDA软件进行,Quartus、Vivado、ISE),对所写的逻辑内容进行分析,并得到逻辑门级别的电路内容。(如if else就是一个二选一多路器)
- 功能仿真(使用专门的仿真工具进行仿真,验证设计的逻辑功能能够实现。对于数字电路来说 ,仿真时基本接近于真实情况的,是可信的)
- 布局布线(走线带来延迟,延迟越长,主频越低)(在指定器件上将设计的逻辑电路实现)
- 分析性能(1、时序仿真(非常耗费时间),仿真软件进行观察;2、静态时序分析(平时用这个))
- 下载到目标板运行,查看运行结果。(板级调试)(使用ILA(嵌入式逻辑分析仪)(Vivado))
- 让设计的逻辑在目标板上正常工作(功能正常,性能稳定)
总结
- 设计定义
- 设计输入
- 分析综合(EDA、Vivado、Quartus)
- 功能仿真(Modelsim)
- 布局布线(Vivado、Quartus)
- 分析性能(时序仿真Modelsim、静态时序分析Vivado、Quartus)
- 板级调试
- OK
2024.12.22
二、基于Vivado的FPGA开发流程实践(二选一多路器)
打开Vivado并创建工程mux2后,Vivado界面上左边流程
即该软件就是按照开发流程进行运行的
什么是二选一多路器
当SEL=1时,out=a
当SEL=0时,out=b
用verilog语言,Vivado软件进行该电路实现
下图要注意符号的运用
1、设计输入:Design Sources中的代码
用到的语法(Verilog语言)
- 定义模块
module xxx(
......
)
endmodule
//定义一个模块
- 定义输入输出
input a;
output out;
- 赋值语句
assign out = (sel==1)?a:b;
//在sel=1的情况下,将a的值赋予out,否则将b的值赋予out
assign out = ~a;
//将a取反的值赋予out
2、分析和综合:分析设计输入中是否有错误
分析综合运行后,看message,其中没有报错那么这一步就完成了,接下来就进行仿真验证
3、功能仿真,Simulation Sources中的仿真实现代码(Test_Bench)
在仿真平台中,通过代码编写,搭建一个仿真平台,取验证之前编写的module是否OK
我们个人一般用不到ps级别的延时,ns够用
如
#1 //延时1ns
#2 //延时2ns
#1.001 //延时1.001ns,这是在配置为1ns/1ps情况下是可以实现的
写一个可以测试之前定义的module mux2的功能的仿真代码
reg s_a; //定义测试平台的输入信号,可接0和1逻辑输入
.a(s_a); //.代表测试端子,a代表module中a端口,(s_a)代表测试平台的reg端口
//这句话的意思是,将测试平台的(s_a)端子与module的a端子通过.来相接
wire out; //定义平台的输出信号,可根据分析输入信号和module来确认这个输出信号
initial begin
......
end
//测试平台的操作
跑仿真后出现这个界面,这个就是仿真的时序图,
第一次跑不全,再跑一次
此时可以看着时序图对out的信号进行对比,看是否是我们需求的信号输出结果。
仿真这一步挺重要的,建议需要掌握
4、布局布线
点击红框中的符号可以看到目前的编译状态
5、时序仿真
时序仿真电路中可以看到真实的电路中的信号延迟情况,
out都滞后于输入信号的变化,根据时序仿真,可以看出延迟情况,来判断 这个延迟我们能否接受
6、板级调试
(1)IO引脚分配
根据上图进行引脚分配
分配完成后,在引脚分配界面进行ctrl+s进行保存,保存名称和项目名称可以一样(不强求,也可以不一样)
(2)程序下载
点击后就可以进行创建,
创建完成后可以进行程序下载
这样就下载完成了,之后就可以进行板级调试。
2021.12.22
三、3-8译码器的实现
什么是3-8译码器
1、设计输入
3-8译码器
创建module,创建3-8译码器的端口
端口创建完成后,定义端口的输入输出状态
输入输出状态定义完成后,进行端口之间的逻辑编写
用到的语法
-
always块
-
位拼接
d是一个4位数据,通过assign将 a, 1bit常量值为0,b , c这四个数拼在一块赋值给d -
描述多位格式的常用限定符号
这两个表达方式是一样的
b 二进制 3‘b101 8'b0000_1010
o 八进制
d 十进制 3'd5 8'd10
h 十六进制 8'ha
- case() endcase
case括号中,为满足操作的条件
在满足条件下进行操作,如:\
3‘b000 : out = 8'b0000_0001;
具体解释为拼接后的{a,b,c}在满足3位bit值是000的情况下,将8bit的0000_0001赋值给out
整个3-8译码器代码为下
2、分析综合
分析综合的作用,检查设计输入的语法有无问题
分析之后查看reports,有错误则报红
3、仿真
创建一个测试平台,命名规则一般为(文件名_tb),tb代表test bench
测试平台的代码具体的流程为
- 定义测试平台的时间尺度
- 定义一个测试平台
- 定义测试平台的端口,标注好reg型(输入)和wire型(输出)
- 将模块放置到测试平台上
- 将模块中的端口与测试平台的端口互相连接
- 定义测试平台的测试动作(给各信号端口激励信号并延时)
- end
所有代码完成后,进行仿真
仿真第一次跑会根据仿真设置中的默认跑1000ns来跑,所以第一次的仿真都是1000ns的时间段中的动作
这里我们可以不用管,直接再次跑,在动作结尾加上 $stop; 即可跑到这里自动停止
4、时序仿真
跳过,这个项目中只进行了模拟仿真,并没有进行时仿真
原因是项目比较简单,无进行必要。但也可以进行。这个步骤有无都无所谓
查看电路
5、IO引脚分配
打开综合类工程
必须打开后才能看到IO planning这个选项
IO设置中首先将I/O Std电压设定为3.3(LVCMOS33),只针对于目前手中的这个开发板进行的设定
其次根据引脚对应表,将拨码开关引脚进行设定
LED灯对应的引脚(高电平点亮)
根据表进行设定
设定完成后进行ctrl+s保存
6、生成代码,板级调试
因为之前没有进行布局布线,即
在生成BitStream时候会进行检查,没布局布线的话这一步会自动进行,所以这一步在之前省略了
点击之后,等待完成。
下一步连接硬件板卡进行程序下载
之后进行板级调试就OK
三个拨码开关,设定为a,b,c
8个LED灯代表out 0-7
拨码开关不同状态代表不同输出,led显示表示输出
课后题,写4-16译码器代码
1、module设计输入
代码完成后进行分析综合,即检查下代码有无错误
2、功能仿真
代码完成后进行分析综合,即检查下代码有无错误
3、布局布线
略
4、 时序仿真
略
5、IO引脚分配
略
6、生成代码,板级调试
略
2024.12.23
四、时序逻辑计数器设计
需掌握知识
- 什么是D触发器
- 怎么组成计数器
- 计数器多少位如何取判断和设定
- module中如何精确进行延迟触发(延时)
- 阻塞赋值和非阻塞赋值初了解
什么是D触发器
CK上升沿的时候,将D端口的信号输出给Q端口
即D触发器有存储特性,只在CK端口上升沿变化,其余时间一概不管,即Q端口可以存储D的电平状态
什么是计数器
counter = counter+1
目标,设计1s频率闪烁的LED灯(亮灭各500ms)
目前板子的时钟是50MHz,那么执行每步操作时候为20ns
一个周期为1s,
则信号翻转一次需500ms,500ms需要计数500ms/20ns=25000000
用Verilog代码实现led闪烁
1、设计输入:module编写
- 确认module端口,需要有时钟信号Clk,复位信号Reset_n(低电平复位),Led控制信号
- 确认信号的类型
时钟信号为输入 input
复位信号为输入 input
led信号为输出 output
counter信号为内部计数,多位D触发器,具体多少位,通过以下计算
即需要25位D触发器,设定为reg类型的counter:reg [24:0]counter;
即最终的端口定义是这样的
通过这个定义可以看出,内部端口也是需要进行定义的,而且内部端口不再试input和output类型的端口了,这里则使用的试reg寄存器类型来表示。
同时对需要进行复杂操作的端口也要进行reg的定义,如之后的代码需要让Led端口自行取反,即Led=!Led; - 端口的逻辑操作,两种写法:1:综合在一块写;2:分开写
综合在一块写(不推荐)
分开写(推荐,构成的电路会更简便)
即最终的module代码:
这个图试后续运行后发现代码无法实现功能反回来查找问题,各种语句使用的都是“<=”,否则会有问题。
<= :非阻塞赋值
= : 阻塞赋值
阻塞赋值 =
阻塞赋值是顺序执行的。在一个begin - end块中,多条阻塞赋值语句按照顺序依次执行,下一条语句会等待当前阻塞赋值语句执行完成后才开始执行。例如:
always @(posedge clk) begin
a = b;
c = a;
end
当clk上升沿到来时,先执行a = b,将b的值赋给a,然后执行c = a,此时a已经更新为b的值,所以c得到的是更新后的a(也就是b的值)。
应用场景:
常用于组合逻辑电路建模,特别是在需要按照特定顺序进行计算的组合逻辑中。例如,在实现一个简单的算术逻辑单元(ALU)的组合逻辑部分时,按照运算优先级进行计算就可以使用阻塞赋值。
还用于initial块中初始化变量,因为initial块主要是顺序执行的初始化代码,例如:
initial begin
reg_a = 0;
reg_b = 1;
end
对仿真的影响:
在仿真过程中,由于阻塞赋值是顺序执行的,所以赋值语句的执行顺序会直接影响变量的值和仿真结果。如果赋值顺序不当,可能会导致不符合预期的结果。
例如,如果在组合逻辑中有相互依赖的阻塞赋值,可能会出现竞争冒险(race condition)现象,导致输出出现毛刺(glitch)。
非阻塞赋值 <=
非阻塞赋值是并发执行的。在一个always块中,所有非阻塞赋值语句在同一时刻开始计算表达式的值,但赋值操作会在当前时间步(time step)结束时同时更新目标变量。例如:
always @(posedge clk) begin
a <= b;
c <= a;
end
当clk上升沿到来时,a <= b和c <= a这两个赋值语句会同时开始计算右侧表达式的值。此时计算c <= a时,a的值是更新前的值,等到这个always块执行结束后,a和c才会同时更新。所以c得到的是a的旧值。
应用场景:
主要用于对时序逻辑电路进行建模,特别是在多个寄存器之间需要传递数据,并且这些寄存器的更新应该在时钟沿触发后同时进行的情况。例如,在一个简单的移位寄存器设计中:
always @(posedge clk) begin
reg1 <= in_data;
reg2 <= reg1;
reg3 <= reg2;
end
对仿真的影响:
非阻塞赋值在仿真时会在每个时间步结束时更新所有被赋值的变量,这样可以更好地模拟实际硬件中的寄存器等时序元件的行为。
有助于避免在时序逻辑建模中因为赋值顺序不当而产生的错误,但如果在组合逻辑中错误地使用非阻塞赋值,也可能会导致意想不到的结果,因为组合逻辑通常期望是立即更新输出的。
使用 = ,直接结果是后续仿真的时候高电平只持续了1个周期,即20ns就又变为低电平
使用<=,则可以实现500.00002ms一反转
为什么是500.000002ms,因为计数是从0开始,计数到25000000时候多计数了一个周期,即20ns,这里需要把计数改为24999999就OK
用到的语法
always@(posedge Clk or negedge Reset_n)//当时钟(Clk)的上升沿或者复位(Reset_n)的下降沿时候,这个代码块工作,其他时候不工作。
counter <= 0;//counter清零,<=是非阻塞赋值的意思
counter=counter + 1'd1;//counter自加1
2、分析综合
3、仿真模拟
最终在module为以下情况下,进行仿真
得出下图:即高低电平切换时间为500ms
查看电路
4、 IO设定
按键
LED灯
时钟引脚为Y18
5、生成程序、板级调试
略
2024.12.23
五、8位跑马灯(流水灯)设计
需掌握知识
- 如何进行位操作,拼接
- 如何实现module中延时触发的条件,通过时钟信号进行判定,通过counter计数进行准确的定时
- 代码中上升沿和下降沿的判断, posedge 、negedge
- TB中,如何初始化测试平台的配置,通过initial和always进行初始化配置操作
1、设计输入
clk作为一个内部时钟信号,通过外部提供的时钟信号来进行计数器的触发+1
而counter作为加法器的数据记录,在到设定记录数据时候,进行动作的触发,这里指Led灯的跑马状态
实现效果的两种方案,最终的效果是一样的
方案一
- 方案一
通过1的不断移位来进行操作,但需要注意的是,移动到最左端需要有个判断语句,在这次移位完成后将1回复到最右端 - 方案二
通过位拼接的方式,将上一次led的输出数据的第8位即Led[7],移动到下次Led输出数据的第一位,即
Led <= {Led[6:0],Led[7]}
这样操作就实现了首尾循环,实现了跑马灯效果
代码编写完成后进行分析综合,即通俗的话来说就是编译一下(个人理解,有错请指正)
2、仿真模拟
需编译仿真平台的代码
- 定义测试平台的时间尺度
- 定义一个测试平台
- 定义测试平台的端口,标注好reg型(输入)和wire型(输出)
- 将模块放置到测试平台上
- 将模块中的端口与测试平台的端口互相连接
- 定义测试平台的测试动作(给各信号端口激励信号并延时)
- end
代码编写完成后进行分析综合
当分析综合完成后,发现没有什么错误,那么就可以进行当前测试平台的仿真模拟了
仿真结果如下,5ms作为一个灯的停留周期,再运行到下一个灯上
4、IO分配设定
根据手册进行IO分配
略
3、生成代码,板级调试
仿真可以实现我们的要求后,就可以进行下一步,此步骤参考前面几个工程的操作,是通用的,此处不再赘述
略
2024.12.25
六、在module设计中引用其他module实现跑马灯
同第五块内容,采用加法计数器作为底层module,这个设计的不同就在于另接一个3-8译码器实现跑马灯的灯的驱动
需掌握知识
- 主要语法是在module设计中,引用另外一个module的操作
- 底层模块驱动的引脚,在顶层模块定义中均为wire型,不能是reg型,如后面代码中,Led输出是由3-8译码器的out驱动,这里out就是底层,而led就是顶层,所以在顶层定义中,将Led定义为
output [7:0]Led ; //wire此处可以省略不写
output wire[7:0]Led ; //这个写法也正确
1、设计输入
将之前设计的3-8译码器的source源代码文件添加到本工程中的source文件中
注意路径,参考该路径找到对应的文件地址进行添加
同时在Vivado的添加source选项中添加decoder_3_8的source
其实可以直接在decoder的工程中调用,但是为了方便整理,我们将同一个工程中用到的文件放到一起。
代码中,前面的counter计数还是和之前相同
后面增加了counter2,就是为了和3-8译码器的输入端子相接
这里引入的module同 Test_Bench文件中的引入方式
2、模拟仿真
TB平台上,接run2的接口,就可以实现这个module的仿真
最终仿真结果如下
查看设计的电路
后面过程省略
2024.12.25
使用参数进行时间定义,parameter
在定义时间参数时候,每个if语句中都需要将时间值,即24999999写一次,这样就导致可能某些时候忘记修改其中一个,导致实际程序和预期不符合,这个时候,需要用到
parameter MCNT = 25'd24999999;
parameter进行参数定义,这样在if语句中只用写入MCNT就可代指24999999,修改的时候只需要MCNT对应的赋值就可以同时运用到之后的程序中。
参数的重新定义,defparam、模块直接重定义
在tb平台上,我们想要更快的仿真,如实际上代码上我们定义的是延时500ms,而500ms在仿真上,要跑很久(10s左右),才能跑出来。所以我们在tb平台上,想要把例华好的模块进行参数的重新定义,而且该定义不能影响正常烧录到板子中的程序,此时我们选择在tb平台使用:
defparam led_run_test.MCNT = 25'd24999 ;
这样在仿真时候,仿真就会采用MCNT参数未24999进行快速仿真
教学视频上有说该种写法错误,defparam不能再tb代码中使用,但我这里验证是可以使用的,包括也可以正常仿真,有问题的话自己多尝试下
另一种写法,在模块放到TB中,先不进行贴标签,先修改参数,具体如下,
如图片红字所示过程,进行参数修改。这样经验证可以正常按照预期仿真,且不影响实际下载如板子的程序。
2024.12.30
七、参数化设计实现模块的重用
需掌握知识
- 计时参数的计算
- 参数的重定义的使用(defparam)
- 如何例化一个module
- 在一个module中,如何例化多个module并对其分别进行操作
- tb平台中如何初始化一个端口
- tb平台中如何让一个端口在后台一直进行某个逻辑运行操作
- 综合时候对综合顺序的要求是什么
- 约束文件的创建
8个LED灯不同频率闪烁
50MHz为板子的频率,则执行一步的时间为1/50M=20ns,即板子中的Clk每翻转一次需要20ns
通过计数Clk的翻转次数进行时间统计,如
需要一个周期为1s的时间,即需要500ms翻转一次,一个周期1s翻转2次,时间为:
500ms/20ns = 25000000 ,则需要进行24999999次Clk计数(前文有讲为什么减1)
0.5s:
250ms/20ns = 12500000,则需要进行12499999次Clk计数
0.4s:
200ms/20ns = 10000000,则需要进行9999999次Clk计数
0.3s:
150ms/20ns = 7500000,则需要进行7499999次Clk计数
0.2s:
100ms/20ns = 5000000,则需要进行4999999次Clk计数
0.1s:
50ms/20ns = 2500000,则需要进行2499999次Clk计数
首先定义一个单个led闪烁的程序
在单个闪烁程序上进一步进行封装,封装为8个led闪烁的module
重新定义一个module,在这个module中例化8个上一个控制单个led闪烁的module,并重新定义计时参数(defparam)
这样就确保在一个module中可进行8个led不同延时闪烁的配置
在tb上进行模拟验证
以上代码全部完成后,进行综合,确认代码中有无错误
注意
需将编写的代码从最底层开始综合,如果从最高层开始综合,就可能出现某些代码未被综合而报错的情况。
按照以下顺序综合
完成以上操作后,进行仿真,通过仿真的时序图,看出我们期望的结果实现了
通过约束文件的创建进行引脚定义
首先通过Add Sources进行constraints文件的新增,
新增后通过增加以下配置命令就可进行IO端口的配置与绑定
以上的配置命令也可以根据前几节讲的,通过以下进行配置
代码的整体编译与烧录,板级调试
以上操作完成后直接点击红框,则可完成所有操作
之后进行硬件烧录,之前文章中有提到过
可查看 第二节→程序下载
2025.1.2
八、从计数器到可控线性序列机
需要掌握的知识
- 什么是线性序列机
- Ctrl输入端口位操作
- 时间计时参数的计算
- 时间计时位数的计算
- 单个时间段内同时操作多个端口
- 多个时间段周期循环如何操作,两个counter计数
- 本节课就是要让初学者意识到,计数器在可控线性序列机中的重要性
- 如何将参数放到模拟波形中可见
课程目标
LED灯亮0.5s,灭0.5s状态循环
思路:
设计计数器,计数周期位1s
默认亮状态,计数到0.5s时候拉低(亮0.5s),计数到1s时候拉高(灭0.5s)
以50MHz的板子为例,20ns为一Clk变化周期
确认计数器的位数 reg[25:0] count
首先要确保led初始状态为拉高状态:initial Led = 1 ;
count == 25000000-1 时候进行拉低
count == 50000000-1 时候进行拉高
TB代码
仿真模拟
通过参数化设计,进行重新编写
这样在模拟的时候就只用修改TB的代码,不用再修改module中的代码了
2025.1.3
LED灯亮0.25s、灭0.5s、亮0.75s、灭1s 状态循环
module代码,在同一个项目中时候记得更改module名称,否则名称重复编译不通过
TB代码,记得更改计时参数,这样仿真能节省时间
具体仿真如下:因为周期为25ms,所以仿真比较快
LED亮灭未知,8个周期,0.25为变化周期,用户随机指定如何亮灭
8个0.25s为一个循环,即一个周期需要2s,计数即2000000000/20=100000000,其中counter有效位为27位
如何指定亮灭模式?
需要有个端口进行输入,来指定这8个0.25周期中,哪一段是亮,那一段是灭。考虑可通过8个拨码开关进行每个周期的指定。
module代码:
else if太多不太好,所以通过case进行条件判断
TB平台
最终仿真
计数器相当于时间轴的横坐标,我们可以在计数器这个技术周期内,进行任何操作。
LED亮灭未知,8个变化状态一个循环,单个变化状态时间未知,用户随机指定如何亮灭
根据题目进行分析,此时用parameter不行,这个不能随时改变,需要定义输入端口,输入端口可以根据用户需求进行改变
parameter定义出的是常量,是实际数值,在编译时候会换算成数值在代码中,所以该定义编译后就是确定的数值,不能再进行变化
思路,现在以单个状态周期位为之间基准值,
定义counter1,去计数这个时间基准值
定义counter2,去计数这时间基准值运行多少次,按照题目规定,每运行8次为完整一周期
让多个led按照设置的模式各自在一个周期中进行亮灭
根据题目进行分析,具体的要求与上个题目基本一样,知识需要根据led的数量添加对应的ctrl,以控制不同的led亮灭模式
同时控制多个信号在什么应用中最普遍?
比如SPI,IIC等这样的总线中,
等等这样的一些总线里面,它有一个最基本的时钟单位,然后控制多个信号,比如说SPI的CS、SCLK、MOSI、MISO,这些同时变化,就可以对应到我们这个程序中的LED0、LED1、LED2。
这个程序不是说要控制LED灯,而是要熟悉这种 控制方式,便于后期其他功能的应用
2025.1.6
思考题:每隔10ms执行一次完整的变化周期
思路:
- 复制之前的代码,需要在其中新增一counter,专门计数10ms这个延时时间,每完成这个计时进行一次状态周期的触发
- 在状态周期运行的过程中,这个counter停止计数,并清零。
- 当led状态周期结束后,触发counter重新计数。
- 等待counter计数满足10ms后,再次触发状态周期进行再一次的循环
自己的思路代码:
module counter_led_5(
Clk,
Reset_n,
Ctrl,
Time,
Led
);
input Clk;
input Reset_n;
input [7:0] Ctrl;//8位输入
input [31:0]Time ;
output reg Led;
reg flag = 0;//0为10ms无动作计时,1为led动作过程
reg [31:0]counter ; //每短单独led变化周期计时
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter <= 0 ;
else if(counter == Time - 1 )
counter <= 0 ;
else if(flag == 1)
counter <= counter + 1'b1 ;
reg [3:0]counter2 ;//led运行标志位计数
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter2 <= 0 ;
else if(counter == Time - 1 ) //当计数满足Time这个时间后,couter+1
counter2 <= counter2 + 1'b1 ;
else if (counter2 == 8 )//当完成整个led运行周期时候,
begin
counter2 = 0 ;//及时清零运动段数量标志
flag <= 0 ;//开启10ms空白段计时
end
reg [18:0]counter3;//空白10ms计时
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter3 <= 0 ;
else if(counter3 == 500000 - 1 )//计时10ms
begin
counter3 <= 0 ;
flag <= 1 ;//当计时满足10ms,led才能动作
end
else if(flag == 0) //当重新计时标志位为0时候,才嗯那个进行10ms计时
counter3 <= counter3 + 1'b1 ;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0 ;
else if (flag == 0)
Led <=8'b00000000;
else case(counter2)
0:Led <= Ctrl[0] ;
1:Led <= Ctrl[1] ;
2:Led <= Ctrl[2] ;
3:Led <= Ctrl[3] ;
4:Led <= Ctrl[4] ;
5:Led <= Ctrl[5] ;
6:Led <= Ctrl[6] ;
7:Led <= Ctrl[7] ;
default:Led <= Led ;
endcase
endmodule
TB代码:
仿真结果
老师的思路
根据图示,自己理解的思路是错误的,是在10ms内进行一个动作周期,而不是动作周期结束后再进行10ms延时。
根据老师的思路,写如下代码
新增EN ,新增counter0
module counter_led_6(
Clk,
Reset_n,
Ctrl,
Time,
Led
);
input Clk;
input Reset_n;
input [7:0] Ctrl;//8位输入
input [31:0]Time ;
output reg Led;
//10ms计时器
reg [18:0]counter0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter0 <= 0 ;
else if(counter0 == 500000-1)
counter0 <= 0 ;
else
counter0 <= counter0 + 1'b1 ;
reg EN;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
EN <= 0 ;
else if(counter0 == 0)
EN <= 1 ;
else if(counter2 == 7)
EN <= 0 ;
reg [31:0]counter ;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter <= 0 ;
else if (EN) begin
if(counter == Time - 1 )
counter <= 0 ;
else
counter <= counter + 1'b1 ;
end
else
counter <= 0 ;
reg [2:0]counter2 ;//计数3位,可进行0-7循环计数
always@(posedge Clk or negedge Reset_n)
if(!Reset_n) //有复位,则清零计数
counter2 <= 0 ;
else if(EN)begin
if(counter == Time - 1 )
counter2 <= counter2 + 1'b1 ;
end
else
counter2 <= 0 ;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 0 ;
else case(counter2)
0:Led <= Ctrl[0] ;
1:Led <= Ctrl[1] ;
2:Led <= Ctrl[2] ;
3:Led <= Ctrl[3] ;
4:Led <= Ctrl[4] ;
5:Led <= Ctrl[5] ;
6:Led <= Ctrl[6] ;
7:Led <= Ctrl[7] ;
default:Led <= Led ;
endcase
endmodule
仿真TB:
2025.01.08
如何将代码中的参数放置到模拟波形参数中可见