本节的文档编号:001600000019
需要看对应的视频,请点击视频编号:001600000042
1.至简原理与应用配套的案例文档
2.使用6个数码管实现数字时钟功能,与数字时钟相同,该功能可以显示00:00:00到23:59:59范围的时间。
3. 这是ALTERA入门学习案例文档
1项目背景
数字时钟用数字电路技术实现时、分、秒计时显示的装置。用数字同时显示时,分,秒的精确时间,并能实现准确校时。与传统表盘式机械式时钟相比,具有更高的准确性和直观性,且无机械装置,具有更长的使用寿命。不仅如此,还具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易开发等特性。本案例将详细介绍用至简设计法实现数字时钟的功能。
2 设计目标
本工程使用6个数码管实现数字时钟功能。该功能与数字时钟相同,即从00:00:00一直到23:59:59。
上板效果图如下图所示。
图 288
3 设计实现
3.1 顶层信号
新建目录:D:mdy_bookmy_shizhong。在该目录中,新建一个名为my_shizong.v的文件,并用GVIM打开,开始编写代码。
我们要实现的功能,概括起来就是控制8个数码管,让其中2个数码管常灭,其他6个数码显示不同的数字。要控制8个数码管,就需要控制位选信号,即FPGA要输出一个8位的位选信号,设为seg_sel,其中seg_sel[0]对应数码管0,seg_sel[1]对应数码管1,以此类推,seg_sel[7]对应数码管7。
要显示不同的数字,就需要控制段选信号,不需要用到DP,一共有7根线,即FPGA要输出一个7位的段选信号,设为seg_ment,seg_ment[6]~segm_ment[0]分别对应数码管的abcdefg(注意对应顺序)。
我们还需要时钟信号和复位信号来进行工程控制。
综上所述,我们这个工程需要4个信号,时钟clk,复位rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment。
器件 |
信号线 |
信号线 |
FPGA管脚 |
内部信号 |
U6,U7 |
SEG_E |
SEG0 |
Y6 |
seg_ment[2] |
SEG_DP |
SEG1 |
W6 |
未用到 |
|
SEG_G |
SEG2 |
Y7 |
seg_ment[0] |
|
SEG_F |
SEG3 |
W7 |
seg_ment[1] |
|
SEG_D |
SEG4 |
P3 |
seg_ment[3] |
|
SEG_C |
SEG5 |
P4 |
seg_ment[4] |
|
SEG_B |
SEG6 |
R5 |
seg_ment[5] |
|
SEG_A |
SEG7 |
T3 |
seg_ment[6] |
|
DIG1 |
DIG_EN1 |
T4 |
seg_sel[0] |
|
DIG2 |
DIG_EN2 |
V4 |
seg_sel[1] |
|
DIG3 |
DIG_EN3 |
V3 |
seg_sel[2] |
|
DIG4 |
DIG_EN4 |
Y3 |
seg_sel[3] |
|
DIG5 |
DIG_EN5 |
Y8 |
seg_sel[4] |
|
DIG6 |
DIG_EN6 |
W8 |
seg_sel[5] |
|
DIG7 |
DIG_EN7 |
W10 |
seg_sel[6] |
|
DIG8 |
DIG_EN8 |
Y10 |
seg_sel[7] |
|
X1 |
|
SYS_CLK |
G1 |
clk |
K1 |
|
SYS_RST |
AB12 |
rst_n |
将module的名称定义为my_shizhong。并且我们已经知道该模块有4个信号:clk、rst_n、seg_sel和seg_ment,代码如下:
1 2 3 4 5 6 |
module my_shizhong( clk , rst_n , seg_sel , seg_ment ); |
其中clk、rst_n是1位的输入信号,seg_sel是8位的输出信号,seg_ment是7位的输出信号,根据此,补充输入输出端口定义。代码如下:
1 2 3 4 |
input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0] seg_ment; |
3.2 信号设计
我们先分析要实现的功能,我们用m_g,m_s,f_g,f_s,s_g,s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示的数字值,m_g_s,m_s_s,f_g_s,f_s_s,s_g_s,s_s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示数码管段选值。
数码管0显示的是秒个位值,则翻译成信号就是seg_sel的值为8’b1111_1110,seg_ment的值为:m_g_s。数码管1显示秒十位,也就是说seg_sel的值为8’b1111_1101,seg_ment的值为m_s_s。以此类推,数码管5显示小时十位,也就是seg_sel的值为8’b1101_1111,seg_ment的值为s_s_s。
图 289
那么seg_ment和seg_sel多久变化一次呢?我们就需要用到数码管动态扫描原理了。
数码管动态显示接口是应用最为广泛的一种显示方式之一,动态驱动是将所有数码管的8个显示笔划"a,b,c,d,e,f,g,dp"的同名端连在一起,另外为每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制,当要输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于单片机对位选通COM端电路的控制,所以我们只要将需要显示的数码管的选通控制打开,该位就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的的COM端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。
也就是说,只要刷新时间为1~2ms,那么人眼看起来就似乎是所有数码管都在显示。因为我们把这个刷新时间定为2ms,也就是如下图。
图 290
由波形图可知,我们需要1个计数器用来计算2毫秒的时间。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到2_000_000/20=100_000个,我们就能知道2毫秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assign add_cnt0==1。综上所述,该计数器的代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt0 <= 0; end else if(add_cnt0)begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0 + 1; end end
assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0==100_000-1 ; |
再次观察波形图,我们发现依次是显示的是m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s,一共6个,循环进行显示。这说明还需要另外一个计数器来表示第几个。该计数器每隔2ms加1,因此加1条件为end_cnt0。该计数器一共要数6次。所以代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt1 <= 0; end else if(add_cnt1)begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1 + 1; end end
assign add_cnt1 = end_cnt0; assign end_cnt1 = add_cnt1 && cnt1==6-1 ; |
有了两个计数器,我们来思考输出信号seg_sel的变化。概括起来,在第1次的时候输出值为8’hfe;在第2次的时候输出值为8’hfd;以此类推,在第6次的时候输出值为8’hdf。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==5的时候输出值为8’hdf。再进一步翻译成代码,就变成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel <= 8'hfe; end else if(cnt1==0) begin seg_sel <= 8'hfe; end else if(cnt1==1) begin seg_sel <= 8'hfd; end else if(cnt1==2) begin seg_sel <= 8'hfb; end else if(cnt1==3) begin seg_sel <= 8'hf7; end else if(cnt1==4) begin seg_sel <= 8'hef; end else if(cnt1==5) begin seg_sel <= 8'hdf; end end |
或者可以简写成:
1 2 3 4 5 6 7 8 |
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel <= 8'hfe ; end else begin
seg_sel <= ~(8'b1< end end |
对上面代码解释一下,第131行是指先将8’b1向左移位,再取反后的值,赋给seg_sel。假设此时cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe;假设cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’b1111_0111,即8’hf7。与第一种写法的结果都是相同的。
我们来思考输出信号seg_ment的变化。seg_ment的值,是m_g、m_s、f_g、f_s、s_g和s_s等数值分别译码成数码管显示的信号m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s。我们一种做法,是将数值分别转成译码信号,然后再挑选出来给seg_ment。如下面的原理图:
图 291
我们还可以是先可以选择哪一组数据,选出来后再做译码,其原理图如下:
图 292
对比两个原理图,可以发现,实现同样的功能,第二个原理图比第一个,少了5个译码电路。这是一个简单的例子,说明实现一个功能,可以有很多种方法,方法各有优势,能用较少的资源、较快的速度实现功能,这就是设计师的能力,也是FPGA设计的魅力所在。
我们采用第二种实现方案。设计一个信号sel_data,表示从6个数据中选择的信号,之后再做译码。波形图更新如下:
图 293
sel_data的值有可能为0~9中的任意数字,这些数字都要转成数码管的段选信号。下表列出不同数字对应的段选信号值。
显示 数字 |
共阳abcdefg 2进制 |
共阳abcdefg 16进制 |
共阴abcdefg 2进制 |
共阴abcdefg 16进制 |
0 |
7’b0000001 |
7’h01 |
7’b 1111110 |
7’h7e |
1 |
7’b 1001111 |
7’h4f |
7’b 0110000 |
7’h30 |
2 |
7’b 0010010 |
7’h12 |
7’b 1101101 |
7’h6d |
3 |
7’b 0000110 |
7’h06 |
7’b 1111001 |
7’h79 |
4 |
7’b 1001100 |
7’h4c |
7’b 0110011 |
7’h33 |
5 |
7’b 0100100 |
7’h24 |
7’b 1011011 |
7’h5b |
6 |
7’b 0100000 |
7’h20 |
7’b 1011111 |
7’h3f |
7 |
7’b 0001111 |
7’h0f |
7’b 1110000 |
7’h70 |
8 |
7’b 0000000 |
7’h00 |
7’b 1111111 |
7’h7f |
9 |
7’b 0000100 |
7’h04 |
7’b 1111011 |
7’h7b |
明德扬开发板使用的是共阳数码管。根据上表,可写出下面代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
always @(posedgeclk or negedgerst_n)begin if(rst_n==1'b0)begin seg_ment<= 7'h01; end else if(sel_data==0)begin seg_ment<= 7'h01; end else if(sel_data==1)begin seg_ment<= 7'h4f; end else if(sel_data==2)begin seg_ment<= 7'h12; end else if(sel_data==3)begin seg_ment<= 7'h06; end else if(sel_data==4)begin seg_ment<= 7'h4c; end else if(sel_data==5)begin seg_ment<= 7'h24; end else if(sel_data==6)begin seg_ment<= 7'h20; end else if(sel_data==7)begin seg_ment<= 7'h0f; end else if(sel_data==8)begin seg_ment<= 7'h00; end else if(sel_data==9)begin seg_ment<= 7'h04; end end |
当然,也可以写成case的形式,结果都是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin segment<= 7'h00; end else begin case (sel_data) 0 : segment <= 7'h01; 1 : segment <= 7'h4f; 2 : segment <= 7'h12; 3 : segment <= 7'h06; 4 : segment <= 7'h4c; 5 : segment <= 7'h24; 6 : segment <= 7'h20; 7 : segment <= 7'h0f; 8 : segment <= 7'h00; 9 : segment <= 7'h04; default : segment <= 7'h00; endcase end end |
sel_data从m_g、m_s、f_g、f_s、s_g和s_s中选取。当cnt1=0,即数码管0显示时,sel_data的值为m_g;当cnt1=1,即数码管1显示时,sel_data的值为m_s;当cnt1=2,即数码管2显示时,sel_data的值为f_g;当cnt1=3,即数码管3显示时,sel_data的值为f_s;当cnt1=4,即数码管4显示时,sel_data的值为s_g;当cnt1=5,即数码管5显示时,sel_data的值为s_s。为此,sel_data的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end |
接下来我们设计m_g、m_s、f_g、f_s、s_g和s_s信号,按照常识,秒个位m_g的变化规律如下:
图 294
秒个位的数据是0、1、2~9、0这样有规律的加1变化,很明显可以看出这个m_g其实就是一个计数器,并且这个计数器每隔1秒时间才变化加1一次。1秒时间计时也需要一个计数器,设为cnt2,则cnt2是一直加1的,即可以写成assign add_cnt2 = 1。cnt2要数1秒时间,本工程的工作时钟是50MHz,即周期为20ns,计数器计数到1_000_000_000/20=50_000_000个,我们就能知道1秒时间到了,所以cnt1的周期是 50_000_000个。有了cnt1,我们就知道m_g这个计数器的加1条件是1秒时间到了,即end_cnt2==1,要计数10个。综上所述,可以写出cnt2和m_g的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin cnt2 <= 0; end else if(add_cnt2)begin if(end_cnt2) cnt2 <= 0; else cnt2 <= cnt2 + 1; end end
assign add_cnt2 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ; |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ; |
接下来思考秒十位m_s,m_s的变化情况如下:
图 295
m_s秒十位的指示,很明显每隔10秒就会加1,很明显,m_s也是一个计数器,加1条件是10秒时间到,也就是end_m_g。该计数器周期性是数6个。所以代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ; |
接下来思考分个位f_g,f_g的变化情况如下:
图 296
f_g分个位的指示,很明显每隔1分钟即60秒就会加1,很明显,f_g也是一个计数器,加1条件是60秒时间到,也就是end_m_s。该计数器周期性是数10个。所以代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ; |
接下来思考分十位f_s,f_s的变化情况如下:
图 297
f_s分十位的指示,很明显每隔10分钟就会加1,很明显,f_s也是一个计数器,加1条件是10分钟到,也就是end_f_g。该计数器周期性是数6个。所以代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ; |
接下来思考小时个位s_g,s_g的变化情况如下:
图 298
s_g小时个位的指示,很明显每隔1小时即6分钟就会加1,很明显,s_g也是一个计数器,加1条件是1小时也就是60分钟时间到,也就是end_f_g。
接下来我们需要好好思考这个小时个位,它的周期是多少。有读者认为是10个,因为是小时个位会从0~9这样变化;有读者认为是24个,因为一共是24小时;有读者认为是4个,因为小时个位会从0~4变化。
认为是24小时的读者,没有认识到我们只是看小时个位的变化,而不是小时个位和小时十位整体,整体才是24小时。我们盯着小时个位,会发现它是这么一个规律:0~9,0~9,0~3,0~9,0~9,0~3。因此正确的答案是,有时候周期是10,有时候是4,也就是说周期性会变的。按照明德扬的变量法,设其周期是x,则可以写出s_g的代码如下,而x是怎么来,我们先不考虑,后期再说。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ; |
接下来思考小时十位s_s,s_s的变化情况如下:
图 299
s_s小时十位的指示,很明显当小时个位要清零时(注意不是每隔10小时),s_s就会加1,很明显,s_s也是一个计数器,加1条件是小时个位要清零了,也就是end_s_g。该计数器周期性是数3个。所以代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ; |
最后,我们再考虑x,也就是小时个位的周期性是多少,并取决于什么因素。我们可以发现,小时个位是0~9,0~9,0~3,0~9,0~9,0~3,也就是周期性是10或者是4。至于是10还是4,则取决于小时十位,即s_s。当时钟的小时十位显示为2时,小时个位只要数到3就行了,没有25点的。综上所术,发s_s为2时,x=4,否则x=10。所以x的代码如下:
1 2 3 4 5 6 |
always @(*)begin if(s_s==2) x = 4; else x = 10; end |
此次,主体程序已经完成。接下来是将module补充完整。
3.3 信号定义
接下来定义信号类型。
cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为100_000,需要用17根线表示,即位宽是17位。add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; |
cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为6,需要用3根线表示,即位宽是3位。add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:
1 2 3 |
reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1; |
seg_sel是用always方式设计的,因此类型为reg,其一共有8根线,即位宽为8。因此代码如下:
1 |
reg [ 7:0] seg_sel ; |
seg_ ment是用always方式设计的,因此类型为reg,其一共有7根线,即位宽为7。因此代码如下:
1 |
reg [ 6:0] seg_ment ; |
sel_data是用always设计的,所以类型为wire。其最大值为9,所以需要4位位宽。代码如下:
1 |
reg [ 3:0] sel_data ; |
cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为50_000_000,需要用26根线表示,即位宽是26位。add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ; |
m_g是用always产生的信号,因此类型为reg。m_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_m_g和end_m_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ; |
m_s是用always产生的信号,因此类型为reg。m_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_m_s和end_m_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ; |
f_g是用always产生的信号,因此类型为reg。f_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_f_g和end_f_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ; |
f_s是用always产生的信号,因此类型为reg。f_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_f_s和end_f_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ; |
s_g是用always产生的信号,因此类型为reg。s_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_s_g和end_s_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ; |
s_s是用always产生的信号,因此类型为reg。s_s计数的最大值为2,需要用2根线表示,即位宽是2位。add_s_s和end_s_s都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 3 |
reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; |
x是用always产生的信号,因此类型为reg。x计数的最大值为10,需要用4根线表示,即位宽是4位。
1 |
reg [ 3:0] x ; |
至此,整个代码的设计工作已经完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
module my_shizhong( clk , rst_n , seg_sel , seg_ment );
input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0] seg_ment;
reg [ 7:0] seg_sel ;
reg [ 6:0] seg_ment ;
reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1;
reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ;
reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ;
reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ;
reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ;
reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ;
reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ;
reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; reg [ 3:0] sel_data ;
reg [ 3:0] x ;
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin cnt0 <= 0; end else if(add_cnt0)begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0 + 1; end end
assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0==100_000-1 ;
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin cnt1 <= 0; end else if(add_cnt1)begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1 + 1; end end
assign add_cnt1 = end_cnt0; assign end_cnt1 = add_cnt1 && cnt1==6-1 ;
always @(posedgeclk or negedgerst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe; end else if(cnt1==0)begin seg_sel<= 8'hfe; end else if(cnt1==1)begin seg_sel<= 8'hfd; end else if(cnt1==2)begin seg_sel<= 8'hfb; end else if(cnt1==3)begin seg_sel<= 8'hf7; end else if(cnt1==4)begin seg_sel<= 8'hef; end else if(cnt1==5)begin seg_sel<= 8'hdf; end end
always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end
always @(posedgeclk or negedgerst_n)begin if(rst_n==1'b0)begin seg_ment<= 7'h01; end else if(sel_data==0)begin seg_ment<= 7'h01; end else if(sel_data==1)begin seg_ment<= 7'h4f; end else if(sel_data==2)begin seg_ment<= 7'h12; end else if(sel_data==3)begin seg_ment<= 7'h06; end else if(sel_data==4)begin seg_ment<= 7'h4c; end else if(sel_data==5)begin seg_ment<= 7'h24; end else if(sel_data==6)begin seg_ment<= 7'h20; end else if(sel_data==7)begin seg_ment<= 7'h0f; end else if(sel_data==8)begin seg_ment<= 7'h00; end else if(sel_data==9)begin seg_ment<= 7'h04; end end
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin cnt2 <= 0; end else if(add_cnt2)begin if(end_cnt2) cnt2 <= 0; else cnt2 <= cnt2 + 1; end end
assign add_cnt2 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ;
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ;
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ;
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ;
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ;
always @(posedgeclk or negedgerst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ;
always @(posedgeclk or negedgerst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ;
always @(*)begin if(s_s==2) x = 4; else x = 10; end
endmodule |
下一步是新建工程和上板查看现象。
4 综合与上板
4.1 新建工程
首先在d盘中创建名为“my_shizhong”的工程文件夹,将写的代码命名为“my_shizhong.v”,顶层模块名为“my_shizhong”。
图 300
图 301
然后打开QuartusⅡ,点击File下拉列表中的New Project Wzard...新建工程选项。
图 302
3.再出现的界面中直接点击Next。
图 303
4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,然后点击Next,在出现的界面选择empty project。
图 304
图 305
5.之后是文件添加界面。添加之前写的“my_shizhong.v”文件,点击右侧的“Add”按钮,之后文件还会出现在下方大方框里,之后点击“Next”。
图 306
器件型号选择界面。在上方红色箭头处选择Cyclone ⅣE,在中间红色箭头处选择EP4CE15F23C8,然后点击“Next”。
图 307
EDA工具界面。直接点击“Next”。
图 308
8.之后出现的界面,点击“Finish”。
图 309
4.2 综合
1.新建工程步骤完成后,就会出现以下界面。选中要编译的文件,点击编译按钮。
图 310
2.编译成功后会出现一下界面。
图 311
4.3 配置管脚
图 312
在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。
图 313
在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。
器件 |
信号线 |
信号线 |
FPGA管脚 |
内部信号 |
U6,U7 |
SEG_E |
SEG0 |
Y6 |
seg_ment[2] |
SEG_DP |
SEG1 |
W6 |
未用到 |
|
SEG_G |
SEG2 |
Y7 |
seg_ment[0] |
|
SEG_F |
SEG3 |
W7 |
seg_ment[1] |
|
SEG_D |
SEG4 |
P3 |
seg_ment[3] |
|
SEG_C |
SEG5 |
P4 |
seg_ment[4] |
|
SEG_B |
SEG6 |
R5 |
seg_ment[5] |
|
SEG_A |
SEG7 |
T3 |
seg_ment[6] |
|
DIG1 |
DIG_EN1 |
T4 |
seg_sel[0] |
|
DIG2 |
DIG_EN2 |
V4 |
seg_sel[1] |
|
DIG3 |
DIG_EN3 |
V3 |
seg_sel[2] |
|
DIG4 |
DIG_EN4 |
Y3 |
seg_sel[3] |
|
DIG5 |
DIG_EN5 |
Y8 |
seg_sel[4] |
|
DIG6 |
DIG_EN6 |
W8 |
seg_sel[5] |
|
DIG7 |
DIG_EN7 |
W10 |
seg_sel[6] |
|
DIG8 |
DIG_EN8 |
Y10 |
seg_sel[7] |
|
X1 |
|
SYS_CLK |
G1 |
clk |
K1 |
|
SYS_RST |
AB12 |
rst_n |
配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
4.4 再次综合
图 314
在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。
图 315
出现上面的界面,就说明编译综合成功。
4.5 连接开发板
图中,下载器接入电脑USB接口,电源接入电源,然后摁下蓝色开关。
图 316
4.6 上板
1.双击Tasks一栏中”Program Device”。
图 317
2.会出现如下界面,点击add file添加.sof文件,点击“Start”,会在“Progress”出显示进度。
图 318
3.进度条中提示成功后,即可在开发板上观察到相应的现象。