Verilog语言学习指南:从基础到实战
Verilog语言学习指南:从基础到实战
本文是一篇关于Verilog语言学习的详细教程,通过具体的代码示例和解释,帮助读者理解Verilog语言的各个方面。文章内容包括基本逻辑、向量位拓展、三种赋值方式、组合逻辑与时序逻辑、生成语句、时钟、状态机等内容。
基本逻辑
Verilog语言中的基本逻辑运算符包括与(&
)、或(|
)、异或(^
)、与非(nand
)、或非(nor
)、同或(xnor
)等。这些运算符的使用方式与C语言类似,但需要注意运算的先后顺序。
例如,当有多个输入时,需要先对所有输入进行与运算,然后再进行非运算。从上面的元件图中也可以直观地理解这个过程。
Verilog与C语言的区别
Verilog语言和C语言的编写逻辑比较接近,但在核心思想上有些区别。在不使用代码块的情况下,Verilog代码描述的是模块内部的关系,而不是对运算过程的描述。这就意味着,代码顺序无关紧要,爱写什么顺序就写什么顺序。
如果需要代码有顺序应该怎么办呢?用always initial
这样的代码块,代码块内部代码有顺序关系。
向量的位拓展
在Verilog中,向量的位拓展需要注意位宽的定义。例如,{5{1'b1}}
是合法的,而{5{1}}
就不行。在定义形参时,中间是逗号,不是分号。为了避免误会,可以写成C的形式避免写错。
以下是一个一位全加器的例子:
module add1(
input a,b,
input cin,
output sum,cout);
assign sum=a^b^cin;
assign cout=(a&b)|(a&cin)|(b&cin);
endmodule
从代码里可以看到,一个一位全加器有a b两个输入变量和cin进位变量,输出则是cout,指示是否需要进位,sum决定当前位。
减法器的设计
减法器的设计和加法器其实一样,只是在b的输入上增加了一个xor异或门,以及一位的符号变量sub。这里需要注意的是,第一个add16的cin输出是sub,不是0。
sub是0的时候,同普通的32位加法器,sub是1的时候,第一个加法器需要考虑sub作为cin也要一并加上,此外b的每一位都与1进行了异或。
正确的代码如下:
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
//module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
wire [31:0] real_b;
wire in_out;
assign real_b= b^{32{sub}};
add16 test1(.a(a[15:0]), .b(real_b[15:0]), .cin(sub), .cout(in_out), .sum(sum[15:0]));
add16 test2(.a(a[31:16]), .b(real_b[31:16]), .cin(in_out), .cout(), .sum(sum[31:16]));
endmodule
三种赋值方式
在Verilog中,有三种赋值方式:assign
连续赋值、阻塞赋值(=
)和非阻塞赋值(<=
)。
assign
连续赋值:右侧的值改变后,左侧立刻更新- 阻塞赋值(
=
):只在initial
和always
里使用,这一句执行后,右边的表达式立刻赋值给左侧,然后执行下一句 - 非阻塞赋值(
<=
):只在initial
和always
里使用,直到当前块全部执行完,才统一更新左侧变量
组合逻辑使用@(*)
表示敏感列表自动维护,一般配合阻塞赋值。时序逻辑使用@(posedge clock)
表示上升沿为敏感列表,一般配合非阻塞赋值。
生成语句
利用生成语句可以很方便地将1位全加器拼接出多位全加器。例如,下面的代码展示了如何利用生成语句将1位全加器拼接出100位全加器:
module top_module(
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );
genvar i; // 定义一个循环变量i
generate
for (i = 0; i < 100; i = i + 1) begin: ripple // 给begin...end块起一个名字ripple
if (i == 0) // 如果是第一个全加器,cin就是模块的输入cin
add1 adder0 (.a(a[0]), .b(b[0]), .cin(cin), .cout(cout[0]), .sum(sum[0]));
else // 如果不是第一个全加器,cin就是上一个全加器的cout
add1 adder1 (.a(a[i]), .b(b[i]), .cin(cout[i-1]), .cout(cout[i]), .sum(sum[i]));
end
endgenerate
endmodule
时序逻辑
在时序逻辑中,需要注意复位信号的处理。异步复位信号可以直接连到复位端,而不需要和时钟同步就可以改变信号。同步复位信号则需要延迟一个时钟周期才能改变输出。
锁存器
锁存器(Latch)是电平敏感的,不依赖边沿。在定义锁存器时,需要使用非阻塞赋值(<=
)。
状态机
在设计状态机时,状态转移的部分放在组合逻辑,外部的复位信号则放在时序逻辑。根据要求的同步复位和异步复位不同,异步复位需要在时序逻辑的敏感条件里增加reset信号的上升沿,同步不需要。
参考资料
本文原文来自cnblogs