问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Verilog语言学习指南:从基础到实战

创作时间:
作者:
@小白创作中心

Verilog语言学习指南:从基础到实战

引用
1
来源
1.
https://www.cnblogs.com/namezhyp/p/18046656

本文是一篇关于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连续赋值:右侧的值改变后,左侧立刻更新
  • 阻塞赋值(=):只在initialalways里使用,这一句执行后,右边的表达式立刻赋值给左侧,然后执行下一句
  • 非阻塞赋值(<=):只在initialalways里使用,直到当前块全部执行完,才统一更新左侧变量

组合逻辑使用@(*)表示敏感列表自动维护,一般配合阻塞赋值。时序逻辑使用@(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

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号