赞
踩
在本章中,将简单介绍实验所用到的软件操作流程,以供同学们参考。个人水平有限,所提供的方法不一定是最正确的、最便捷的,如有疏漏之处敬请指正。
由于软环境中没有时钟、按键等激励输入,我们无法对设计的电路进行功能性测试。因此需要手动编写Test Bench文件,以根据需求测试使用Verilog设计电路的功能、性能与预期是否相符。
Test Bench文件可以自行编写,也可以在Quartus Ⅱ菜单栏Processing→Start中选择Start Test Bench Template Writer,让Quartus Ⅱ帮助我们生成Test Bench模板。自动生成的文件位于工程目录下的:simulation\ModelSim\*.vt。在模板的基础上,根据实际需要进行改写。
编写完Test Bench文件后,需要修改工程的Test Bench配置。在Quartus Ⅱ菜单栏选择Assignments→Settings,打开如图1.2所示窗口。进入“Simulation”栏,选择仿真工具为“ModelSim-Altera”(注意不要选择成“ModelSim”)。点击“Test Benchs”按钮,在弹出的对话框中添加.vt文件,并输入正确的文件名、顶层模块名。
图1.2 修改工程Test Bench配置
为了能够在Quartus Ⅱ中直接调用ModelSim运行仿真,需要在Quartus Ⅱ菜单栏Tools→Options→general→EDA tool options中正确配置ModelSim运行路径,如图1.3所示。具体路径根据每个人安装时的位置而不同,只要确保路径的末级目录为\win32aloem即可。
图1.3 配置ModelSim路径
设置完成后,点击Quartus Ⅱ上方工具栏中的RTL Simulation(如图1.4所示)启动ModelSim,仿真将在程序启动后自动运行。
图1.4 启动仿真按钮
但是这会存在一个问题:在ModelSim启动后,仿真便会自动开始运行,必须手动点击暂停,不便于我们观察波形。解决方法是在Test Bench中initial块执行的第一句添加代码“$stop”,如下所示。
- initial
- begin
- $stop;
- //Write your code here
- //……
- end
这样,当ModelSim运行时,会在一开始就执行$stop指令暂停仿真,然后,我们就可以通过图1.5上方红框处的按钮,根据实际需要设置仿真时间,进行单步仿真。
如果在调试过程中发现仿真结果有误,需要修改程序重新进行仿真,可以点击图1.5下方的transcript栏,用键盘的上箭头键浏览历史命令,找到“do *.do”命令按回车执行,就可以直接重启仿真,而不需要重启ModelSim。
图1.5 单步仿真与重启仿真
在使用上述方法重启仿真时请务必注意:如果修改了Test Bench程序,必须保存.vt文件;如果修改了Verilog程序,则必须在Quartus Ⅱ中重新编译程序,否则重启仿真后执行的仍然是修改前的程序。
当程序编写完成,且仿真结果无误后,便可以进行引脚配置,为后续将程序下载到开发板上运行做最后的准备。
首先,点击Quartus Ⅱ菜单栏中的Assignments→Device,在图1.6所示窗口中选择正确的FPGA芯片系列、芯片型号(以手中的开发板为准)。
图1.6 FPGA型号设置
然后点击上图中的“Device and Pin Options”,打开图1.7所示窗口。按图中的方式配置未使用引脚和复用引脚。
图1.7 配置未使用引脚和复用引脚
完成上述操作后,编译程序。待程序编译完成后,在Quartus Ⅱ菜单栏选择Assignments→Pin Planner,打开图1.6所示窗口。顶层模块中定义的所有输入输出引脚都会被罗列在下方(如果在打开Pin Planner前没有编译程序,列出的引脚可能会有误),根据开发板提供的手册,在图中红框标识处填入正确的引脚,按回车键确认输入。
引脚分配完成后,必须在Quartus Ⅱ中再次编译程序,必须在Quartus Ⅱ中再次编译程序,必须在Quartus Ⅱ中再次编译程序,才能使引脚配置生效。
图1.6 引脚分配
程序经上述操作完成,且编译无误后,使用USB-Blaster将开发板正确连接到计算机。点击图1.7上方的“Programmer”键,弹出Programmer窗口。点击“Hardware Setup”,选择“USB-Blaster”。点击“Add File”,将工程目录下\output_files文件夹中自动输出的.sof文件添加进来。最后点击“Start”,将程序下载到FPGA开发板。
图1.7 程序下载
设计一个键控LED灯,要求具有以下功能:
(1)按键KEY状态为000时,第一个灯亮;
(2)按键KEY状态为001时,第二个灯亮;
(3)按键KEY状态为010时,第三个灯亮;
(4)按键KEY状态为011时,第四个灯亮;
(5)按键KEY状态为100时,第五个灯亮;
(6)按键KEY状态为101时,第六个灯亮;
(7)按键KEY状态为110时,第七个灯亮;
(8)按键KEY状态为111时,第八个灯亮。
本实验的程序为单个文件。以下为LED.v文件代码,功能是利用case语句根据key的输入选择不同的led控制位输出。
- /*LED.V*/
- module LED(key,led);
-
- input [2:0]key;
- output [7:0]led;
-
- reg [7:0]led = 8'd0;
-
- always@(key)
- begin
- case(key)
- 3'b111:led = 8'b10000000;
- 3'b110:led = 8'b01000000;
- 3'b101:led = 8'b00100000;
- 3'b100:led = 8'b00010000;
- 3'b011:led = 8'b00001000;
- 3'b010:led = 8'b00000100;
- 3'b001:led = 8'b00000010;
- 3'b000:led = 8'b00000001;
- default:led = 8'd0;
- endcase
- end
-
- endmodule
编写Test Bench文件代码如下。初始按键值为3'b0,按键值每100ns加一,以模拟被顺序按下的情景。
- `timescale 1 ns/ 100 ps
-
- module led_tb();
-
- reg eachvec;
- reg [2:0] key_test;
- wire [7:0] led_test;
-
- LED i1 (
- .key(key_test),
- .led(led_test));
-
- initial
- begin
- $stop;
- key_test = 3'b0;
- end
-
- always
- #100 key_test = key_test + 3'b1;
- endmodule
运行ModelSim,仿真结果如下图所示。
图2.1 键控LED灯ModelSim仿真波形图
正确分配引脚后,将程序下载到开发板中,运行效果如图2.2所示。
图2.2 键控LED灯运行效果
设计一个双向跑马灯,具体要求如下:
(1)当按键KEY=1时,8路LED灯从左往右依次亮灯,每次只亮一个灯,亮灯间隔时间1s;
(2)当按键KEY=0时,8路LED灯从右往左依次亮灯,每次只亮一个灯,亮灯间隔时间1s;
(3)当复位信号nRST为0时,恢复初始状态。
本实验的程序分为三个文件。以下为divide_N.v文件代码,功能是对FPGA的50MHz时钟进行分频。
- /*divide_N.v*/
- module divide_N(clk_in,clk_out,rst);
-
- input clk_in;
- input rst;
- output clk_out;
-
- reg clk_out;
- reg [25:0] cnt;
- parameter N = 50000000;//仿真时填入500,否则会出错
-
- always @(posedge clk_in or negedge rst)
- begin
- if(!rst)
- cnt <= 6'd0;
- else if(cnt == N-1)
- cnt <= 6'd0;
- else
- cnt <= cnt + 1'b1;
- end
-
- always @(posedge clk_in or negedge rst)
- begin
- if(!rst)
- clk_out <= 0;
- else if(cnt == N-1)
- clk_out <= 1'b1;
- else
- clk_out <= 1'b0;
- end
-
- endmodule
以下为ctr_led.v文件代码,功能是控制引脚输出,实现跑马灯效果。其中,第12~15行是通过位拼接运算符实现了八位的循环位移,从而使LED灯依次循环点亮。
以下为top.v文件代码,是本工程的顶层模块,功能是将上面两个文件中的模块连接起来。
- /*top.v*/
- module top(clk,rst,key,led);
-
- input clk;
- input rst;
- input key;
- output [7:0]led;
-
- wire clk_div;
-
- divide_N u1(
- .clk_in(clk),
- .clk_out(clk_div),
- .rst(rst));
-
- ctr_led u2(
- .clk_in(clk_div),
- .rst(rst),
- .key(key),
- .led(led));
-
- endmodule
在多文件工程中,需要手动定义顶层文件,从而让编译器知道哪个是程序的顶层模块。设置方法为:右键点击编写的顶层文件(这里以top.v为例),点击“Set as Top-Level Entity”,如图3.1所示。
图3.1 设置顶层文件
跑马灯程序在FPGA开发板上运行时,闪烁时间为1s,以便肉眼能够清晰的观察到运行效果。但是在仿真实验过程中发现,当时间尺度过长时,无法得到正确的仿真结果。目前我仍然没有解决这个问题,临时的处理方法为:在仿真时使用较小的分频系数,即修改divide_N.v文件第十行N的值为500,此时跑马灯闪烁周期为10us,仿真结果正确。同时,欢迎有遇到类似情况的同学与我交流。
编写Test Bench文件代码如下。初始按键值为1,在程序复位后的第40us,按键值变为0,跑马灯反向运行。
运行ModelSim,仿真结果如下图所示。
图3.2 跑马灯ModelSim仿真波形图
正确分配引脚后,将程序下载到开发板中,运行效果如图3.3所示。跑马灯运行方向由波动开关控制,闪烁周期为1s。
图3.3 跑马灯运行效果
利用状态机设计一个序列检测器,实现以下功能:将一个指定序列从数字码流中识别出来。本实验要求设计一个“10010”序列的检测器。设X为数字码流的输入,Z为检测出标记输出,Z平时为高电平,一旦发现指定的序列10010,则变为低电平。
由于码流难以产生,因此采用两个按键来模拟码流,设置两个按键,按键A和按键B,按键A按下代表输入了一个“1”,按键B按下代表输入了一个“0”,如果按键顺序为A-B-B-A-B,则代表输入了“10010”。
另外,输出状态的改变可通过LED灯来指示。平时LED灯保持灭的状态,一旦检测到“10010”,LED灯亮。
本实验的程序分为三个文件。以下是shake_handle.v文件代码,功能是对FPGA的50MHz时钟进行分频,然后通过连续三次判断按键状态,实现按键消抖。
- /*shake_handle.v*/
- module shake_handle(
- input clk,
- input key,
- input nRST,
- output clk_5KHz,
- output reg key_o);
-
- reg [20:0]cnt;
- reg key_d1;
- reg key_d2;
-
- //10000分频,得到一个5kHz时钟
- always @(posedge clk or negedge nRST)
- begin
- if(!nRST)
- cnt <= 21'd0;
- else
- begin
- if(cnt<21'd9999)
- cnt <= cnt + 1'b1;
- else
- cnt <= 21'd0;
- end
- end
- assign clk_5KHz = (cnt<21'd5000)? 1'b1:1'b0;
-
- //按键消抖
- always @(posedge clk_5KHz or negedge nRST)
- begin
- if(!nRST)
- begin
- key_d1 <= 0;
- key_d2 <= 0;
- end
- else
- begin
- key_d1 <= key;
- key_d2 <= key_d1;
- end
- end
-
- always @(posedge clk_5KHz or negedge nRST)
- begin
- if(!nRST)
- key_o <= 1'b0;
- else
- begin
- if(key_d1 & key_d2 & key)//只有连续出现3个1,才认为是1
- key_o <= 1'b1;
- else if(!key_d1 & !key_d2 & !key)//只有连续出现3个0,才认为是0
- key_o <= 1'b0;
- end
- end
- endmodule
以下为state_machine.v文件代码,功能是通过Verilog程序构建D触发器实现按键的下降沿检测,同时利用有限状态机实现序列检测功能。
- /*state_machine.v*/
- module state_machine(
- input clk,
- input nRST,
- input key_high,
- input key_low,
- output reg [4:0]state,
- output reg flag);
-
- //下面两个信号是在key的基础上延时一个时钟周期
- reg key_high_d;
- reg key_low_d;
-
- always @(posedge clk)
- begin
- key_high_d <= key_high;
- end
-
- always @(posedge clk)
- begin
- key_low_d <= key_low;
- end
-
- parameter
- S0 = 5'b00000,
- S1 = 5'b00001,
- S2 = 5'b00010,
- S3 = 5'b00100,
- S4 = 5'b01000,
- S5 = 5'b10000;
-
- //状态机
- always @(posedge clk or negedge nRST)
- begin
- if(!nRST)
- begin
- flag <= 0;//指示信号复位
- state <= S0;//状态变量复位
- end
- else
- case(state)
- S0:
- begin
- if(key_high_d & !key_high)//1
- state <= S1;
- else
- state<= state;
- end
- S1:
- begin
- if(key_high_d & !key_high)
- state <= S0;
- else if(key_low_d & !key_low)//0
- state<= S2;
- else
- state<= state;
- end
- S2:
- begin
- if(key_high_d & !key_high)
- state <= S0;
- else if(key_low_d & !key_low) //0
- state<= S3;
- else
- state<= state;
- end
- S3:
- begin
- if(key_high_d & !key_high)//1
- state <= S4;
- else if(key_low_d & !key_low)
- state<= S0;
- else
- state<= state;
- end
- S4:
- begin
- if(key_high_d & !key_high)
- state <= S0;
- else if(key_low_d & !key_low) //0
- state<= S5;
- else
- state<= state;
- end
- S5:
- begin
- flag <= 1'b1;
- state <= state;//死循环,除非复位,才会跳出循环
- end
- default:state<=S0;
- endcase
- end
-
- endmodule
以下为top.v文件代码,是本工程的顶层模块,功能是将上面两个文件中的模块连接起来。
- /*top.v*/
- module top(
- clk_50MHz,
- key_A,
- key_B,
- nRST,
- state,
- LED);
-
- input wire clk_50MHz;
- input wire key_A;
- input wire key_B;
- input wire nRST;
- output wire LED;
- output wire [4:0]state;
-
- wire clk_5KHz;
- wire key1;
- wire key2;
-
- shake_handle u1(
- .clk(clk_50MHz),
- .key(key_A),
- .nRST(nRST),
- .clk_5KHz(clk_5KHz),
- .key_o(key1));
-
- shake_handle u2(
- .clk(clk_50MHz),
- .key(key_B),
- .nRST(nRST),
- .key_o(key2));
-
- state_machine u3(
- .clk(clk_5KHz),
- .nRST(nRST),
- .key_high(key1),
- .key_low(key2),
- .state(state),
- .flag(LED));
-
- endmodule
按键消抖是毫秒级操作,在使用ModelSim进行仿真时,同样会出现类似于3.3节介绍的由于时间维度过大,导致仿真结果错误的问题。
考虑到仿真时,按键的输入信号是不含任何抖动的,不需要进行消抖。因此,这里跳过了按键消抖模块,仅对状态机模块进行仿真验证。具体操作方法为:将state_machine.v设置为顶层模块,在编写Test Bench文件时,只对state_machine模块进行定义,具体代码如下。
运行ModelSim,仿真结果如下图所示。
图4.1 序列检测器ModelSim仿真波形图
正确分配引脚后,将程序下载到开发板中,运行效果如图4.2所示。两个按键分别代表“1”和“0”,输入时,LED灯会依次点亮。若输入正确的密码序列“10010”,密码正确指示灯点亮,如果过程中任意一位输入错误,LED灯会全部熄灭。
图4.2 序列检测器运行效果
设计一个变速数字时钟,要求数字时钟的速度有三个档位:第一个档位为标准数字时钟,每隔1S秒计数器加1;第二个档位为快速数字时钟,每隔0.1S秒计数器加1;第三个档位为超快速数字时钟,每隔0.01S秒计数器加1。三个档位可用按键切换。
除此之外,时钟具备按键清零功能;具有整点报时功能,即在59分59秒时给出指示信息(LED灯亮),持续时间为1s/0.1s/0.01s,指示信号结束的时刻恰好为正点时刻。
本实验的程序为单个文件。以下为clock.v文件代码,功能是实现时钟计时、进位,整点报时,数码管显示。
- /*clock.v*/
- module clock(CLK,nRST,key,seg,dig,led);
-
- input CLK,nRST;
- input [1:0]key;
- output [7:0]dig;
- output [6:0]seg;
- output led;
-
- reg led=1'b0;
- reg [7:0]dig,dis_data,num_data=3'd0;
- reg [6:0]seg;
- reg [31:0]count=32'd0;
- reg [5:0]shi=6'd0,fen=6'd0,miao=6'd0;
- reg [31:0]num;
-
- always @(key)//挡位调整
- begin
- if(key==2'd0)
- num<=32'd1000;//速度为1s
- else if(key==2'd1)
- num<=32'd100;//速度为0.1s
- else num<=32'd10;//速度为0.01s
- end
-
- //计时模块
- always @(posedge CLK or negedge nRST)
- begin
- if(!nRST)
- count<=32'd0;
- else if(count==num)
- count<=32'd0;
- else count<=count+1'b1;
- end
- always @(count)
- begin
- if(!nRST)
- miao<=6'd57;
- else if(count==num)
- if(miao==6'd59)
- miao<=6'd0;
- else miao<=miao+1'b1;
- end
- always @(miao)
- begin
- if(!nRST)
- fen<=6'd59;
- else if(miao==6'd00)
- if(fen==6'd59)
- fen<=6'd0;
- else fen<=fen+1'b1;
- end
- always @(fen)
- begin
- if(!nRST)
- shi<=6'd0;
- else if(fen==6'd00)
- if(shi==6'd23)
- shi<=6'd0;
- else shi<=shi+1'b1;
- end//该模块最后得到shi,fen,miao的输出
-
- //指示灯功能
- always @(miao)
- begin
- if(fen==6'd59)
- if(miao==6'd59)
- led<=1'b1;
- else led<=1'b0;
- else led<=1'b0;
- end
-
- //数码管显示模块
- always @(posedge CLK)
- begin
- if(num_data==5)
- num_data<=0;
- else num_data<=num_data+1'b1;
- end
-
- always @(posedge CLK)//1ms一次
- case(num_data)
- 3'd0:dis_data <= miao%6'd10;
- 3'd1:dis_data <= miao/6'd10;
- 3'd2:dis_data <= fen%6'd10;
- 3'd3:dis_data <= fen/6'd10;
- 3'd4:dis_data <= shi%6'd10;
- 3'd5:dis_data <= shi/6'd10;
- default:dis_data <= 6'd0;
- endcase
-
- always @(dis_data)
- case(dis_data )
- 4'd1:seg <= 7'b0000110; //’1’
- 4'd2:seg <= 7'b1011011; //’2’
- 4'd3:seg <= 7'b1001111; //’3’
- 4'd4:seg <= 7'b1100110; //’4’
- 4'd5:seg <= 7'b1101101; //’5’
- 4'd6:seg <= 7'b1111101; //’6’
- 4'd7:seg <= 7'b0000111; //’7’
- 4'd8:seg <= 7'b1111111; //’8’
- 4'd9:seg <= 7'b1101111; //’9’
- default:seg <= 7'b0111111; //’0’
- endcase
-
- always @(posedge CLK)
- case(num_data)
- 3'd0:dig <= 8'b0111_1111; //选择第1个数码管
- 3'd1:dig <= 8'b1011_1111; //选择第2个数码管
- 3'd2:dig <= 8'b1101_1111; //选择第3个数码管
- 3'd3:dig <= 8'b1110_1111; //选择第4个数码管
- 3'd4:dig <= 8'b1111_0111; //选择第5个数码管
- 3'd5:dig <= 8'b1111_1011; //选择第6个数码管
- 3'd6:dig <= 8'b1111_1101; //选择第7个数码管
- 3'd7:dig <= 8'b1111_1110; //选择第8个数码管
- default:dig <= 8'b1111_1111; //不选择
- endcase
-
- endmodule
编写Test Bench文件代码如下。初始按键值为2'b0,即挡位为正常速度。提供50MHz时钟信号以供时钟运行。由于仿真程序时间尺度为秒级,设置仿真精度为100us以提高仿真效率。
- `timescale 100 us/ 10 us
-
- module led1_vlg_tst();
-
- reg CLK;
- reg [1:0]key;
- reg nRST;
-
- wire [7:0]dig;
- wire led;
- wire [6:0]seg;
-
- led1 i1 (
- .CLK(CLK),
- .dig(dig),
- .key(key),
- .led(led),
- .nRST(nRST),
- .seg(seg));
-
- initial
- begin
- $stop;
- CLK=1'b0;
- nRST=1'b0;
- key=2'b0;
- #100 nRST=1'b1; //10个时钟周期
- end
-
- always
- #5 CLK=~CLK;
- endmodule
运行ModelSim,仿真结果如下图所示。
图5.1 变速数字时钟ModelSim仿真波形图
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。