Verilog 笔记

Posted on Oct 20, 2020

上手一个比较舒服的编辑器

作为一个用惯了 VS Code 的童鞋来说,Vivado 自带的编辑器实在是太丑了!于是打算换到熟悉一点的环境。看了一下hin简单,稍稍配置一下就可以啦。 上手之后主要是

  • 更换 Vivado 的默认编辑器
  • 给 VS Code 用上 Vivado 的代码补全

具体过程省略100字 2333

实际重要的是编程,不是嘛。

Veribuglog 基本语法

一个模块长啥样呢

module test(	//	put your module name here
    input in,	//	claim input signal
    output out 	//	claim output signal
 	output out_n   
);
    //	if needed you can claim your internal variables here
    /******	below are the logic descriptions *****/
    assign out = in;
    assign out_n = ~in;
    /****************** end **********************/
endmodule

这个电路长介个样子:

​ 图.jpg

好吧我承认我太懒了ww

半加器的 Verilog 代码

module add(
	input a,b,
    output sum,cout
);
    assign {cout,sum} = a+b;	
endmodule

这里的 { } 是位拼接符号。将两个单 bit 符号拼接成了一个 2bit 符号,用于接收相加的结果。

下面这段代码与上面的代码等价。

module add(
	input a,b,
	output sum,cout
);
	assign cout = a & b;
	assign sum = a ^ b;
endmodule

需要注意的是, Verilog 作为一个硬件描述语言,上面代码块中的 cout 和 sum 两条语句顺序交换并不会对电路本身产生任何影响。他们是位置无关的。

全加器的 Verilog 代码

这个要上图了qwq

fulladder

用上面做好的半加器来做一个全加器吧。

module full_add(
	input a,b,cin,
    output sum,cout
);
    wire s,carry1,carry2;
    add add_inst1(
        .a(a ),
        .b(b ),
        .sum(s ),
        .cout(carry1)
    );
    add add_inst2(	//	claimed an half adder here. add_inst2 is customed name of the adder, you can name it anything.
        .a(s ),
        .b(cin ),
        .sum(sum ),
        .cout(carry2)
    );
    assign cout = carry1|carry2;
endmodule

二选一选择器的 Verilog 代码

module 2-1mux(
    input a_1,a_2,s,
    output aout
);
    assign aout = (~s & a_1)|(s & a_2);
endmodule

四选一选择器的 Verilog 代码

module 4-1mux(
	input a_1,a_2,a_3,a_4,s_1,s_2,
    output aout
);
    wire carry_1,carry_2;
    2-1mux selector_inst1(
        .a_1(carry_1 ),
        .a_2(carry_2 ),
        .s(s_1 ),
        .aout(aout )
    );
    2-1mux selector_inst2(
        .a_1(a_1 ),
        .a_2(a_2 ),
        .s(s_2 ),
        .aout(carry_1 )
    );
    2-1mux selector_inst3(
        .a_1(a_3 ),
        .a_2(a_4 ),
        .s(s_2 ),
        .aout(carry_2 )
    );
endmodule

时序电路的 Verilog 描述

时序电路是比较复杂的一种电路。在这里我们将仍然利用示例程序来辅助理解 Verilog 语言。

D 触发器的 Verilog 代码

module d_ff(
	input clk,d,
    output reg q
);
    always@(posedge clk)
        q<=d;
endmodule

reg、always 和 posedge 是 Verilog 中的关键字,其中 always 表示其后是个过程语句块。reg 与前面学习到的 wire 关键字类似,是一种数据类型,称为寄存器类型。对于初学者,可以简单的理解为凡是在 always 语句块内被赋值 的信号,都应定义为 reg 类型。

posedge 为事件控制关键字,例如代码中的 “posedge clk”表示“clk 信号的上升沿”这一事件。另外,在时序逻辑电路中,信号赋值采用“<=”(非阻塞赋值),而不是“=”(阻塞赋值),这两种赋值方式的区别暂不介绍,读者只需记住一个原则:组合逻辑采用阻塞赋值“=”, 时序逻辑采用非阻塞赋值“<=”。

Tip: There’s a tutorial on “nonblocking assignment” and “blocking assignment”. Check it here.

增加 复位信号 的D触发器

module d_ff_r(
	input clk,rst_n,d,
    output reg q
);
    always@(posedge clk)
        begin
            if(rst_n==0)
                q<=1'b0;
            else
                q<=d;
        end
endmodule

这段代码中又新出现了 begin、end、if、else 四个关键字,其中 begin/end 必须成对出现,用于表征语句块的作用区间,如上述例子中,begin/end 之间的 代码都属于同一 always 块。if、else 用于条件判断,在很多其它语言中都有出现,其含义也都一样,此处不再赘述。

“1’b0”是一种数据表示方式,一般格式为“数据位宽’进制数值”,本例中表示这是一个 1bit 的数据,用二进制表示, 其值为 0。

上述是同步复位信号。相对应的,我们还有一种异步复位方式。即不论时钟和D信号是什么,一旦复位信号有效,输出端Q立刻变为确定的复位值(一般是低电平)。

异步复位的 D 触发器

