这学期开了模电,所以要用到 Veribug语言啦。
上手一个比较舒服的编辑器
作为一个用惯了 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
用上面做好的半加器来做一个全加器吧。
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 <= 1’b0;
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 <= 4’b0;
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 <= 4’b0;
else
CNT <= CNT + 4’b1;
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.
无符号数:用补码存储
- 高位溢出赋给一个位宽不够的数:高位截断,保留低位
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".
- 高位溢出赋给一个位宽足够的数:留下符号位,多位补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".
- 对中间结果移位:先赋值再移位
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?
有符号数
- 正常运算
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
- 赋值给位宽不够的数: 舍弃高位
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"
- 位宽足够:补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"
- 给中间结果移位
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~