Verilog学习笔记

这篇笔记记录了Verilog基本知识的学习

参考的资料和网站

Verilog在线仿真平台

Bilibili视频

Verilog语法简介


Chapter 1 基本认识

Verilog 是一种硬件描述语言(HDL,Hardware Description Language)

 

Verilog描述的是门电路的逻辑,在Verilog编写好后,往往还需要使用EDA工具形成与、或、非门构成的逻辑网表

当然,在一些精细的设计场景,往往也有直接设计门电路的案例

 

Chapter 2 基本语法

这部分的内容跟着Verilog在线仿真平台学习

Basics

Wires

需要把每个节点想象成一个连接线,Verilog描述了这些连接线怎么相互连接

 

连接两个连接线的语句是

assign out = in ;

 

这句话把outin连接在了一起

当然,需要连接多个连接线时,可以使用如下语句

assign {w,x,y,z} = {a,b,b,c} ;

基本逻辑门

非门

使用非门连接的两个输出如下

assign out = ~ in ; //按位非
assign out = ! in ; //逻辑非

 

与门

使用与门连接的两个输出如下

assign out = a & b ; //按位与
assign out = a && b ; //逻辑与
或门

使用或门连接的两个输出如下

assign out = a | b ; //按位或
assign out = a || b ; //逻辑或
异或门

使用异或门连接的两个输出如下

assign out = a ^ b ; 

 

中间连接线

在逻辑较为复杂时候,需要声明中间连接线

语法如下

 wire not_in;

复杂组合逻辑实现

使用上面提到的方法可以实现复杂的组合逻辑

这个芯片的实现代码如下

module top_module ( 
    input p1a, p1b, p1c, p1d, p1e, p1f,
    output p1y,
    input p2a, p2b, p2c, p2d,
    output p2y );
	
    wire tp1,tp2,tp3,tp4;
    assign p2y=tp1 || tp2;
    assign p1y=tp3 || tp4;
    assign tp1=p2a&p2b;
    assign tp2=p2c&p2d;
    assign tp3=p1a&p1b&p1c;
    assign tp4=p1d&p1e&p1f;

endmodule

 

Vectors

Vectors 用于使用一个名称对相关信号进行分组

声明Vector

声明向量的语法如下

type [upper:lower] vector_name;
//type通常是wire或reg
//example
wire [99:0] my_vector;      // Declare a 100-element vector
assign out = my_vector[10]; // Part-select one bit out of the vector

 

在关系图中,旁边带有数字的刻度线表示向量(总线)的宽度

Vector的字节序

Vector的字节序是最低有效位是具有较低的索引(小端,[3:0])还是较高的索引(大端, [0:3])。在 Verilog 中,一旦 vector 声明了特定的字节序,它必须始终以相同的方式使用。

隐式网络

在代码中使用一个没有事先声明(比如用 wirereg 关键字)的信号时,Verilog 不会报错,而是会自动为你创建一个默认的、1位宽度的 wire 类型信号。

wire [2:0] a, c;    // 声明了 a 和 c 是3位的向量 (vector)

assign a = 3'b101;  // 给 a 赋值 101

assign b = a;       // 变量 b 没有被声明。
                    // Verilog 不会报错,而是隐式创建了一个1位的 wire `b`。
                    // 它会将 a 的值 (101) 赋给 b,但因为 b 只有1位
                    // 所以只会取 a 的最低位,即 1。
                    // 因此,b 的值实际上是 1

assign c = b;       // 将 b (值为1) 赋给3位的 c。
                    // 因此 c 的值变成了 3'b001。

// 本意是想让 c 的值也等于 a (101)。
// 但由于隐式网络 b 的存在,c 的值最终错误地变成了 001
// 在编译时不会有任何警告或错误

 

为了发现这个报错,可加入添加 default_nettype none,使第二行代码成为错误

Vector索引的访问与取值

Verilog定义了两种方式(见上文的字节序)来定义Vector的访问

[0:3][低位:高位] 大端格式

[3:0][高位:低位] 小端格式

在取低位和高位的时候需要注意

同样Verilog支持负索引,此时需要依据负数的大小确定大小端格式

部分选择

{}用于连接Vector

assign out = {in[7:0] , in[15:8] };

 

需要注意的是,当out超过16位时,发生补0

不足16位时,发生截断

  • 补充知识:进制的表示(位数+符号+数)
二进制 八进制 十进制 十六进制
b(Binary) o(Octal) d(Decimal) h(Hexadecimal)
3’b111 2’o11 4’d10 4’ha

需要指定位宽,当不指定时,编译器自己一句上下文决定

位宽>实际宽度,补0;

位宽<实际宽度,截断;

连接运算符

连接运算符可以复用Vector

