Verilog基础教程学习笔记

2023-12-11

Verilog基础教程学习笔记。

verilog基础教程

1 数值表示

1. 逻辑电平

0:逻辑0或假

1:逻辑1或真

x或X:未知

z或Z:高阻

2. 数字

  1. 十进制:8’d1(负数:-8’d1)
  2. 二进制:8’b0(8’b0000_0000)
  3. 八进制:6’o0
  4. 十六进制:8’h0(8’h00)

3. 字符串

每个字符用单字节ASCII码表示

reg [5*8-1:0] str;
initial begin
    str = "hello";
end

2 数据类型

1. wire(线网)

  1. 定义:表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动
  2. 赋值:不能过程赋值(initial,always),只能连续赋值(assign),即需要驱动
  3. 连接:用于模块间的连接,input必须是wire类型
wire clk;

2. reg(寄存器)

  1. 定义:表示存储单元
  2. 赋值:不需要驱动,可在任何时候可以赋值
reg addr

3. 向量

字面意思,如下定义8bit寄存器

reg [7:0] addr

4. parameter

相当于常量

parameter SIZE 2'd10

3 操作符

优先级由高到低为

操作符 操作符号
单目运算 + - ! ~
乘、除、取模 * / %
加减 + -
移位 « »
关系 < <= > >=
等价 == != === !===
归约 & ~&
  ^ ~^
  | ~|
逻辑 &&
  ||
条件 ?:

特殊操作符:{}

拼接操作符,可以拼接多个操作数为一个向量

