verilog基础运算与FPGA中LUT的理解
1、verilog位拼接运算符
位拼接运算符定义和tb仿真
- 在Verilog HDL语言有一个特殊的运算符:位拼接运算符 {},它可以把两个或多个信号的某些位拼接起来进行运算操作。
如下是位拼接运算的例子以及tb仿真:
module Example_Operation(a,b,f11,f22);
input [3:0] a ;
input [3:0] b ;
output [7:0] f11 ;
output [5:0] f22 ;
assign f11 = {a , b}; //拼接a和b,a和b位宽均为4,f11位宽为8
assign f22 = {2'd2{b}}; //拼接b低2位
endmodule
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module Example_Operation_tb;
//端口
reg [3:0] a,b;
wire [7:0] f11;
wire [5:0] f22;
Example_Operation u2 (
.a(a),
.b(b),
.f11(f11) ,
.f22(f22)
);
initial begin
a=0000;b=0000;
#(`Clock*20);
a=0001;b=1000;
#(`Clock*20);
a=1000;b=0101;
#(`Clock*20);
a=1010;b=1001;
#(`Clock*20);
a=1011;b=1111;
end
endmodule
由图可以看出,f22是拼接b低2位,将b低2位作为高位,后面拼接b
2、三人表决器
确定输入输出以及真值表
根据真值表写出输出表达式
根据表达式得到逻辑电路图
-
三个输入,可分别设为a,b,c,输出设为 I
-
写真值表:
a | b | c | I |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 |
0 | 1 | 0 | 0 |
0 | 1 | 1 | 1 |
1 | 0 | 0 | 0 |
1 | 0 | 1 | 1 |
1 | 1 | 0 | 1 |
1 | 1 | 1 | 1 |
-根据真值表写出输出表达式:
化简:
-得到逻辑电路:
module Example_Structure(a,b,c,l);
input a,b,c;
output l;
assign l = ((a&&b)||(b&&c)||(a&&c));
endmodule
testbench测试:
module Example_Structure_tb;
reg a,b,c;
wire l;
Example_Structure u1 (
.a(a),
.b(b),
.c(c),
.l(l)
);
initial begin
#(`Clock*20);
a=0;b=0;c=0;
#(`Clock*20);
a=0;b=0;c=1;
#(`Clock*20);
a=0;b=1;c=0;
#(`Clock*20);
a=0;b=1;c=1;
#(`Clock*20);
a=1;b=0;c=0;
#(`Clock*20);
a=1;b=0;c=1;
#(`Clock*20);
a=1;b=1;c=0;
#(`Clock*20);
a=1;b=1;c=1;
#(`Clock*20);
$stop;
end
endmodule
3、半加器
半加器是对两个一位二进制数进行相加,产生“和”、“进位”。
确定输入和输出后写真值表
根据真值表得到输出表达式
- 真值表:
a | b | s | c |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
-根据真值表得到输出表达式:
-逻辑电路:
module halfadder(a,b,c,s);
input a,b;
output s;
output c;
assign s=a^b;
assign c=a&b;
endmodule
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module halfadder_tb;
//端口
reg a ;
reg b ;
wire s ;
wire c ;
halfadder u2 (
.a(a),
.b(b),
.s(s),
.c(c)
);
initial begin
a=0;b=0;
#(`Clock*20);
a=0;b=1;
#(`Clock*20);
a=1;b=0;
#(`Clock*20);
a=1;b=1;
#(`Clock*20);
$stop;
end
endmodule
采用模块化RTL的方式进行设计
top顶层模块:
module Example_module
(
input a ,
input b ,
output s ,
output c
);
Example_yumen yumen_module
(
.yumen_a(a),
.yumen_b(b),
.yumen_c(c)
);
Example_yihuo yihuo_module
(
.yihuo_a(a),
.yihuo_b(b),
.yihuo_s(s)
);
endmodule
分层小模块 c1:
module Example_yumen(yumen_a,yumen_b,yumen_c);
input yumen_a,yumen_b;
output yumen_c;
assign yumen_c=yumen_a&yumen_b;
endmodule
分层小模块 c2:
module Example_yihuo(yihuo_a,yihuo_b,yihuo_s);
input yihuo_a,yihuo_b;
output yihuo_s;
assign yihuo_s=yihuo_a^yihuo_b;
endmodule
4、全加器
全加器是实现两个一位二进制数相加的基本单元,其内部主要由 n 个全加器构成。 设Ai为被加数,Bi为加数,本位和Si,高位进位Ci,低位进位Ci-1
Ci-1 | Ai | Bi | Si | Ci |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
得到输出表达式:
逻辑电路:
硬件描述:
module fulladder(a,b,Ci,Si,C_out);
input a,b,Ci;
output Si,C_out;
assign Si=a^b^Ci;
assign C_out=((a&b)| (Ci&(a^b)));
endmodule
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module fulladder_tb;
//端口
reg a,b,Ci ;
wire Si ;
wire C_out ;
fulladder u2 (
.a(a),
.b(b),
.Ci(Ci),
.Si(Si) ,
.C_out(C_out)
);
initial begin
a=0;b=0;Ci=0;
#(`Clock*20);
a=0;b=0;Ci=1;
#(`Clock*20);
a=0;b=1;Ci=0;
#(`Clock*20);
a=0;b=1;Ci=1;
#(`Clock*20);
a=1;b=0;Ci=0;
#(`Clock*20);
a=1;b=0;Ci=1;
#(`Clock*20);
a=1;b=1;Ci=0;
#(`Clock*20);
a=1;b=1;Ci=1;
#(`Clock*20);
$stop;
end
endmodule
5、数据选择器
经过选择,把多个通道的数据传到唯一的公共数据通道上。
module Digital_Selector(sel,D0,D1,D2,D3,Y);
input [1:0] sel;
input [7:0] D0,D1,D2,D3;
output reg [7:0] Y; //Y为reg型
always @(*)begin //电平触发
case (sel)
2'b00:Y=D0;
2'b01:Y=D1;
2'b10:Y=D2;
2'b11:Y=D3;
default : Y = 1'b0;
endcase
end
endmodule
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module Digital_Selector_tb;
//端口
reg [1:0]sel;
reg [7:0] D0,D1,D2,D3;
wire [7:0] Y ;
Digital_Selector u2 (
.sel(sel),
.D0(D0),
.D1(D1),
.D2(D2),
.D3(D3),
.Y(Y)
);
initial begin
sel=2'b00;
D0= 00000000;
D1= 00000001;
D2= 00000010;
D3= 00000100;
#(`Clock*20);
sel=2'b01;
D0 = 00000000;
D1 = 00000001;
D2 = 00000010;
D3 = 00000100;
#(`Clock*20);
sel=2'b10;
D0 = 00000000;
D1 = 00000001;
D2 = 00000010;
D3 = 00010100;
#(`Clock*20);
sel=2'b00;
D0 = 00010000;
D1 = 00000001;
D2 = 00000010;
D3 = 00000100;
#(`Clock*20);
sel=2'b11;
D0 = 00000000;
D1 = 00000001;
D2 = 00000010;
D3 = 00000100;
#(`Clock*20);
sel=2'b00;
D0 = 00000000;
D1 = 00000001;
D2 = 00000010;
D3 = 00000100;
#(`Clock*20);
sel=2'b10;
D0 = 00000000;
D1 = 00000001;
D2 = 00000010;
D3 = 00000100;
#(`Clock*20);
$stop;
end
endmodule
6、8-3编码器
编码器(encoder)是将信号(如比特流)或数据进行编码
8-3编码器真值表:
得到输出表达式:
module Digital_Encoder
(
input [ 7:0] I ,
output reg [ 2:0] A
);
always @(*)begin
case(I)
8'b00000001 : A = 3'b000;
8'b00000010 : A = 3'b001;
8'b00000100 : A = 3'b010;
8'b00001000 : A = 3'b011;
8'b00010000 : A = 3'b100;
8'b00100000 : A = 3'b101;
8'b01000000 : A = 3'b110;
8'b10000000 : A = 3'b111;
default : A = 3'b000;
endcase
end
endmodule
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module Digital_Encoder_tb;
reg [7:0]I ;
wire [2:0] A ;
Digital_Encoder u2 (
.I(I),
.A(A)
);
initial begin
I=00000000;
#(`Clock*20);
I=00000001;
#(`Clock*20);
I=00000010;
#(`Clock*20);
I=00000011;
#(`Clock*20);
I=000000100;
#(`Clock*20);
I=00010000;
#(`Clock*20);
I=01100000;
#(`Clock*20);
I=00000000;
#(`Clock*20);
I=1000000;
#(`Clock*20);
I=01000000;
#(`Clock*20);
I=00100000;
#(`Clock*20);
I=00010000;
#(`Clock*20);
I=00001000;
#(`Clock*20);
I=0000010;
#(`Clock*20);
$stop;
end
endmodule
7、3-8解码器
三位输入,通过解码器,得到对应的八位输出。
module Decoder0(
input [2:0] I,
output reg [7:0] O
);
always@(*) begin
case(I)
3'b000 : O = 8'b0000_0001;
3'b001 : O = 8'b0000_0010;
3'b010 : O = 8'b0000_0100;
3'b011 : O = 8'b0000_1000;
3'b100 : O = 8'b0001_0000;
3'b101 : O = 8'b0010_0000;
3'b110 : O = 8'b0100_0000;
3'b111 : O = 8'b1000_0000;
default: O = 8'b0000_0000;
endcase
end
endmodule
RTL图:
`timescale 1ns/1ps //时间精度
`define Clock 20
module Decoder0_tb;
reg [2:0] I;
wire [7:0] O;
Decoder0 inst_Decoder0 (
.I(I),
.O(O)
);
initial begin
I = 3'b000 ;
#(`Clock*20);
I = 3'b001 ;
#(`Clock*20);
I = 3'b100 ;
#(`Clock*20);
I = 3'b110 ;
#(`Clock*20);
I = 3'b111 ;
#(`Clock*20);
I = 3'b011 ;
#(`Clock*20);
#(`Clock*150);
$stop;
end
endmodule
波形如下:
8、D触发器
边沿触发方式:
边沿触发:输出只要在时钟边沿的时候才能发生改变,否则保持不变。
电平触发:
触发信号为有效电平(高或低)时,输出随输入变化,否则保持。
module Digital_Data_Flip_Flop(D,CLK,rst_n,Q); //数据数字触发器
input D;
input CLK,rst_n;
output reg Q;
always @(posedge CLK or negedge rst_n)begin
if(!rst_n)
Q <= 1'b0;
else
Q <= D;
end
endmodule
RTL图:
testbench测试:
`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module Digital_Data_Flip_Flop_tb;
reg D;
reg CLK,rst_n;
wire Q;
Digital_Data_Flip_Flop u1(
.D(D),
.CLK(CLK),
.rst_n(rst_n),
.Q(Q)
);
initial begin
CLK = 0;
forever #(`Clock/2) CLK = ~CLK;
end
initial begin
rst_n=0; #(`Clock*20+1);
rst_n=1;
end
initial begin
D=1'b0;#(`Clock*20);
D=1'b1;#(`Clock*20);
D=1'b1;#(`Clock*20);
D=1'b0;#(`Clock*20);
D=1'b1;#(`Clock*20);
D=1'b0;#(`Clock*20);
D=1'b0;#(`Clock*20);
D=1'b1;#(`Clock*20);
$stop;
end
endmodule
波形可看到,上升沿的时候Q变化
9、4bit移位寄存器
4个边沿D触发器构成4位移位寄存器
module Digital_Shift_Reg (
input clk ,
input rst_n ,
input data_in ,
input data_en ,
output reg [ 3:0] data_out ,
output reg [ 3:0] data_out_n
);
//组合逻辑,不断移位
always @(*)begin
if(data_en)
data_out_n = {data_in,data_out[3:1]};
else
data_out_n = data_out;
end
//时序逻辑,寄存data_out_n的值,可以比data_out_n慢一拍
//寄存器输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
data_out <= 4'b0;
else
data_out <= data_out_n;
end
endmodule
RTL:
testbench测试:
`timescale 1ns/1ps
`define Clock 20
module Digital_Shift_Reg_tb;
reg clk,rst_n;
reg data_en;
reg data_in;
wire [3:0] data_out_n;
wire [3:0] data_out;
Digital_Shift_Reg u1(
.clk(clk),
.rst_n(rst_n),
.data_en(data_en),
.data_in(data_in),
.data_out(data_out),
.data_out_n(data_out_n)
);
initial clk=0;
always #(`Clock/2) clk = ~clk;
initial begin
rst_n = 0;
data_in= 0;
data_en=0;
#(`Clock+1)
rst_n = 1;
data_en=1;
data_in= 1;
#1000
$stop;
end
endmodule
10、阻塞赋值“=”(组合逻辑电路),非阻塞赋值“<=”(时序逻辑电路)
- 阻塞
"阻塞"当前的赋值语句阻断了其后的语句,也就是说后面的语句必须等到当前的赋值语句执行完毕才能执行
- 非阻塞
“非阻塞”是当前的赋值语句不会阻断其后的语句。非阻塞语句可以认为是分为两个步骤进行的,非阻塞赋值操作只能用于对寄存器类型变量进行赋值
- 加入延迟
向assign连续赋值语句以及过程赋值语句always中加入delay,都会被综合器忽视。如下:
assign #2 Out = ~In; //assign语句中的延迟
#5 RegB <= RegA ; //always块非阻塞语句中的延迟
Reg = #2 RegA ;//always块阻塞语句中的延迟
题目1:答案 0 2
这里可直接画出对应的电路更好理解:
左边:
右边:
题目2:
如果我们用阻塞和非阻塞两种方式实现非门,RTL如下:
阻塞:生成门电路
非阻塞:生成寄存器
对阻塞赋值和非阻塞赋值进行仿真验证
top顶层设计:
module Example_Block(clk,block_in,no_block_out1,no_block_out2,block_out1,block_out2);
input clk;
input block_in;
output no_block_out1;
output no_block_out2;
output block_out1;
output block_out2;
//block模块例化
block block_init //阻塞赋值
(
.clk (clk ),
.block_in (block_in ),
.block_out1 (block_out1 ),
.block_out2 (block_out2 )
);
//no_block模块例化
no_block no_block_init //非阻塞赋值
(
.clk (clk ),
.no_block_in (block_in ),
.no_block_out1 (no_block_out1 ),
.no_block_out2 (no_block_out2 )
);
endmodule
//分层模块 c1阻塞赋值 :
module block
(
input clk ,
input block_in ,
output reg block_out1 ,
output reg block_out2
);
always @(posedge clk)begin
block_out1 = block_in;
block_out2 = block_out1; //相当于组合逻辑,因此不会产生clk延迟
end
endmodule
//分层模块 c2非阻塞赋值:
module no_block
(
input clk ,
input no_block_in ,
output reg no_block_out1 ,
output reg no_block_out2
);
always @(posedge clk)begin
no_block_out1 <= no_block_in;
no_block_out2 <= no_block_out1; //生成寄存器,因此out2会下一clk接收
end
endmodule