一、了解不同的变量类型
①、wire和reg的区别
verilog中的变量类型有wire类型和reg类型。在实际的电路中wire类型对应的就是一根导线,只存在传输的作用。而reg类型在实际电路中表示寄存器,可用来存储数据的。
②、如何对变量进行赋值呢?
学习C语言可以知道赋值就是用=号,比方int a = 15;//将15赋值给a;然而在verilog语法中也有 =(阻塞赋值) 这种赋值方式,当然除了这种方式还有 <=(非阻塞赋值)这种方式;阻塞赋值 = (例如 a = b;):a的值在赋值语句执行完后立刻就改变的,用在组合逻辑;非阻塞赋值 <= (例如 a <= b ;):a的值想要发生变化,必须要在时钟的上升沿/下降沿,用在时序逻辑;
③、 阻塞与非阻塞
阻塞:假设信号pi_a在导线上传输的时间为5ns,pi_b在导线上传输的时间是10ns,而po_c的值因为pi_b晚到5ns而被阻塞。
非阻塞:假设信号pi_a在导线上传输的时间为5ns,pi_b在导线上传输的时间是10ns,虽然po_c存在阻塞问题,但是Q端输出的结果只取决于clk上升沿/下降沿到来时D端的瞬时结果,因次Q输出不存在阻塞。
用户想要在时钟的上升沿到来时输出变化那就用非阻塞,输出的变化取决于输入变化,输出随之变换那就用非阻塞。组合逻辑只能用阻塞赋值,时序逻辑只能用非阻塞赋值。
二、变量位宽的概念
①、各个系统默认值
在没有标注的情况下,位宽默认值为1。
input a;
reg clk;
reg key1,key2;
②、变量位宽声明方式
wire [高位:低位]变量名;
reg [高位:低位]变量名;
wire [高位:低位]变量名[denth:0];
reg [高位:低位]变量名[denth:0];
变量名[i]; 默认认为是第几个数据,而不是该数据的第几位,除非再加一个括号,才表示第几位
③、表明位宽的情况下,赋值方式
wire [7:0] value;
assign value = 8'd255 = 8'hff = 8'b1111_1111 = 'd255;
assign value = 255;
reg [7:0] value;
assign@ (posedge clk) begin
value <= 8'd255;
value <= 8'hff;
value <= 8'b1111_1111;
value <= 'd255;
end
④、两模块之间例化,不定义变量直接引用
一位位宽的wire类型变量在不输入也不输出的情况下,可以不声明,但是这种做法不推荐。
⑤、常用的变量定义为参数
parameter后面的参数建议定义成大写字符,一般情况下,变量定义为小写字母,参数定义为大写字母。
parameter LEN 100;//相当于C语言中的
三、赋值语句
①、assign和always赋值语句的区别
1、assign赋值语句:
A、assign只能实现组合逻辑;
B、assign语句后面只能跟一条语句;
C、wire型变量,必须在assign中进行赋值。
assign 表达式;
2、always赋值语句:
A、always赋值语句不但能实现时序逻辑,还能实现组合逻辑;B、reg型变量,必须在always中进行赋值;
C、always敏感信号可分为边沿触发和电平触发;
D、电平触发时always@(*) = always@(a or b) = always@(a ,b);
E、电平触发:敏感列表触发电平信号不全会导致锁存器的产生;
F、always@(边沿触发):非阻塞赋值语句,对应时序逻辑电路。G、always@(电平触发):阻塞赋值语句,对应组合逻辑电路。
注意:一个变量只能在一个always语句块中被赋值,不能在两个语句块中赋值,因为always语句块是并行执行的,会产生冲突。
always@(敏感信号)begin
语句;
end
②、assign和always赋值语句的例子
module variable_assignment(
input wire clk,
input wire a,
input wire b,
output wire c,
output reg c1,
output reg c2
);
//assign combinatorial logic
assign c = a & b;
//always combinatorial logic
always@(*)begin
c1 = a & b;
end
always@(a,b)begin//equivalence
c1 = a & b;
end
always@(a or b)begin//equivalence
c1 = a & b;
end
//sequential logic
always @(posedge clk)begin
c2 <= a & b;
end
endmodule
③、inital语句
inital语句是初始化语句,但是不能写在可综合模块中,因为有些综合软件无法综合inital语句;inital语句中被赋值的变量必须是reg类型。
//举例子
initial begin
clk = 0;
a = 0;
b = 0;
end
④、reg类型变量的初始值问题
reg类型的变量是可以赋初始值,但是有个条件就是这个reg类型的变量不能在后面的语句中被综合为了组合逻辑,只有reg是时序逻辑才可以在fpga里赋值初始值。reg变量既可以生成时序逻辑,也可以生成组合逻辑。reg赋初始值时要用阻塞赋值。
四、运算符合
①、算数运算符(+,-,*,/,%)
( 注意:在功能模块中尽量减少使用 ,/,%这三种运算。*)
②、关系运算符(>、=、<=、>=、==、!=)
( 注意:小于等于(<=),在条件判断语句中和非阻塞赋值有点像,所以一定要注意 )
③、逻辑运算符(&&、||、!)
④、位运算符(&、|、~)
⑤、三目/条件运算符((x)?x:x)
⑥、赋值运算符(= 、<=)
⑦、移位运算符(>>、<<)
(注意:多次左移,会使数据溢出,例如0001,在左移4次后会变为0000,丢失一位数据,回到0。这在循环右移的代码中就会节约一个选择器。)
⑧、位拼接运算符({})
可以利用位拼接符实现循环左移的方法,示例如下:
module operational_sign_homework(
input wire clk,
input wire rst,
output reg [7:0]po_a = 1'b0
);
always@(posedge clk) begin
if(rst == 1'b1)
po_a <= 8'd1;
else if(po_a == 8'b1000_0000)
po_a <= 8'b0000_0001;
else
po_a <= po_a << 1;
always@(posedge clk) begin
if(rst == 1'b1)
po_a <= 8'd1;
else
po_a <= {po_a[6:0],po_a[7]};
end
endmodule
五、条件判断语句
条件判断语句有if else 和 case endcase这两种,这两种语句的赋值都必须放在always语句中。
①、if else语句的注意事项
A、if else语句叠加不能太多,老师的经验是不能大于8级,因为if else是串行执行的语句,如果太多就会导致线路的延时太多,进而导致时序违例。
B、if else语句书写的时候要考虑优先级。
C、if else语句结尾尽量以else结尾,这样可以避免锁存器的产生。
什么是时序违例?
也就是说:当前时钟上升沿我把信号给了当前的电路模块,因为当前个电路模块时延很严重,经过一个时钟周期后,到了下一个时钟的上升沿,正常情况下你这个模块就应该输出处理好的输出信号,结果因为时延,我在输出端口检测不到输出信号。
通常表现上是:建立时间不满足要求通常是因为组合逻辑处理时间太长、保持时间不满足要求通常是因为组合逻辑处理时间太短。
②、begin end问题
begin end就是括号等价于C语言中{}
always@(posedge clk) begin
if(rst == 1'b1)
po_a <= 8'd1;
else
po_a <= 8'd0;
end
//需要注意的是,加begin end和不加begin end都是对的!!!!
always@(posedge clk)
if(rst == 1'b1)
po_a <= 8'd1;
else
po_a <= 8'd0;
③、case endcase语句的注意事项
A、case语句结尾必须时endcase。
B、case语句是并行执行,(多路选择器)所以多种条件时使用case endcase语句比使用if else速度要快,但是case endcase也不能非常多。
C、case的条件在没有完全列清楚的情况下,要用default语句。
//可以带上begin end
always@(posedge clk)
case(po_a)
1: po_a <= 8'd1;
2: po_a <= 8'd2;
default: po_a <= 8'd0;
endcase
//带上begin end
always@(posedge clk)
case(po_a)
1:begin
po_a <= 8'd1;
end
2: po_a <= 8'd2;
default: po_a <= 8'd0;
endcase
六、、赋值分隔符问题
当在数值前面声明了位宽以及进制,这种表达方式可以放数值的分隔符。下面两种表达方式都可以,这两条语句完全等价:
parameter END_COUNT = 'd49_999_999;
parameter END_COUNT = 26'd49_999_999;//这两条语句完全等价
当数值前面没有带位宽以及进制,系统默认为32位的十进制数据,这个时候无法再对数据放置分隔符。
错误的表达方式:
parameter END_COUNT = 49_999_999;//错误的表达方式
七、编写测试代码时常用的语句
①、随机数产生代码
a = {$random}%256
②、初始化代码模块
initial begin
clk = 0;
rst_n = 0;
rst_n = 1;
end
③、时钟产生代码
always
④、延时代码
#200;
八、小结
以上是学习FPGA的一些入门知识,一起学习,与大家共勉!