俗话说设计验证不分家,一个好的verifier一定也是一个好的designer,因此至少需要掌握一些典型电路的Verilog设计。
- 01011011101111…序列生成
这种有规律的序列生成问题,一般来说都可以使用计数器解决。第1段序列为01,第2段为011,第3段为0111,由此可知每一段都是一个0和“段数个”1,因此需要两个计数器,cnt0记录当前是第几段,cnt1记录当前段输出了几个1,通过状态机实现。
S0:输出0;
S1:输出1;
采用三段式写法,代码如下:
module seq_gen
#(parameter cnt_size = 8)
(input clk,
input rstn,
output seq);
reg [cnt_size-1:0] cnt0;
reg [cnt_size-1:0] cnt1;
localparam S0 = 1'd0;
localparam S1 = 1'd1;
reg current_s;
reg next_s;
reg seq_r;
always@(posedge clk or negedge rstn) begin
if(!rstn)
current_s <= S0;
else
current_s <= next_s;
end
always@(*) begin
case(current_s)
S0: next_s <= S1;
S1: if(cnt1==0)
next_s <= S0;
else
next_s <= S1;
default: next_s <= S0;
endcase
end
always@(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt0 <= 0;
cnt1 <= 0;
seq_r <= 0;
end
else
case(current_s)
S0: begin
cnt0 <= cnt0 + 1;
cnt1 <= cnt0;
seq_r <= 0;
end
S1: begin
cnt1 <= cnt1 - 1;
seq_r <= 1;
end
default: begin
cnt0 <= 0;
cnt1 <= 0;
seq_r <= 0;
end
endcase
end
assign seq = seq_r;
endmodule
//test bench
`timescale 1ns/1ps
module tb();
reg clk;
reg rstn;
wire seq;
initial begin
clk <= 1'b0;
forever begin
#5 clk <= !clk;
end
end
initial begin
#10 rstn <= 1'b0;
repeat(10) @(posedge clk);
rstn <= 1'b1;
end
seq_gen#(.cnt_size(8))
dut(.clk(clk),
.rstn(rstn),
.seq(seq));
endmodule
modelsim仿真波形如下:
- 译码器
译码器属于组合逻辑电路,用verilog表示的话,使用case语句即可,如下
//3-8译码器
module decode3_8(input [2:0] in,output reg [7:0] out);
always@(*) begin
case(in)
3'd0: out = 8'b0000_0001;
3'd1: out = 8'b0000_0010;
3'd2: out = 8'b0000_0100;
3'd3: out = 8'b0000_1000;
3'd4: out = 8'b0001_0000;
3'd5: out = 8'b0010_0000;
3'd6: out = 8'b0100_0000;
3'd7: out = 8'b1000_0000;
default: out = 8'b0000_0000;
endcase
end
endmodule
//BCD译码器(4-10译码器),BCD码又称为8421码,用4位2进制数来表示十进制的0-9
module decode_BCD(input [3:0] in,output reg [9:0] out);
always@(*) begin
case(in)
4'd0: out = 10'b1111_1111_10;
4'd1: out = 10'b1111_1111_01;
4'd2: out = 10'b1111_1110_11;
4'd3: out = 10'b1111_1101_11;
4'd4: out = 10'b1111_1011_11;
4'd5: out = 10'b1111_0111_11;
4'd6: out = 10'b1110_1111_11;
4'd7: out = 10'b1101_1111_11;
4'd8: out = 10'b1011_1111_11;
4'd9: out = 10'b0111_1111_11;
default: out = 10'b1111_1111_11;
endcase
end
endmodule
//其他的也一样,用assign语句据说会好一些,不容易产生不定态。assign的思路就是根据真值表,单独对输出的每一位赋值就行了。
仿真略
-
编码器
与译码器相反,也可以用always结合case或if…else…实现组合逻辑或者根据真值表由assign连续赋值完成。 -
加法器
最基础的加法器是半加器和全加器,半加器不考虑来自地位的进位(一般不咋用),全加器考虑来自低位的进位。设计思路一般是通过较少位数的全加器构成多位数的加法器,比如1bit的全加器构成4bit加法器,4bit加法器再构成16bit加法器等等。在构成的过程中有两种方式:串联起来,构成行波进位加法器;通过一个额外的组合逻辑提前计算出最终的进位输出,称为超前进位加法器。
半加器、全加器、行波进位加法器的原理很简单,行波进位加法器由于是一级级串联,低级的进位作为高级的输入,因此需要多个时钟周期才能计算完成,延时很大,多位加法器不采用这种方法;超前进位加法器通过数学原理提前计算出最终的进位和输出表达式,可以在一个周期完成计算,具体原理如下:
对于1bit的全加器而言,sum = a ^ b ^ cin;cout = (a&&b) || (cin && (a^b));
令P=a ^ b,G=a&&b,则有:sum = P ^ cin,cout =G || (cin && P) ;
对于2bit的全加器而言,有sum[0] = P[0] ^ cin,cout[0] = G[0] || (cin && P[0]),sum[1] = P[1] ^ cout[0],cout = G[1] || (cout[0] && P[1]);
对于N bit的全加器而言,有
sum[0] = P[0] ^ cin,cout[0] = G[0] || (cin && P[0])
sum[1] = P[1] ^ cout[0],cout = G[1] || (cout[0] && P[1]);
sum[N-1] = P[N-1] ^ cout[N-2],cout = G[N-1] || (cout[N-2] && P[N-1]);
综上,个人理解的超前进位加法器就是通过将cout[N-2],cout[N-3]等数全部用cin和p,g的组合逻辑表示出来,从而能够对输入变化产生立即响应
//1bit全加器
module adder_1(input a, input b, input cin, output sum, output cout);
assign sum = a^b^cin;//奇数个1相异或的结果为1
assign cout = (a&b) || (cin&(a^b));
endmodule
//4bit全加器-行波进位加法器
module adder_4(input [3:0] a, input [3:0] b, input cin, output [3:0] sum, output cout);
wire c1, c2, c3;
adder_1 dut1(.a(a[0]), .b(b[0]), .cin(cin), .sum(sum[0]), .cout(c1));
adder_1 dut2(.a(a[1]), .b(b[0]), .cin(c1), .sum(sum[1]), .cout(c2));
adder_1 dut3(.a(a[2]), .b(b[0]), .cin(c2), .sum(sum[2]), .cout(c3));
adder_1 dut4(.a(a[3]), .b(b[0]), .cin(c3), .sum(sum[3]), .cout(cout));
endmodule
//4bit全加器-超前进位加法器
module adder_4(input [3:0] a, input [3:0] b, input cin, output [3:0] sum, output co);
wire [3:0] p, g;
wire [2:0] cout;
//给P赋值
assign p = {a[3]^b[3],a[2]^b[2],a[1]^b[1],a[0]^b[0]};
//给G赋值
assign g = {a[3]&&b[3],a[2]&&b[2],a[1]&&b[1],a[0]&&b[0]};
//给进位cout赋值
assign cout[0] = g[0] || cin && p[0];
assign cout[1] = g[1] || (g[0] || cin && p[0]) && p[1];
assign cout[2] = g[2] || (g[1] || (g[0] || cin && p[0]) && p[1]) && p[2];
assign co = g[3] || (g[2] || (g[1] || (g[0] || cin && p[0]) && p[1]) && p[2]) && p[3];
//给sum赋值
assign sum[0] = p[0] ^ cin;
assign sum[1] = p[1] ^ (g[0] || cin && p[0]);
assign sum[2] = p[2] ^ (g[1] || (g[0] || cin && p[0]) && p[1]);
assign sum[3] = p[3] ^ (g[2] || (g[1] || (g[0] || cin && p[0]) && p[1]) && p[2]);
endmodule
//4bit超前进位加法器testbench
`timescale 1ns/1ps
module tb ();
reg [3:0] a,b;
reg cin;
wire [3:0] sum;
wire cout;
initial begin
a <= 1;
b <= 1;
cin <= 0;
forever begin
#20 a <= {$random}%16;
b <= {$random}%16;
cin <= {$random}%2;
end
end
adder_4 dut(.a(a),.b(b),.cin(cin),.sum(sum),.co(cout));
endmodule
下图为4bit超前进位加法器综合电路(quartus综合):
- 乘法器
加减乘除都是由加法完成的。
1bit x 1bit为例,有a x b = 1 when a&b;
1bit x 4bit为例,有abcd x a = abcd & {4{a}};
4bit x 4bit为例,有abcd x abcd = abcd x d x 2^0 +abcd x c x 2^1 + abcd x b x 2^2 + abcd x a x 2^3 = abcd&{4{a}} << 3 + abcd & {4{b}} << 2 + abcd &{4{c}} << 1 + abcd&{4{d}} << 0;
因此,n位 x m位的乘法为:a x b = a & {m{b[m1]}} << (n-1) + … + a & {m{b[0]}} << 0;一般将位宽小的作为乘数,可以减小逻辑运算和加法运算次数。代码如下(移位前需要进行扩位):
//加法器树形乘法器
module mul_4x4(input [3:0] a, input