data = {2'b01, 1'b0, 3'b101}
// data = 6'b010101

4 编译指令

  1. `define:类似C语言的#define,定义一个宏
  2. `undef:取消一个宏
  3. `include:类似C语言的#include,引入一个.v文件
  4. `timescale:定义时延、仿真的单位和精度
  5. `resetall:如其名,重置所有编译指令为缺省值

5 连续赋值

用于组合逻辑设计

对wire类型变量进行赋值,一旦设定了一个连续赋值后,每当等号右边的任何一个值发生变化,赋值将自动重新赋值,即wire每时每刻都等于右边的表达式结果

如下,Z一直等于X&Y

wire X, Y, Z
assign Z = X & Y

6 时延

可以用#+时延秒数实现时延

assign #10 Z = X & Y
// 当X或Y发生变化后会时延10秒才会赋值给Z

7 过程结构

1. initial

只执行一次,一帮用于给reg赋初值

reg clk;
reg [3:0] data;

initial begin
	clk = 0;
	data = 4'b0001;
end

2. always

重复执行,只要触发了设定的条件就执行一次

reg clk;

initial begin
	clk = 0;
end

// 表示每隔1秒clk就取反
always #1 begin
	clk = ~clk;
end
reg clk;
reg rst;

initial begin
	clk = 0;
	rst = 0;
end

// 表示每隔1秒clk就取反
always #1 begin
	clk = ~clk;
end

// 表示每当clk处于上升沿就让rst取反
always @(posedge clk) begin
	rst = ~rst;
end

注意

  1. initial不能与always嵌套嵌套
  2. 每个过程结构快都是并行执行,即initial与always是同时开始的
  3. 每个过程结构块内是顺序执行

8 过程赋值

1. 过程赋值与连续赋值

  1. 过程赋值:在initial或always块里的赋值,可以描述组合逻辑或时序逻辑,对象一般为寄存器
  2. 连续赋值:一般是assign关键字标志的赋值,用于描述组合逻辑,对象一般是wire

2. 阻塞赋值与非阻塞赋值

  1. 阻塞赋值:用=赋值,顺序执行,会阻塞
  2. 非阻塞赋值:用<=赋值,并行执行,不会阻塞

3. 避免竞争

如下例子,由于两个always块同时执行,使用阻塞赋值会导致两个对寄存器的操作竞争,使用非阻塞赋值则可以实现交换两个寄存器的值

// 竞争
always @(posedge clk) begin
    a = b ;
end
 
always @(posedge clk) begin
    b = a;
end

// 不竞争,结果是交换两个寄存器的值
always @(posedge clk) begin
    a <= b ;
end
 
always @(posedge clk) begin
    b <= a;
end

9 时序控制

1. 时延控制

用时延#10实现时延控制

2. 事件控制

事件一般是指某个reg或者wire发生变化,事件控制用@或wait触发

  1. 边沿触发事件控制:

一般事件:

  • 关键字 posedge 指信号发生边沿正向跳变
  • negedge 指信号发生负向边沿跳变
  • 未指明跳变方向时,则 2 种情况的边沿变化都会触发
  • 如果有多个,任何一个都可以触发
  • *可以表示语句块中所有变量
always @(posedge clk) begin
	data = 1'b0;
end

always @(negedge clk) begin
	data = 1'b1;
end

always @(clk) begin
	data = 1'b0;
end

// 等价于上一个
always @(posedge clk, negedge clk) begin
	data = 1'b0;
end

// 当语句块中任何变量变化时都触发,A、B、C任一变化都触发
always @(*) begin
	Z = A | (B & C);
end

命名事件:命名事件用event关键字,用->触发,用法不好解释,看例子

event reset;
always @(posedge clk) begin
        -> reset;
end

//触发,重置data
always @(reset) begin
    data = 1'b0;
end
  1. 电平敏感事件控制:用wait等待某一个信号为真
initial begin
	// 等待start_signal
	wait (start_signal);
	data = 1'b0;
end

10 语句块

1. 顺序块

用begin与end标识,顺序块中语句顺序执行,但用非阻塞赋值可以实现并行执行

2. 并行块

用fork和join标识,即使语句是阻塞赋值,也会强制并行执行

3. 块命名

module test();

	reg data;

	// 命名为init
    initial begin: init
        data = 0'b0;
    end
    
    // 访问
    test.init.data = 0'b1;
    // 禁用该块
    disable init;

endmodule

11 条件语句

if,else

reg select;
reg clk;
reg data;

if (clk) begin

	if (select) begin
		data = 1'b0;
	end
	else begin
		data = 1'b1;
	end

end

12 多路分支

case

always @(*) begin
	case(select)
		2'b00:	begin
					data = a;
				end
		2'b01:	begin
					data = b;
				end
		2'b10:	begin
					data = c;
				end
		default:begin
					data = d;
				end
	endcase
end

13 循环

注意,循环只能在initial与always块中使用

1. while

initial begin
	counter = 2'd0;
	while (counter <= 10) begin
		#10;
		counter = counter + 1'd1;
	end
end

2. for

// 整形变量i,之前没说
integer i;

initial begin
    counter = 2'd0 ;
    for (i = 0; i <= 10; i = i+1) begin
        #10;
        counter = counter + 1'd1;
    end
end

3. repeat

固定次数

initial begin
    counter = 2'd0;
    repeat (10) begin  //重复10次
        #10;
        counter = counter + 1'd1 ;
    end
end

4. forever

永久执行,可以用来做一个时钟

initial begin
    clk = 0;
    forever begin
        clk = ~clk;
        #5;
    end
end

// 其实等价于
always #5 begin
	clk = ~clk;
end

14 模块

  1. 有点像class,但含义不同
  2. 用input表示模块输入,input只能是wire类型,用output表示模块输出,output可以是reg或wire

一个全加器模块

module full_adder_1bit(
    input wire A,
    input wire B,
    input wire Cin,
    output wire F,
    output wire Cout
);

    assign F = A^B^Cin;
	assign Cout = (A&B) | ((A^B)&Cin);

endmodule

15 模块例化

  1. 有点像class对象实例化,但含义不同
  2. 对接input的可以是wire或reg,对接output的只能是wire
  3. genvar是一个特殊的变量类型,专门用于generate语句中的循环控制。genvar只能在编译时赋值,并且只能在generate语句中使用
  4. generate用来重复生成实例(注意,不能用for循环直接生成实例,需要用generate包起来)

一个32位加法器模块,用上述全加器实例化实现,注意一下接口写法

module full_adder_32bit(
    input wire [31:0] A,
    input wire [31:0] B,
    input wire Cin,
    output wire [31:0] F,
    output wire Cout
);

    wire [31:0] Cout_tmp;
    full_adder_1bit u_full_adder_1bit(
        .A      (A[0]),
        .B      (B[0]),
        .Cin    (Cin),
        .F      (F[0]),
        .Cout   (Cout_tmp[0])
    );

    genvar i;
    generate
        for (i = 1; i <= 31 ; i = i+1) begin
            full_adder_1bit u_full_adder_1bit(
                .A      (A[i]),
                .B      (B[i]),
                .Cin    (Cout_tmp[i-1]),
                .F      (F[i]),
                .Cout   (Cout_tmp[i])
            );
        end
    endgenerate

    assign Cout = Cout_tmp[31];

endmodule