assign out = {3{in}} ;
//这句话把in复制了3次赋值给out
//在数组中需要作为整体使用
assign out ={3'd5, {2{3'd6}}} ;

 

Module

基本认识

Module通过模块化的方式编写代码

之前所有的代码都是通过Module编写

Module的基本结构如下

module mod_a ( input in1, input in2, output out );
    // Module body
endmodule

Module的存在使一个模块可以复用,类似于一个可复用的数字单元

将外部连接线连接到Module

有两种方式

mod_a instance1 ( wa, wb, wc );
//这种方法需要严格对应各个参数的位置

mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) );
//这种方法用input和output来区分输入输出,因此不需要关心位置

其中,实例化mod_a成instance1和instance2是必须的,相当于给一个元件实际化成了实际使用的器件

通俗的理解:触发器D1,D2,D3

通常这种结构可以嵌入另外一个Module成为一个大的芯片单元

这段用3个D触发器形成的3位寄存器的代码实现

module top_module ( input clk, input d, output q );
    wire q1,q2;
    my_dff d1(clk,d,q1);
    my_dff d2(clk,q1,q2);
    my_dff d3(clk,q2,q);
endmodule

 

加法器

加法器是使用module的典型,高位的加法器可以用低位的加法器级联而成

具体的方法是将高位和低位分别输入两个加法器中,同时处理进位

 

由图可知,16个一位加法器组成了16位加法器,随后两个16位加法器又组成了32位加法器

这个组合单元的实现代码

module top_module (
    input [31:0] a,
    input [31:0] b,
    output [31:0] sum
);//
    wire cout1;
 	wire cout2;
    wire [15:0] sum1,sum2;
    add16 instance1(a[15:0],b[15:0],0,sum1,cout1);
    add16 instance2(a[31:16],b[31:16],cout1,sum2,cout2);
    assign sum={sum2,sum1};
endmodule

module add1 ( input a, input b, input cin,   output sum, output cout );
// Full adder module here
    assign{cout,sum}=a+b+cin;
endmodule

 

可控加减法器

可用控制信号sub控制加减法运算,其中取补码需要用到反转加一的操作,这个操作可以用异或被操作数,并将sub作为第一个加法器的进位来实现

这个器件实现了如下功能:

sub=1,执行a-b

sub=0,执行a+b

具体的实现代码如下

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,
    output [31:0] sum
);
    wire [31:0] b_real;
    wire cout1,cout2;
    wire [15:0]sum1,sum2;
    assign b_real=b ^ {32{sub}};
    add16 instance1(a[15:0],b_real[15:0],sub,sum1,cout1);
    add16 instance2(a[31:16],b_real[31:16],cout1,sum2,cout2);
    assign sum={sum2,sum1};
endmodule

 

Procedures

always语句

always语句和assign语句都可以用来赋值,但它们的使用情景不同

assign表示纯组合逻辑,表示硬件的硬连线

always可用于边沿触发等时序逻辑,它是变量变化时才执行的

always语句的基本使用如下,其中@(*)代表always在监测到后面任何一个变量变化后计算一次,@(posedge clk)代表监测到时钟上升沿后计算一次

always @(*);
always @(posedge clk);

需要注意的是,assign赋值的只能是常量(例如wire),always赋值的只能是变量(例如reg)

reg代表可赋值单元,可以代表线网,也可以代表触发器的状态等

阻塞赋值与非阻塞赋值

阻塞赋值(=):需要等一个变量更新后再计算下一个变量

非阻塞赋值(<=):所有变量并行地全部更新

一般规定,在always @(*)语句中,使用阻塞赋值,在always @(posedge clk)语句中,使用非阻塞赋值

数据选择器

使用always语句构造2-to-1 MUX

always @(*) begin
    if (condition) begin
        out = x;
    end
    else begin
        out = y;
    end
end

其中always监测condition,x,y三个变量,发生改变则开始执行begin到end间的语句

if语句检测condition是否为真,为真则开始执行begin到end间的语句

也可以简写成

assign out = (condition) ? x : y;

如果 (condition) 为真,那么 out 的值等于 ? 后面的 x

如果 (condition) 为假,那么 out 的值等于 : 后面的 y

需要注意的是,需要为所有的情况都指定输出,否则会导致latch

case语句

上述讲到的是if语句配合always使用

case语句也可以用于表示分支结构

always @(sensitivity_list) begin
    case (expression)
        value1: statement1; // 如果 expression 的值等于 value1,执行 statement1
        value2: statement2; // 如果 expression 的值等于 value2,执行 statement2
        ...
        valuen: statementn;

        default: default_statement; // 可选:如果以上所有值都不匹配,执行 default_statement
    endcase
end

 

casez语句

可以无视case输入量的某些位,从而减少case语句的使用次数

