目录

1  实验操作流程

1.1 ModelSim仿真

1.2 引脚配置

1.3 程序下载

2  键控LED灯

2.1 实验要求

2.2 程序设计

2.3 软件仿真

2.4 实验结果

3  跑马灯

3.1 实验要求

3.2 程序设计

3.3 软件仿真

3.4 实验结果

4  序列检测器

4.1 实验要求

4.2 程序设计

4.3 软件仿真

4.4 实验结果

5  变速数字时钟

5.1 实验要求

5.2 程序设计

5.3 软件仿真

6  实验心得



 实验操作流程

        在本章中,将简单介绍实验所用到的软件操作流程,以供同学们参考。个人水平有限,所提供的方法不一定是最正确的、最便捷的,如有疏漏之处敬请指正。

1.1 ModelSim仿真

        由于软环境中没有时钟、按键等激励输入,我们无法对设计的电路进行功能性测试。因此需要手动编写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 Ⅱ中重新编译程序,否则重启仿真后执行的仍然是修改前的程序。

1.2 引脚配置

        当程序编写完成,且仿真结果无误后,便可以进行引脚配置,为后续将程序下载到开发板上运行做最后的准备。

        首先,点击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 引脚分配

1.3 程序下载

        程序经上述操作完成,且编译无误后,使用USB-Blaster将开发板正确连接到计算机。点击图1.7上方的“Programmer”键,弹出Programmer窗口。点击“Hardware Setup”,选择“USB-Blaster”。点击“Add File”,将工程目录下\output_files文件夹中自动输出的.sof文件添加进来。最后点击“Start”,将程序下载到FPGA开发板。

 图1.7 程序下载

2  键控LED灯

2.1 实验要求

        设计一个键控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时,第八个灯亮。

2.2 程序设计

        本实验的程序为单个文件。以下为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  

2.3 软件仿真

        编写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.4 实验结果

        正确分配引脚后,将程序下载到开发板中,运行效果如图2.2所示。

图2.2 键控LED灯运行效果

3  跑马灯

3.1 实验要求

        设计一个双向跑马灯,具体要求如下:

        (1)当按键KEY=1时,8路LED灯从左往右依次亮灯,每次只亮一个灯,亮灯间隔时间1s;

        (2)当按键KEY=0时,8路LED灯从右往左依次亮灯,每次只亮一个灯,亮灯间隔时间1s;

        (3)当复位信号nRST为0时,恢复初始状态。

3.2 程序设计

        本实验的程序分为三个文件。以下为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 设置顶层文件

3.3 软件仿真

        跑马灯程序在FPGA开发板上运行时,闪烁时间为1s,以便肉眼能够清晰的观察到运行效果。但是在仿真实验过程中发现,当时间尺度过长时,无法得到正确的仿真结果。目前我仍然没有解决这个问题,临时的处理方法为:在仿真时使用较小的分频系数,即修改divide_N.v文件第十行N的值为500,此时跑马灯闪烁周期为10us,仿真结果正确。同时,欢迎有遇到类似情况的同学与我交流。

        编写Test Bench文件代码如下。初始按键值为1,在程序复位后的第40us,按键值变为0,跑马灯反向运行。

        运行ModelSim,仿真结果如下图所示。

 图3.2 跑马灯ModelSim仿真波形图

3.4 实验结果

        正确分配引脚后,将程序下载到开发板中,运行效果如图3.3所示。跑马灯运行方向由波动开关控制,闪烁周期为1s。

 图3.3 跑马灯运行效果

4  序列检测器

4.1 实验要求

        利用状态机设计一个序列检测器,实现以下功能:将一个指定序列从数字码流中识别出来。本实验要求设计一个“10010”序列的检测器。设X为数字码流的输入,Z为检测出标记输出,Z平时为高电平,一旦发现指定的序列10010,则变为低电平。

        由于码流难以产生,因此采用两个按键来模拟码流,设置两个按键,按键A和按键B,按键A按下代表输入了一个“1”,按键B按下代表输入了一个“0”,如果按键顺序为A-B-B-A-B,则代表输入了“10010”。

        另外,输出状态的改变可通过LED灯来指示。平时LED灯保持灭的状态,一旦检测到“10010”,LED灯亮。

4.2 程序设计

        本实验的程序分为三个文件。以下是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  

4.3 软件仿真

        按键消抖是毫秒级操作,在使用ModelSim进行仿真时,同样会出现类似于3.3节介绍的由于时间维度过大,导致仿真结果错误的问题。

        考虑到仿真时,按键的输入信号是不含任何抖动的,不需要进行消抖。因此,这里跳过了按键消抖模块,仅对状态机模块进行仿真验证。具体操作方法为:将state_machine.v设置为顶层模块,在编写Test Bench文件时,只对state_machine模块进行定义,具体代码如下。

        运行ModelSim,仿真结果如下图所示。

 图4.1 序列检测器ModelSim仿真波形图

4.4 实验结果

        正确分配引脚后,将程序下载到开发板中,运行效果如图4.2所示。两个按键分别代表“1”和“0”,输入时,LED灯会依次点亮。若输入正确的密码序列“10010”,密码正确指示灯点亮,如果过程中任意一位输入错误,LED灯会全部熄灭。

 图4.2 序列检测器运行效果

5  变速数字时钟

5.1 实验要求

        设计一个变速数字时钟,要求数字时钟的速度有三个档位:第一个档位为标准数字时钟,每隔1S秒计数器加1;第二个档位为快速数字时钟,每隔0.1S秒计数器加1;第三个档位为超快速数字时钟,每隔0.01S秒计数器加1。三个档位可用按键切换。

        除此之外,时钟具备按键清零功能;具有整点报时功能,即在59分59秒时给出指示信息(LED灯亮),持续时间为1s/0.1s/0.01s,指示信号结束的时刻恰好为正点时刻。

5.2 程序设计

        本实验的程序为单个文件。以下为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  

5.3 软件仿真

        编写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仿真波形图

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