verilog的代码的编写要根据FPGA的运算规程来编程,最开始可以模仿别人的代码,然后在学习的过程中遇见更好的编写方式就记下来,并学习它,把它融汇贯通到你自己的代码中去,跟多的是一个公式化的编写方式,随着学习的时间增长,记下的公式也越来越多,那时候编写代码就是一个不断套公式的过程,所以不仅要自己写代码还要多看看别人的代码,你才能越来越强。
在verilog中一些常用的公式,1,例如if,else模块如果多层嵌套代码就显得比较臃肿,可以用“?”来简化它,使代码更加的简洁明了
always@(posedge clk)if(a == 1) d <= 1;
else if(b == 1) d <= 2;
else if(c == 1) d<= 3;
always@(posedge clk)if(a == 1) ? 1 : (b == 1) ? : 2 : 3;
2,计数器,在verilog中计数器也是有公式的,记住这个万能公式,在以后编写中就可以随便套用,cnt根据时钟进行计数,cnt_max为你要计数的最大值,flag为最大值的标志信号,例如你需要一个1秒中的标志型号,当时钟为50MHz时,cnt_max为49000000,之后需要用到计数器的时候就可以直接判断flag是否等为1。
reg [31:0] cnt;
wire flag = (cnt == cnt_max);
always@(posedge clk) cnt <= (flag) ? 0 : cnt + 1;
3,移位操作,例如流水灯这种每种状态就只有一个位发生变化,无需用case定义每一种状态亮那个灯,直接用移位操作即可完成,这只是一个简单的理解,更深刻的用法自己在写代码中去领悟。
reg [31:0] cnt;
wire flag = (cnt == cnt_max);
always@(posedge clk) cnt <= (flag) ? 0 : cnt + 1;
reg [7:0] led;
always@(posedge clk) led <= (led == 0) ? 1 : led << flag;
4,状态机,状态机就是在不用的状态时,执行不同的任务,例如,数码管的动态显示,数码管由8位的段选型号和位选信号驱动,状态机的不同的状态就是位选信号选择不同的数码管,每一个状态其段选信号不同,当位选信号便利的时候,就可以显示出动态数码管了。
//flag为位选信号,data为位选信号,当段选不同时位选也不同
always@(posedge clk)
case(flag)
0: data <= n0;
1: data <= n1;
2: data <= n2;
3: data <= n3;
4: data <= n4;
5: data <= n5;
6: data <= n6;
7: data <= n7;
8: data <= n8;
9: data <= n9;
default data <= 8'd1111_1111;
endcase
5,计数器低位计数,高位计时,类如在数码管的动态显示中,段选信号需要快速不停的便利,使我们的视觉产生一直亮着的错觉,我们可以用计数器的低位来产生计数,高位表示状态,例如一个18位计数器,前15位计数,15位计满后16加1,利用16到18组成一个38译码器,刚好与数码管的位选对应,这样就是用最简单方法写出动态数码管。
always @(posedge clk)cnt <= cnt+1;
//计数器第17位到第15位为位选信号,3位刚好有8种状态对应8位数码管,
wire [2:0] flag = cnt[17:15];
//根据位选信号不同,产生对应的段选信号
always@(posedge clk)
case(flag)
0: data <= n0;
1: data <= n1;
2: data <= n2;
3: data <= n3;
4: data <= n4;
5: data <= n5;
6: data <= n6;
7: data <= n7;
8: data <= n8;
9: data <= n9;
default data <= 8'd1111_1111;
endcase
6,每个alwyas块尽量只对一个寄存器赋值,控制他的可以是任何的寄存器,这样编写的代码更加的简洁明了,更加的模块化。以及代码边学过程中,尽量不要出现小学3年纪以上的数学公式,例如乘除取余操作,2的倍数乘除可以使用移位的操作完成,因为这些公式不利于FPGA的运算,会消耗大量的逻辑资源。