always @(*) begin
    casez (in[3:0])
        4'bzzz1: out = 0;   // in[3:1] can be anything
        4'bzz1z: out = 1;
        4'bz1zz: out = 2;
        4'b1zzz: out = 3;
        default: out = 0;
    endcase
end

这是一个优先编码器,输出最低位的1,逻辑是:识别到哪一位有1,就直接输出,因此case的排列顺序也要有要求,从低到高

More Verilog Features

三元运算符

(condition ? value_if_true : value_if_false)

计算condition的值,为真即取value_if_true,为假即取value_if_false

三元运算符可以描述许多的数字电路(这部分借鉴了AI的解说)

基本求值
(0 ? 3 : 5)  // 结果是 5,因为条件是 0 (false)
  • condition: 0:假
  • value_if_true: 3
  • value_if_false: 5
  • 因为条件为假,所以表达式的结果取 value_if_false,即 5
2-to-1 MUX
(sel ? b : a)  // 一个由 sel 控制的,在 a 和 b 之间选择的二路选择器
  • 如果 sel1 (真),输出 b
  • 如果 sel0 (假),输出 a
T-Flip-Flop
always @(posedge clk)
    q <= toggle ? ~q : q;  // T 触发器
  • toggle 信号是使能信号

    • 在时钟 clk 的上升沿,判断 toggle 信号
    • 如果 toggle1 (真),q 的下一个状态是它当前值的反相 (~q),实现翻转功能
    • 如果 toggle0 (假),q 的下一个状态是它当前值 (q),实现保持功能
FSM 状态转移逻辑
// 一个单输入 FSM 的状态转移逻辑
always @(*)
    case (state)
        A: next = w ? B : A;
        B: next = w ? A : B;
    endcase
  • 用于决定有限状态机(FSM)的下一个状态 next

    • 当处于状态 A 时 (state == A):

      • next = w ? B : A; :如果输入 w1,下一个状态转移到 B;否则,下一个状态保持在 A

    • 当处于状态 B 时 (state == B):



      • next = w ? A : B; :如果输入 w1,下一个状态转移到 A;否则,下一个状态保持在 B


       


Tri-state Buffer (三态缓冲器)
assign out = ena ? q : 1'bz; // 一个三态缓冲器

  • condition: ena (enable, 使能)



  • value_if_true: q (驱动正常信号)


  • value_if_false: 1'bz (高阻态)

    • 如果 ena1 (真),输出端口 out 被正常驱动,其值为 q
    • 如果 ena0 (假),输出端口 out 被置为高阻态 (z)。
嵌套实现 3-to-1 MUX
// 一个 3-to-1 MUX
((sel[1:0] == 2'h0) ? a :         // 如果 sel=00, 输出 a
 (sel[1:0] == 2'h1) ? b :         // 否则, 如果 sel=01, 输出 b
                      c )          // 否则 (sel=10 或 11), 输出 c

用于实现多路选择,等价于下面的 if-else if-else 结构:

if (sel[1:0] == 2'b00) begin
    out = a;
end 
else if (sel[1:0] == 2'b01) begin
    out = b;
end 
else begin
    out = c; // 包括了 sel=10 和 sel=11 的情况
end
  1. 检查 sel[1:0] == 2'h0
  2. 如果为真,整个表达式的结果就是 a
  3. 如果为假,整个表达式的结果是第二个(内层)三元运算符 (sel[1:0] == 2'h1) ? b : c 的值
  4. 接着对内层进行判断,如果 sel[1:0] == 2'h1 为真,结果为 b;否则为 c

归纳运算符

可用于多位相与

& a[3:0]     // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0]     // OR:  b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0]     // XOR: c[2]^c[1]^c[0]

for循环

for (initialization; condition; step) begin
    // 循环体内的语句
end

括号里的三个内容与C语言几乎一样,可直接套用

这个循环变量需要声明为interger(整形)

integer i

 

避免多重驱动

与c语言不同,Verilog中的每一段代码都是并行执行的,这就带来了一个问题:不能在两个模块同时对同一根wire进行赋值,从硬件的角度看,一根电线不可能同时接受高低两个电平,这会带来冲突

generate语句

同样在generate语句内部引入for/if,但是与for不同

  • always:描述行为,会被eda工具展开成组合或时序逻辑电路
  • generate:并行构造多个硬件实例
generate
    // 声明一个专门用于 generate 块的循环变量
    genvar i; 

    // for 循环
    for (i = 0; i < N; i = i + 1) begin : block_name 
        // 在这里放置要重复生成的硬件代码
        // block_name是给begin起的名字,方便调试
    end
endgenerate

注意:generate语句内部的赋值需要使用assign

在generate模块中使用循环模块化硬件模块时,可以不加声明,编译器会自动分配


以上是Verilog的基本语法部分,之后还剩一些数字电路的练习题,这部分内容会缓慢更新

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Puff
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