以下内容为北邮2020级数电大实验实验报告,蜂鸣器模块没有报错但是时灵时不灵的,删去后别的代码都可以正常运行。整个报告缺少仿真代码和仿真图片(懒得粘贴了),会贴在个人发布的资源中。
北邮的学弟学妹们有问题欢迎联系qq:1041211873
目录
- 进一步掌握Verilog和Quartus II软件的使用
- 掌握状态机的工作原理和设计方法
- 计算机
- MAX EPM1270T144C5
简要设计思路为:
- 通过一个总体控制拨码开关SW6控制整个虚拟浴霸的开启与关闭,若处于关闭状态,则一切按键和功能都处于失效状态;若处于开启状态,则通过一个旗帜变量start记录浴霸从关到开的状态,我暂且称之为开启状态,在start = 1的情况下进行开机动画,此状态持续2s,此后start = 0,进入按键检测状态。
- 开启状态结束后,进入按键检测状态,此时设计一个切换变量switch,通过该变量的变化在对应case语句中进行对应的状态变化。简述为:按键按下-按键消抖产生脉冲-检测脉冲-switch变化-状态变化。由于题目中要求的是可以进行不同状态间的切换,以及通过同一个按钮的两次按下进行状态的开启与关闭,故而想到了一个解决方案:与switch的上一个状态比较,若不同则切换,若相同则关闭,并且进行相应延时(设计done,延时已完成变量,不过多赘述)等操作。流程设计图如下:
在本次设计中包含:顶层模块bath_heater,按键检测模块button,蜂鸣器模块sound,数码管模块segment,动画展示模块display,开机动画模块view_open,换气动画模块view1,风暖动画模块view2,强暖动画模块view3,干燥动画模块view4。
在该模块中进行对开机动画以及四个功能模块的例化,将对应变量分配给主模块输出管脚进行动画的显示,并且完成对灯的控制以及和数码管模块的连接。
在仿真图片中可以看到,检测到on为1之后,进行开机动画的显示,switch1010为待机状态,1000为换气状态,0100为强暖状态,0010为风暖状态,0001为干燥状态。在仿真图中可以看到随着switch的状态变化,绿色列信号和红色列信号以及行信号均有变化(具体展开波形图在后续动画模块仿真分析中会有展示)。
并且在testbench的编写中,我将lighten(点亮信号)赋值为1-0-1-0,故可以观察到,在开机动画后,light的第六位即led6有对应1-0-1-0的变化。
该仿真模拟了延时状态下的数码管变化,可以看见,在delay = 4以及 delay = 2的情况下数码管均有不同状态的显示,并且当延时完成时,输出变量done处产生了两个脉冲表示延时完成。
在顶层模块的模块例化以及参数传递下,当segment模块中的done = 1后,该变量传入button模块中,作为条件判断改变switch状态值,使点阵停止,进入待机状态。
在仿真波形中可以看见pulse变量输入窄小脉冲激励代表实际板子中按键按下产生的脉冲,检验到相应脉冲pulse[3],pulse[2],pulse[1],pulse[0]后switch进行相应的变化,代表不同的功能动画状态,并且图中演示了如果按下两下(该脉冲为两个脉冲叠加,故明显粗一些),delay值变为010,即进行2s倒计时,在顶层模块中将该值传入segment模块进行数码管倒计时显示,倒计时结束后进入倒计时状态,switch变为1111。
其中lighten对应的是灯是否点亮,其对应控制脉冲为pulse[4],在图片中可以看到第一次脉冲产生后,lighten为1,再次产生脉冲(即第二次按下按键)lighten变为0。
通过行扫描在点阵上显示动画,观察第一个行扫描信号与最后一个行扫描信号之间的绿色列信号,易看出点阵动画为题目所需,行扫描信号为0有效,绿色列信号为1有效。
在此模块中有红色列信号与绿色列信号,故行信号不便截图给出。我在管脚处配置的列信号管脚与仿真中的高低位相反,故应该倒着看,(当时上板烧制的时候发现图形正好反了,更改整个代码过于复杂,故直接更改管脚配置中的引脚。)便与规定图形相同。(中心对称图形无差距。)
在检测到浴霸开机后,由仿真波形可以看见绿色列信号与红色列信号均为0-1-0-1,完成四帧闪烁,之后变为全0,点阵全灭,row为全1,行扫描信号一直有效。
Seg信号一直有效,即所有数码管都处于同一状态,segnum从0100-1111-0100-1111,完成全八点亮到default值控制数码管全灭的过程,完成四帧闪烁。
Light信号的变化可以看书十六个led灯也为有规律的四帧闪烁。
传入例化参数N=11,WIDTH=4,实现11分频。
将pulse设置为四位宽的窄脉冲,检验到脉冲,则产生不同频率的时钟信号赋值给beep蜂鸣器,产生频率不同的一段声音。
module bath_heater( input clk,//输入选择1MHz的时钟 input on,//为sw6键状态,是否工作 input rst,//设置为按键0 input BTN7,BTN6,BTN5,BTN4,BTN3,//分别实现换气、风暖、强暖、干燥功能 output beep,//蜂鸣器 output [15:0]light,//LED6为照明灯 output [7:0] col_r,//红色列 output [7:0] col_g,//绿色列 output [7:0] row,//行 output [7:0] Seg, //选择哪个数码管输出,低电平有效 output [7:0] Seg_pin//数码管受控端,高电平有效 ); wire [7:0]seg;//选择八个数码管哪一个输出端 wire [3:0]segpin;//选择数码管引脚端 wire done ;//计时完成 wire [2:0]delay ;//是否进行延迟,延迟几秒 wire lighten;//灯是否亮 wire [3:0] switch;//选择播放哪个动画,全1则处于待机状态.对应按键6543 wire start; wire pulse7,pulse6,pulse5,pulse4,pulse3; //按键消抖 debounce #(.N(5)) de1( .clk(clk), .rst(!rst), .key({!BTN7,!BTN6,!BTN5,!BTN4,!BTN3}), .key_pulse({pulse7,pulse6,pulse5,pulse4,pulse3}) ); //声音模块 sound so( .rst(rst), .on(on), .clk(clk), .pulse3(pulse3),.pulse4(pulse4),.pulse5(pulse5),.pulse6(pulse6),.pulse7(pulse7), .beep(beep) ); //按键检测 button b1( .clk(clk), .on(on), .pulse3(pulse3),.pulse4(pulse4),.pulse5(pulse5),.pulse6(pulse6),.pulse7(pulse7), .lighten(lighten), .switch(switch), .delay(delay), .done(done) ); //数码管模块 segment s1( .on(on), .seg(seg), .start(start), .delay(delay), .clk(clk), .segpin(segpin), .done(done), .Seg(Seg), .Seg_pin(Seg_pin) ); //动画、功能展示模块 display di( .clk(clk), .on(on), .lighten(lighten), .switch(switch), .start(start), .light(light), .col_r(col_r), .col_g(col_g), .row(row), .segpin(segpin), .seg(seg) ); endmodule |
module button ( input clk, input on,//是否开启,作为判断最优先条件 input pulse3,pulse4,pulse5,pulse6,pulse7, input done,//由segment模块输出的变量done表示延时是否完成 output reg lighten,//控制灯是否点亮变量 output reg [3:0] switch,//切换变量 output reg [2:0] delay//记录延时时间,若为0则为default值全灭 ); initial begin lighten <= 0; switch <= 4'b1111; delay <= 0; end always@(posedge clk)begin //检测功能按键 if(on)begin//跟当前状态比较,若输出下一状态与当前状态相同,则进入待机/关闭 if(pulse7)begin if(lighten) lighten <= 0; else lighten <= 1; end else if(pulse3)begin if(switch == 4'b0001) switch <= 4'b1111;//进入待机状态 else switch <= 4'b0001; end else if(pulse4)begin //延时4s if(switch == 4'b0010) delay <= 4; else switch <= 4'b0010; end else if(pulse5)begin //延时2s if(switch == 4'b0100) delay <= 2; else switch <= 4'b0100; end else if(pulse6)begin if(switch == 4'b1000) switch <= 4'b1111;//进入待机状态 else switch <= 4'b1000; end else if(done)begin delay <= 0; switch <= 4'b1111;//进入待机状态 end end else begin//关机则重置 switch <= 4'b1111; lighten <= 0; delay <= 0; end end endmodule |
module segment( input on, input start,//开机动画是否开启 input clk, input [2:0]delay, input [3:0]segpin, input [7:0]seg, output reg done,//接受button模块传来的延时信号delay,将是否完成延时输出 output reg [7:0]Seg, output reg [7:0]Seg_pin ); reg [3:0]segnum = 4'b1111;//segnum控制数码管的显示 reg [19:0] delaycnt = 0; initial begin done <= 0; end always@(segnum)begin case(segnum) 4'd1: Seg_pin = 8'b0000_0110; 4'd2: Seg_pin = 8'b0101_1011; 4'd3: Seg_pin = 8'b0100_1111; 4'd4: Seg_pin = 8'b0110_0110; 4'd8: Seg_pin = 8'b0111_1111;//全8闪烁,不需要小数点 default: Seg_pin = 8'b0000_0000;//全灭 endcase end always@(negedge clk)begin //数码管控制模块 if(on)begin if(start)begin segnum <= segpin; Seg <= seg; end else if(delay != 0)begin //不为0则 if(segnum != 0)begin Seg <= 8'b1111_0111; if(delaycnt == 1000000)begin //延时到1s delaycnt <= 0; if(segnum == 1)begin segnum <= 0;done <= 1; end else segnum <= segnum-1; end else delaycnt <= delaycnt + 1; end else begin segnum <= delay;done <= 0; end end else begin//若不是开机动画,且不需要延时(待机动画) Seg <= 8'b1111_1111; done <= 0; delaycnt <= 0; segnum <= 0; end end//若关机 else begin segnum <= 4'b1111;//为default值 Seg <= 8'b1111_1111; end end endmodule |
module display( input clk, input on, input lighten, input [3:0]switch, output start, output reg [15:0]light,//LED6为照明灯 output reg [7:0] col_r,//红色列 output reg [7:0] col_g,//绿色列 output reg [7:0] row,//行 output [3:0]segpin, output [7:0]seg ); wire [7:0] col_go,col_ro,row_open; wire [15:0]light_rec1; //换气、风暖、强暖、干燥功能动画存储变量 wire [7:0] col_g1,col_r1;//记录第一个动画绿色列信号 wire [7:0] col_g2,col_r2;//记录第二个动画绿色、红色列信号 wire [7:0] col_g3,col_r3;//记录第三个动画绿色列信号 wire [7:0] col_g4,col_r4;//记录第四个动画绿色列信号 wire [7:0] row1,row2,row3,row4;//记录行信号 //开机动画 view_open open( .on(on), .clk(clk), .col_r(col_ro), .col_g(col_go), .light(light_rec1), .segnum(segpin), .Seg(seg), .row(row_open), .start(start) ); //换气动画 view1 v1( .clk(clk), .col_r(col_r1), .col(col_g1), .row(row1) ); //风暖 view2 v2( .clk(clk), .col_r(col_r2), .col_g(col_g2), .row(row2) ); //强暖 view3 v3( .clk(clk), .col_r(col_r3), .col_g(col_g3), .row(row3) ); //干燥 view4 v4( .clk(clk), .col_g(col_g4), .col_r(col_r4), .row(row4) ); always@(posedge clk)//此处通过检测按键,利用条件判断输出哪个动画 begin if(on)begin//若开机 if(start)begin //通过是否 col_r <= col_ro; col_g <= col_go; light <= light_rec1; row <= row_open; end else begin//功能动画 if(lighten) light <= 16'b0000_0000_0100_0000; else light <= 16'b0000_0000_0000_0000; case (switch)//利用switch的改变检测按键变化,从而进行相应赋值显示相应动画 4'b1111:begin col_g <= 8'b0000_0000;col_r <= 8'b0000_0000;row <= 8'b1111_1111; end 4'b0001:begin col_g <= col_g4;col_r <= col_r4;row <= row4; end 4'b0010:begin col_g <= col_g3;col_r <= col_r3;row <= row3; end 4'b0100:begin col_g <= col_g2;col_r <= col_r2;row <= row2; end 4'b1000:begin col_g <= col_g1;col_r <= col_r1;row <= row1; end endcase end end else begin//若关机 light <= 16'b0000_0000_0000_0000; col_g <= 8'b0000_0000; col_r <= 8'b0000_0000; row <= 8'b1111_1111; end end endmodule |
module view_open( input clk, input on, output reg [7:0] col_r, output reg [7:0] col_g, output reg [15:0]light, output reg [3:0] segnum,//数码管受控端 output reg [7:0] Seg, //选择哪个数码管输出 output reg [7:0] row, output reg start );//开机动画 reg flag = 1; reg [2:0]cnt = 0; reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期 //开关打开后,点阵全红、全绿交替全亮以2Hz闪烁,八个数码管亮“8”、16个发光二极同时亮以2Hz频率闪烁 //2s后,即红绿交替闪烁两次进入待机状态,点阵、数码管和二极管全灭 initial begin start <= 1; end always @(posedge clk)begin if(start)begin case(flag) 2'd1:begin light <= 16'b1111_1111_1111_1111; col_r <= 8'b1111_1111; col_g <= 8'b0000_0000; row <= 8'b0000_0000; segnum <= 4'd8; Seg <= 8'b0000_0000; end
2'd0: begin light <= 16'b0000_0000_0000_0000; col_g <= 8'b1111_1111; col_r <= 8'b0000_0000; row <= 8'b0000_0000; segnum <= 4'b1111;//default全灭 Seg <= 8'b0000_0000; end endcase end else begin light <= 16'b0000_0000_0000_0000; col_g <= 8'b0000_0000; col_r <= 8'b0000_0000; row <= 8'b1111_1111; segnum <= 0; Seg <= 8'b0000_0000; end end always @(posedge clk)begin//现在需要再次重置start if(on)begin//若开机 if(period == 500000) begin if(cnt == 3)begin//如果完成四次转换,则开机动画结束 start <= 0;//动画结束 end else begin cnt <= cnt + 1;//完成一次转换 flag <= ~flag;//完成帧的转换 period <= 0; end end else begin period <= period+1; end end //若关机 else begin start <= 1; cnt <= 0; period <= 0; flag <= 1; end end endmodule |
module view1 //第一个动画 ( input clk, output reg [7:0] col,//列 output reg [7:0] col_r,//列 output reg [7:0] row//行 ); reg [2:0]cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭 reg flag = 0;//每一帧画面是否切换 reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期 always@(negedge clk) begin if(cnt == 8)cnt <= 0; else cnt<=cnt+1; if(period == 500000) begin //若使用仿真,该数字有点大,可改小为500 flag <= ~flag;//完成帧的转换 period<=0;//此处period=0.5/t,t为时钟的一个周期,暂设为7,则两帧动画无缝衔接,便于在仿真中观测波形 end else begin period<=period+1; end end always@(cnt) begin
//第一帧 if(!flag) case(cnt)//从第七行开始扫描,即最上面一行 3'd0:begin col <= 8'b11100001;col_r <= 8'b00000000;row <= 8'b11111110;end 3'd1:begin col <= 8'b01100011;col_r <= 8'b00000000;row <= 8'b11111101;end 3'd2:begin col <= 8'b00100111;col_r <= 8'b00000000;row <= 8'b11111011;end 3'd3:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11110111;end 3'd4:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11101111;end 3'd5:begin col <= 8'b11100100;col_r <= 8'b00000000;row <= 8'b11011111;end 3'd6:begin col <= 8'b11000110;col_r <= 8'b00000000;row <= 8'b10111111;end 3'd7:begin col <= 8'b10000111;col_r <= 8'b00000000;row <= 8'b01111111;end default : begin row <= 8'b11111111;col_r <= 8'b00000000;col <= 8'b00000000;//点阵全灭 end endcase else case(cnt) 3'd0:begin col <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end 3'd1:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11111101;end 3'd2:begin col <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111011;end 3'd3:begin col <= 8'b01011111;col_r <= 8'b00000000;row <= 8'b11110111;end 3'd4:begin col <= 8'b11111010;col_r <= 8'b00000000;row <= 8'b11101111;end 3'd5:begin col <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b11011111;end 3'd6:begin col <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b10111111;end 3'd7:begin col <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end default : begin row <= 8'b11111111;col_r <= 8'b00000000;col <= 8'b00000000;//点阵全灭 end endcase end endmodule |
module view2( input clk, output reg [7:0] col_r, output reg [7:0] col_g, output reg [7:0] row ); reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭 reg [2:0]flag = 0;//每一帧画面的切换 reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期 always@(negedge clk) begin if(cnt == 8) cnt<=0; else cnt<=cnt+1; if(period == 500000) begin//仿真时改为500显示全部图形 period <= 0; flag <= (flag+1)%4; end else period<=period+1; end always@(cnt) begin case(flag) //第一帧动画 3'd0:case(cnt) 3'd0:begin col_g <= 8'b11100000;col_r <= 8'b00000111;row <= 8'b01111111;end 3'd1:begin col_g <= 8'b11000000;col_r <= 8'b00000011;row <= 8'b10111111;end 3'd2:begin col_g <= 8'b10100000;col_r <= 8'b00000101;row <= 8'b11011111;end 3'd3:begin col_g <= 8'b00010000;col_r <= 8'b00001000;row <= 8'b11101111;end 3'd4:begin col_g <= 8'b00001000;col_r <= 8'b00010000;row <= 8'b11110111;end 3'd5:begin col_g <= 8'b00000101;col_r <= 8'b10100000;row <= 8'b11111011;end 3'd6:begin col_g <= 8'b00000011;col_r <= 8'b11000000;row <= 8'b11111101;end 3'd7:begin col_g <= 8'b00000111;col_r <= 8'b11100000;row <= 8'b11111110;end endcase 3'd1:case(cnt) //第二帧动画 3'd0:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end 3'd1:begin col_g <= 8'b00011100;col_r <= 8'b00000000;row <= 8'b10111111;end 3'd2:begin col_g <= 8'b00001000;col_r <= 8'b01000000;row <= 8'b11011111;end 3'd3:begin col_g <= 8'b00001000;col_r <= 8'b11110010;row <= 8'b11101111;end 3'd4:begin col_g <= 8'b00010000;col_r <= 8'b01001111;row <= 8'b11110111;end 3'd5:begin col_g <= 8'b00010000;col_r <= 8'b00000010;row <= 8'b11111011;end 3'd6:begin col_g <= 8'b00111000;col_r <= 8'b00000000;row <= 8'b11111101;end 3'd7:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end endcase 3'd2:case(cnt) //第三帧动画 3'd0:begin col_r <= 8'b11100000;col_g <= 8'b00000111;row <= 8'b01111111;end 3'd1:begin col_r <= 8'b11000000;col_g <= 8'b00000011;row <= 8'b10111111;end 3'd2:begin col_r <= 8'b10100000;col_g <= 8'b00000101;row <= 8'b11011111;end 3'd3:begin col_r <= 8'b00010000;col_g <= 8'b00001000;row <= 8'b11101111;end 3'd4:begin col_r <= 8'b00001000;col_g <= 8'b00010000;row <= 8'b11110111;end 3'd5:begin col_r <= 8'b00000101;col_g <= 8'b10100000;row <= 8'b11111011;end 3'd6:begin col_r <= 8'b00000011;col_g <= 8'b11000000;row <= 8'b11111101;end 3'd7:begin col_r <= 8'b00000111;col_g <= 8'b11100000;row <= 8'b11111110;end endcase 3'd3:case(cnt) //第四帧动画 3'd0:begin col_r <= 8'b00001000;col_g <= 8'b00000000;row <= 8'b01111111;end 3'd1:begin col_r <= 8'b00011100;col_g <= 8'b00000000;row <= 8'b10111111;end 3'd2:begin col_r <= 8'b00001000;col_g <= 8'b01000000;row <= 8'b11011111;end 3'd3:begin col_r <= 8'b00001000;col_g <= 8'b11110010;row <= 8'b11101111;end 3'd4:begin col_r <= 8'b00010000;col_g <= 8'b01001111;row <= 8'b11110111;end 3'd5:begin col_r <= 8'b00010000;col_g <= 8'b00000010;row <= 8'b11111011;end 3'd6:begin col_r <= 8'b00111000;col_g <= 8'b00000000;row <= 8'b11111101;end 3'd7:begin col_r <= 8'b00010000;col_g <= 8'b00000000;row <= 8'b11111110;end endcase endcase end endmodule |
module view3( input clk, output reg [7:0] col_r, output reg [7:0] col_g, output reg [7:0] row ); reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭 reg [2:0]flag = 0;//每一帧画面的切换 reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期 always@(negedge clk) begin if(cnt == 8) cnt<=0; else cnt<=cnt+1; if(period == 500000) begin period <= 0; flag <= (flag+1)%4; end else period<=period+1; end always@(cnt) begin case(flag) //第一帧动画 3'd0:case(cnt) 3'd0:begin col_g <= 8'b11100000;col_r <= 8'b11100111;row <= 8'b01111111;end 3'd1:begin col_g <= 8'b11000000;col_r <= 8'b11000011;row <= 8'b10111111;end 3'd2:begin col_g <= 8'b10100000;col_r <= 8'b10100101;row <= 8'b11011111;end 3'd3:begin col_g <= 8'b00010000;col_r <= 8'b00011000;row <= 8'b11101111;end 3'd4:begin col_g <= 8'b00001000;col_r <= 8'b00011000;row <= 8'b11110111;end 3'd5:begin col_g <= 8'b00000101;col_r <= 8'b10100101;row <= 8'b11111011;end 3'd6:begin col_g <= 8'b00000011;col_r <= 8'b11000011;row <= 8'b11111101;end 3'd7:begin col_g <= 8'b00000111;col_r <= 8'b11100111;row <= 8'b11111110;end endcase 3'd1:case(cnt) //第二帧动画 3'd0:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b01111111;end 3'd1:begin col_g <= 8'b00011100;col_r <= 8'b00011100;row <= 8'b10111111;end 3'd2:begin col_g <= 8'b00001000;col_r <= 8'b01001000;row <= 8'b11011111;end 3'd3:begin col_g <= 8'b00001000;col_r <= 8'b11111010;row <= 8'b11101111;end 3'd4:begin col_g <= 8'b00010000;col_r <= 8'b01011111;row <= 8'b11110111;end 3'd5:begin col_g <= 8'b00010000;col_r <= 8'b00010010;row <= 8'b11111011;end 3'd6:begin col_g <= 8'b00111000;col_r <= 8'b00111000;row <= 8'b11111101;end 3'd7:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111110;end endcase 3'd2:case(cnt) //第三帧动画 3'd0:begin col_g <= 8'b00000111;col_r <= 8'b11100111;row <= 8'b01111111;end 3'd1:begin col_g <= 8'b00000011;col_r <= 8'b11000011;row <= 8'b10111111;end 3'd2:begin col_g <= 8'b00000101;col_r <= 8'b10100101;row <= 8'b11011111;end 3'd3:begin col_g <= 8'b00001000;col_r <= 8'b00011000;row <= 8'b11101111;end 3'd4:begin col_g <= 8'b00010000;col_r <= 8'b00011000;row <= 8'b11110111;end 3'd5:begin col_g <= 8'b10100000;col_r <= 8'b10100101;row <= 8'b11111011;end 3'd6:begin col_g <= 8'b11000000;col_r <= 8'b11000011;row <= 8'b11111101;end 3'd7:begin col_g <= 8'b11100000;col_r <= 8'b11100111;row <= 8'b11111110;end endcase 3'd3:case(cnt) //第四帧动画 3'd0:begin col_r <= 8'b00001000;col_g <= 8'b00000000;row <= 8'b01111111;end 3'd1:begin col_r <= 8'b00011100;col_g <= 8'b00000000;row <= 8'b10111111;end 3'd2:begin col_r <= 8'b01001000;col_g <= 8'b01000000;row <= 8'b11011111;end 3'd3:begin col_r <= 8'b11111010;col_g <= 8'b11110010;row <= 8'b11101111;end 3'd4:begin col_r <= 8'b01011111;col_g <= 8'b01001111;row <= 8'b11110111;end 3'd5:begin col_r <= 8'b00010010;col_g <= 8'b00000010;row <= 8'b11111011;end 3'd6:begin col_r <= 8'b00111000;col_g <= 8'b00000000;row <= 8'b11111101;end 3'd7:begin col_r <= 8'b00010000;col_g <= 8'b00000000;row <= 8'b11111110;end endcase endcase end endmodule |
module view4( input clk, output reg [7:0] col_r, output reg [7:0] col_g, output reg [7:0] row ); reg [2:0] cnt = 0;//每一帧画面的行扫描记录,初始值为default,点阵全灭 reg [2:0]flag = 0;//每一帧画面的切换 reg [18:0]period = 0;//记录周期,当达到0.5s时切换下一周期 always@(negedge clk) begin if(cnt == 8) cnt<=0; else cnt<=cnt+1; if(period == 500000) begin period <= 0; flag <= (flag+1)%4; end else period <= period+1; end always@(cnt) begin case(flag) //第一帧动画 3'd0:case(cnt) 3'd0:begin col_g <= 8'b11100001;col_r <= 8'b11100000;row <= 8'b11111110;end 3'd1:begin col_g <= 8'b01100011;col_r <= 8'b01100000;row <= 8'b11111101;end 3'd2:begin col_g <= 8'b00100111;col_r <= 8'b00100000;row <= 8'b11111011;end 3'd3:begin col_g <= 8'b00011000;col_r <= 8'b00010000;row <= 8'b11110111;end 3'd4:begin col_g <= 8'b00011000;col_r <= 8'b00001000;row <= 8'b11101111;end 3'd5:begin col_g <= 8'b11100100;col_r <= 8'b00000100;row <= 8'b11011111;end 3'd6:begin col_g <= 8'b11000110;col_r <= 8'b00000110;row <= 8'b10111111;end 3'd7:begin col_g <= 8'b10000111;col_r <= 8'b00000111;row <= 8'b01111111;end endcase 3'd1:case(cnt) //第二帧动画 3'd0:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111110;end 3'd1:begin col_g <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b11111101;end 3'd2:begin col_g <= 8'b00010000;col_r <= 8'b00000000;row <= 8'b11111011;end 3'd3:begin col_g <= 8'b01011111;col_r <= 8'b01001111;row <= 8'b11110111;end 3'd4:begin col_g <= 8'b11111010;col_r <= 8'b11110010;row <= 8'b11101111;end 3'd5:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b11011111;end 3'd6:begin col_g <= 8'b00011000;col_r <= 8'b00000000;row <= 8'b10111111;end 3'd7:begin col_g <= 8'b00001000;col_r <= 8'b00000000;row <= 8'b01111111;end endcase 3'd2:case(cnt) //第三帧动画 3'd0:begin col_g <= 8'b11100001;col_r <= 8'b00000001;row <= 8'b11111110;end 3'd1:begin col_g <= 8'b01100011;col_r <= 8'b00000011;row <= 8'b11111101;end 3'd2:begin col_g <= 8'b00100111;col_r <= 8'b00000111;row <= 8'b11111011;end 3'd3:begin col_g <= 8'b00011000;col_r <= 8'b00001000;row <= 8'b11110111;end 3'd4:begin col_g <= 8'b00011000;col_r <= 8'b00010000;row <= 8'b11101111;end 3'd5:begin col_g <= 8'b11100100;col_r <= 8'b11100000;row <= 8'b11011111;end 3'd6:begin col_g <= 8'b11000110;col_r <= 8'b11000000;row <= 8'b10111111;end 3'd7:begin col_g <= 8'b10000111;col_r <= 8'b10000000;row <= 8'b01111111;end endcase 3'd3:case(cnt) //第四帧动画 3'd0:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111110;end 3'd1:begin col_g <= 8'b00011000;col_r <= 8'b00011000;row <= 8'b11111101;end 3'd2:begin col_g <= 8'b00010000;col_r <= 8'b00010000;row <= 8'b11111011;end 3'd3:begin col_g <= 8'b01011111;col_r <= 8'b00010000;row <= 8'b11110111;end 3'd4:begin col_g <= 8'b11111010;col_r <= 8'b00001000;row <= 8'b11101111;end 3'd5:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b11011111;end 3'd6:begin col_g <= 8'b00011000;col_r <= 8'b00011000;row <= 8'b10111111;end 3'd7:begin col_g <= 8'b00001000;col_r <= 8'b00001000;row <= 8'b01111111;end endcase endcase end endmodule |
module sound( input rst, input on, input clk, input pulse3,pulse4,pulse5,pulse6,pulse7, output reg beep ); reg flag = 0; reg [18:0]timecnt = 0; wire clk1,clk2,clk3,clk4,clk5; reg [2:0] temp; divide #(.WIDTH(10),.N(1911/2)) u1 ( .clk(clk), .rst_n(rst),//该实验板和小脚丫的rst按键有效值不同!需要取反,否则无效 .clkout(clk1) ); divide #(.WIDTH(10),.N(1702/2)) d2 ( .clk(clk), .rst_n(rst), .clkout(clk2) ); divide #(.WIDTH(10),.N(1516/2)) d3 ( .clk(clk), .rst_n(rst), .clkout(clk3) ); divide #(.WIDTH(10),.N(1431/2)) d4 ( .clk(clk), .rst_n(rst), .clkout(clk4) ); divide #(.WIDTH(10),.N(1275/2)) d5 ( .clk(clk), .rst_n(rst), .clkout(clk5) ); always@(posedge clk)begin case (temp) 3'd0:beep <= 0; 3'd1:beep <= clk1; 3'd2:beep <= clk2; 3'd3:beep <= clk3; 3'd4:beep <= clk4; 3'd5:beep <= clk5; endcase end always@(posedge clk)begin //检测功能按键 if(on)begin if(pulse7)begin temp <= 1; flag <= 1; end else if(pulse3)begin temp <= 2; flag <= 1; end else if(pulse4)begin temp <= 3; flag <= 1; end else if(pulse5)begin temp <= 4; flag <= 1; end else if(pulse6)begin temp <= 5; flag <= 1; end else begin if(flag)begin if(timecnt == 500000)begin temp <= 0;timecnt <= 0; flag <= 0; end else timecnt <= timecnt + 1; end end /*else temp <= 0;always@(*)是时钟沿到来和变量发生变化的时候均会扫描,所以不能采用该语句*/ end else begin//关机则重置 temp <= 0; end end endmodule |
module divide (clk,rst_n,clkout); input clk,rst_n; //输入信号,其中clk连接到FPGA output clkout; //输出信号,可以连接到LED观察分频的时钟 //parameter是verilog里常数语句 parameter WIDTH = 3; //计数器的位数,计数的最大值为 2**WIDTH-1 parameter N = 5; //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出 reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器 reg clk_p = 0,clk_n; //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟 //上升沿触发时计数器的控制 always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿 //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句 begin if(!rst_n) cnt_p<=0; else if (cnt_p==(N-1)) cnt_p<=0; else cnt_p<=cnt_p+1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器 end //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50% always @ (posedge clk or negedge rst_n) begin if(!rst_n) clk_p<=0; else if (cnt_p<(N>>1)) //N>>1表示右移一位,相当于除以2去掉余数 clk_p<=0; else clk_p<=1; //得到的分频时钟正周期比负周期多一个clk时钟 end //下降沿触发时计数器的控制 always @ (negedge clk or negedge rst_n) begin if(!rst_n) cnt_n<=0; else if (cnt_n==(N-1)) cnt_n<=0; else cnt_n<=cnt_n+1; end //下降沿触发的分频时钟输出,和clk_p相差半个时钟 always @ (negedge clk) begin if(!rst_n) clk_n<=0; else if (cnt_n<(N>>1)) clk_n<=0; else clk_n<=1; //得到的分频时钟正周期比负周期多一个clk时钟 end assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式 //当N=1时,直接输出clk //当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p //当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与 endmodule |
module debounce (clk,rst,key,key_pulse); parameter N = 1; //要消除的按键的数量 input clk; input rst; input [N-1:0] key; //输入的按键 output [N-1:0] key_pulse; //按键动作产生的脉冲 reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值 reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值 wire [N-1:0] key_edge; //检测到按键由低到高变化是产生一个高脉冲 //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中 always @(posedge clk or negedge rst) begin if (!rst) begin key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1 key_rst_pre <= {N{1'b1}}; end else begin key_rst <= key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre key_rst_pre <= key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值 end end assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平 reg [15:0] cnt; //产生延时所用的计数器,系统时钟1MHz,要延时20ms左右时间,至少需要16位计数器 //产生20ms延时,当检测到key_edge有效时计数器清零开始计数 always @(posedge clk or negedge rst) begin if(!rst) cnt <= 16'h0; else if(key_edge) cnt <= 16'h0; else cnt <= cnt + 1'h1; end reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量 reg [N-1:0] key_sec; //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效 always @(posedge clk or negedge rst) begin if (!rst) key_sec <= {N{1'b1}}; else if (cnt==16'hffff) key_sec <= key; end always @(posedge clk or negedge rst) begin if (!rst) key_sec_pre <= {N{1'b1}}; else key_sec_pre <= key_sec; end assign key_pulse = key_sec_pre & (~key_sec); endmodule |
该工程实现了基础浴霸的风扇功能,可以通过按键控制进行对风扇功能的开启、切换以及停止,并且可以实现对灯亮灭的控制。
此下介绍几个主要模块的功能。
button模块主要实现了对按钮脉冲的检测,从而使风扇进入不同的状态,即点阵动画以及灯的状态传递。
display模块主要是对从button模块中传过来的状态进行展示,即对板子引脚的赋值,最终实现呈现在实验板上的效果,主要是控制动画的展示、切换、停止以及灯的亮灭。
segment模块控制数码管的状态传递以及数码管的显示,并且完成延时功能,将是否完成延时的状态返回给display模块,从而实现对延时动画的控制。
使用引脚65/116(56%)。
总逻辑宏单元655/1270(52%)。
当时验收的时候没有删去一些不需要的模块(想做提高功能)以及多余代码和变量声明,故逻辑宏单元近1000位,之后进行删改并且减少了时钟分频例化模块,减小计时变量位宽(一开始都非常豪迈的设成了32位),故最终逻辑宏单元减少为655。
考虑问题产生的原因:当烧制板子的时候动画始终只能出来第一行,便考虑是不是case语句或者是计数器出了问题
问题根源:在模块中引入了rst变量,但是在实际分配引脚时并未分配该管脚,处于悬空状态,便一直重置,导致动画显示不完全。
解决方案:配置rst引脚。
考虑问题产生的原因:其实这是一个我在写代码过程中经常碰见的一个问题,就是各个模块之间的连接问题。
问题根源:(1)位宽设置不正确,在模块接口时,声明变量时若没有设置正确的位宽,则会导致只有一位0和1的变化;
(2)模块接口中output接口只能设置为wire型变量;
(3)在拆分模块的时候,要实现规划好每个模块的功能,并且对模块之间的连接有一个初步的构思,不要遗忘引脚的分配。
考虑问题产生的原因:是按键消抖模块出了问题,或者是条件判断语句出错。
问题根源:使用的按键消抖为原来max10系列的芯片的教程代码,该芯片固定时钟为12MHz,而本实验中采用的频率是1MHz,相对而言按键消抖时间过长,无法产生有效脉冲。
解决方案:将原来延时的位宽缩小至16位,便能正确产生按键消抖效果。
解决了按键消抖问题后,觉得应该是always块的判断条件出了问题,再经过细心调试后,发现是always@(*)与always@(posedge clk)判断语句执行的问题。在Verilog中always@(*)语句的意思是always模块中的任何一个输入信号或电平发生变化时,该语句下方的模块将被执行。
以下为查阅资料得:
1、always语句有两种触发方式。第一种是电平触发,例如always @(a or b or c),a、b、c均为变量,当其中一个发生变化时,下方的语句将被执行。
2、第二种是沿触发,例如always @(posedge clk or negedge rstn),即当时钟处在上升沿或下降沿时,语句被执行。
3、而对于always@(*),意思是以上两种触发方式都包含在内,任意一种发生变化都会触发该语句。
4.在板子上实验现象正确时,仿真还是无法出现正确波形
考虑问题产生的原因:给予变量的数值不正确,或者是代码逻辑错误等
问题根源:(1)本工程中许多模块相互传递参数,则在仿真时也要仔细设定正确的延时以及对应的参变量,否则无法出现正确的波形。
(2)在仿真中本来应该是以稳定电平形式出现的波形出现时钟类型震荡,是因为在仿真中给的参数不对,例如在button模块,条件判断是以窄小脉冲形式,在仿真中应该给予窄脉冲形式的激励
5.数码管延时问题
此处将数码管出现的问题整合
(1)动画要播放完整后才进入待机状态/动画不进行延时
if(segnum == 0) begin segnum <= delay; done <=0;end//在delay不为0的状态下,如果segnum为0则进行倒计时
语句如上,陷入死循环。原来的意图是,如果delay不为0,则将数码管的初始数值赋为delay之后在时钟的作用下进行减法计数器。更改条件判断语句即可。注意在if-else语句,尤其是多种选择嵌套的时候,一定要理顺优先级关系,否则及其容易出现逻辑错误,有时通过仿真和现象都很难排查。
(2)数码管一直进入倒计时死循环212121,且无法切入进待机状态,考虑是条件判断分支出了问题。
解决方法:因为按键按下,并且经过按键消抖后会产生一个暂时性脉冲,判断延时完成的条件分支不可以放在检测按键脉冲的大条件分支下。
(3)将数码管控制部分写成模块后,又无法进行切换,由于之前功能完好,故怀疑是模块内部出现问题,定义输入/输出/reg/wire变量一定记得位宽!!!!
总结:
在整个实验过程中,我加深了对verilog语言的理解以及状态机的构思,并且能够熟练编写工程,编写testbench,运用modelsim-altera进行仿真。
这个数电实验相对于数电课内实验来说复杂得多,在烧录一个工程的时候能写出近十个模块也是我第一次经历的。
其实浴霸相对而言是一个比较简单的题目,基础的逻辑思维设计我其实一开始花个两三个小时就比较轻松的写了出来,但是最花时间的是debug的过程,一开始并不擅长使用testbench,所以debug对我来说就是一个烧板子看现象的过程,但是这次的代码牵扯到的引脚很多,一次写完所有模块的代码看现象,即一次性正确的可能性实在是太低。由于实验板不在手边,我一次性酣畅淋漓地写完了所有代码,但是第一次烧录到板子上的时候什么现象也没有,不由得傻眼,但是后来就把代码一部分一部分地注释掉,逐段调试。
写工程的本质还是语言,最重要的还是逻辑语言。我还记得第一次验收模块电路的时候老师看一眼我的代码说if-else其实是容易出错的。我在整个实验过程中都深刻体会到了这一点,而且quartus没有c++那么简洁的debug机制,着实是让人头疼。
其实一开始对老师所说的调整红绿光比例从而调出黄色点阵是很感兴趣的,这部分其实是我花时间最多的一个地方,查阅资料发现调整红绿光占空比即可实现黄色光,在静态点阵中可以实现黄色光,但是本工程中点阵动画为动态的,还没有调试出来。由于期末周和实验巧妙撞车,衡量之下还是决定专心复习。
该实验的难点我可以总结为两点:1.代码量大,看完题目后最好能在心里先构思一个模块分块以及各个功能代码实现的基础逻辑。并且由于是第一次接触到这种对我而言的大工程,想要思路中断一次性写完是比较难的,如果没有遵循一个主线去做的话,每次写实验的时候都要重新思考并且延续之前的思路,其实是一个低效率的做法,并且很容易在之前的基础上出错。2.逻辑易错,其实代码整体思路不难,但是在各个模块之间的数据传递以及条件判断循环之间比较容易出错,而且一旦出错需要花费大量时间去改错,也有可能跟我不擅长使用这个软件有关系,不太擅长利用仿真去查错。
结论:1.在建立工程的时候一定要先对题目分析,列出状态图以及流程图,大概规划出自己需要几个模块以及代码的大致框架;2.在对模块进行分割的时候,要注意各个模块之间的逻辑连接情况。