module d_ff_r(
	input clk,rst_n,d,
	output reg q
);
	always@(posedge clk or negedge rst_n)
		begin
			if(rst_n==0)
				q <= 1b0;
			else
				q <= d;
		end
endmodule

negedge 是与 posedge 同类型的一个关键字,只不过它表示信号的下降沿事件。关键字“or”表示“或”操作。

可以看出,异步复位与同步复位最大的区别在于,复位信号与时钟信号同时出现在了 always 语句的敏感变量列表中,在没有时钟上升沿的情况下,复位信号也能够起作用。因为复位操作不再完全与时钟信号的上升沿同步,因此称为异步复位。

寄存器

module REG4( // a register that can store 4-bit data
	input CLK,RST_N,
	input [3:0] D_IN,
	output reg [3:0] q
);
	always@(posedge CLK)
	begin
		if(RST_N==0)
			D_OUT <= 4b0;
		else
			D_OUT <= D_IN;
	end
endmodule

对于多 bit 位宽的信号,在 Verilog 中使用“[x:y]”这种方式声明,例如上述代码中,D_OUT 就是一个 4bit 的信号,它包含了 D_OUT[0]、D_OUT[1]、 D_OUT[2]、D_OUT[3]四个单 bit 信号。

整个计数器!

利用 4bit 寄存器,我们可以搭建一个 4bit 的计数器。该计数器在 0~15 之间循环计数,复位时输出值为 0。

module REG4(
	input CLK,RST_N,
	output reg [3:0] CNT
);
	always@(posedge CLK)
		begin
			if(RST_N==0)
				CNT <= 4b0;
			else
				CNT <= CNT + 4b1;
		end
endmodule

附录

常数表示

从 CSDN 搬过来一个!

请参考 verilog 数据常量 数字表达式:<位宽><进制><数字> ’b:二进制 //eg.4’b1110 表示4位二进制数1110 ‘h:十六进制 //eg 8’hef、4’ha等 ’d:十进制 //eg 2’d3、4‘d15(不能写16,4位宽最大15)等

所以10’d0表示10位宽的数值0,0000000000 加入10‘d15,则表示十进制15, 0000001111

有符号数和无符号数的区别

Originated from here.

无符号数:用补码存储
  1. 高位溢出赋给一个位宽不够的数:高位截断,保留低位
wire [3:0] a = 4'b1111; // a = 15
wire [3:0] b = 4'b0010; // b = 2
wire [3:0] c;

assign c = a + b;		//c = 17 = 10001, while c is a 4-bit wire variable, so 10001 will be stored as "0001".
  1. 高位溢出赋给一个位宽足够的数:留下符号位,多位补0
wire [3:0] a = 4'b1111; // a = 15
wire [3:0] b = 4'b0010; // b = 2
wire [4:0] c;

assign c = a + b;		//c = 17 = 10001, while c is a 5-bit wire variable, so 10001 will be stored as "10001".
wire [3:0] a = 4'b1111; // a = 15
wire [3:0] b = 4'b0010; // b = 2
wire [5:0] c;

assign c = a + b;		//c = 17 = 10001, while c is a 6-bit wire variable, so 10001 will be stored as "010001".
  1. 对中间结果移位:先赋值再移位
wire [3:0] a = 4'b1111; // a = 15
wire [3:0] b = 4'b0010; // b = 2
wire [3:0] c;

assign c =(( a + b) >> 1);	//c = 17 = 10001, while c is a 3-bit wire variable, so 10001 will be stored as "0001". After moving digits the final result will be "0000".
  • 算数右移和逻辑右移待补充… (refer to this post?
有符号数
  1. 正常运算
wire signed [3:0] a = 4'b1111; // a = -1
wire signed [3:0] b = 4'b0010; // b = 2
wire signed [3:0] c;

assign c = a + b;			   // c = 0001
wire signed [3:0] a = 4'b1110; // a = -2
wire signed [3:0] b = 4'b0001; // b = 1
wire signed [3:0] c;

assign c = a + b;			   // c = 1111
  1. 赋值给位宽不够的数: 舍弃高位
wire signed [3:0] a=4'b0111;//7
wire signed [3:0] b=4'b0010;//2
wire signed [3:0] c;

assign c =a + b; //9=1001,displayed fine
wire signed [3:0] a=4'b1001;//-7
wire signed [3:0] b=4'b1110;//-2
wire signed [3:0] c;

assign c =a + b;//-9=10111,displayed "0111"
  1. 位宽足够:补0
wire signed [3:0] a=4'b0111;
wire signed [3:0] b=4'b0010;
wire signed [4:0] c;

assign c =a + b;//9=1001.displayed "01001"
  1. 给中间结果移位
wire signed [3:0] a=4'b1001;//-7
wire signed [3:0] b=4'b1110;//-2
wire signed [3:0] c;

assign c =(( a + b ) >> 1); //-9=10111,displayed 0011
wire signed [3:0] a=4'b1001;
wire signed [3:0] b=4'b1110;
wire signed [5:0] c;

assign c = ((a + b)>>1) ; //-9 = 10111. >>1 : 01011 displayed 

I'm a little bit confused about this.Please refer to sign extension to know more~