本文主要对牛客网Verilog刷题中的入门习题进行记录,其中标题带*号的表示题目相对有学习价值。
四选一多路器
制作一个四选一的多路选择器,要求输出定义上为线网类型
Mux41.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21module Mux41(
input [1:0]d1,d2,d3,d0,
input [1:0]sel,
output[1:0]mux_out
);
reg [1:0] mux_out_reg;
always @(*) begin
case(sel)
2'b00: mux_out_reg = d3;
2'b01: mux_out_reg = d2;
2'b10: mux_out_reg = d1;
2'b11: mux_out_reg = d0;
default: mux_out_reg = 2'b00;
endcase
end
assign mux_out = mux_out_reg;
endmoduleMux41_tb.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25module Mux41_tb;
reg [1:0]d1,d2,d3,d0;
reg [1:0]sel;
wire[1:0]mux_out;
Mux41 u1(
.d1(d1),
.d2(d2),
.d3(d3),
.d0(d0),
.sel(sel),
.mux_out(mux_out)
);
initial begin
d1 = 0;
d2 = 1;
d3 = 2;
d0 = 3;
#10 sel = 0;
#10 sel = 1;
#10 sel = 2;
end
endmodule结果如下:
RTL原理图:
异步复位的串联T触发器
T触发器逻辑功能为:在脉冲有效边沿到来时,T=0,触发器状态不变Qn+1=Qn;T=1,Qn=~Qn
Tff_2.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36module Tff_2(
input wire data, clk, rst,
output reg q
);
reg data_reg;
always @(posedge clk or negedge rst) begin
if(!rst)begin
data_reg <= 0;
end
else begin
if(data == 0) begin
data_reg <= data_reg;
end
else if (data == 1) begin
data_reg <= ~data_reg;
end
end
end
always @(posedge clk or negedge rst) begin
if(!rst)begin
q <= 0;
end
else begin
if(data_reg == 0) begin
q <= q;
end
else if (data_reg == 1) begin
q <= ~q;
end
end
end
endmoduleTff_2_tb.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module Tff_2_tb;
reg data, clk, rst;
wire q;
Tff_2 u1(
.data(data),
.clk(clk),
.rst(rst),
.q(q)
);
//时钟信号的产生
initial clk = 0;
always #(`CLK_PERIOD/2) clk = ~clk;
//复位和结束信号的产生
initial begin
rst = 0;
#11 rst = 1;
#1000 $stop;
end
//其他激励信号
initial begin
data = 0;
#21 data = 1;
#20 data = 0;
#40 data = 1;
#20 data = 0;
end
endmodule结果如下:
RTL原理图:
奇偶校验
如果是奇数个1 则结果为1,为偶数则结果为0(通过sel选择是进行奇校验还是偶校验)
运用单目运算符(|,^,&)可以进行如下检测:
e = &d;
//检测是否全为1(若全为1,则e=1,若有0,则e=0)f = ^d;
//奇偶校验(检测1的个数是奇数还是偶数,若是奇数,则f=1,若是偶数,则f=0)g = |d;
//检测是否全为0(若全为0,则g=0,若有1,则g=1)
odd_sel.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14module odd_sel(
input [31:0] bus,
input sel,
output check
);
//使用单目运算符^判断bus中1的个数,若奇数个1,则结果为1,若偶数个1,则结果为0
wire odd_even;
assign odd_even = ^bus;
//sel == 1意味着进行奇校验,sel == 0意味着进行偶校验
assign check = sel ? odd_even : ~odd_even;
endmoduleRTL原理图:
移位运算与乘法*
已知d为一个8位数,请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效(d给出的信号的上升沿表示写入有效)
状态机与移位运算逻辑:
状态 输出位运算 1 d 3 (din << 2) - din 7 (din << 3) - din 8 (din << 3) 状态机不受输入信号控制,循环跳转就可以
状态输出采用时序电路的方式,这样可以节省一个复位状态,在rst归0时输出为0
multi_sel.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71module multi_sel(
input clk,
input rst,
input [7:0]d,
output reg input_grant,
output reg [10:0]out
);
//状态定义
parameter S0 = 4'd1,
S1 = 4'd3,
S2 = 4'd7,
S3 = 4'd8;
reg [3:0] state;
reg [3:0] next_state;
reg [7:0] din;
//状态跳转
always @(posedge clk or negedge rst) begin
if(~rst) begin
state <= S0;
end
else begin
state <= next_state;
end
end
//下一状态的判断
always @(*) begin
case(state)
S0: next_state <= S1;
S1: next_state <= S2;
S2: next_state <= S3;
S3: next_state <= S0;
default: next_state <= S0;
endcase
end
//状态对应输出
always @(posedge clk or negedge rst) begin
if(~rst) begin
out <= 11'd0;
input_grant <= 0;
din <= 8'd0;
end
else begin
case(state)
S0: begin
din <= d; //这里很妙,能保证后面的都是与din相乘,而不是与新输入的d相乘
out <= d;
input_grant <= 1;
end
S1: begin
out <= (din << 2) - din; //d*3 = d*4-d
input_grant <= 0;
end
S2: begin
out <= (din << 3) - din; //d*7 = d*8-d
input_grant <= 0;
end
S3: begin
out <= (din << 3);
input_grant <= 0;
end
endcase
end
end
endmodulemulti_sel_tb.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module multi_sel_tb;
reg clk;
reg rst;
reg [7:0] d;
wire input_grant;
wire [10:0] out;
multi_sel u1(
.clk(clk),
.rst(rst),
.d(d),
.input_grant(input_grant),
.out(out)
);
//时钟信号的产生
initial clk = 0;
always #(`CLK_PERIOD/2) clk = ~clk;
//复位和结束信号的产生
initial begin
rst = 0;
#11 rst = 1;
#1000 $stop;
end
//其他激励信号
initial begin
d = 143;
#40 d = 7;
#50 d = 6;
#10 d = 128;
#20 d = 129;
end
endmodule结果如下:
位拆分与运算*
现在输入了一个压缩的16位数据,按照sel选择输出四个数据的相加结果,并输出valid_out信号(在不输出时候拉低)
- 0: 不输出且只有此时的输入有效
- 1:输出[3:0]+[7:4]
- 2:输出[3:0]+[11:8]
- 3:输出[3:0]+[15:12]
这题的关键其实在于:只有当sel信号为0时,输入数据才被锁存,后续执行的都是锁存的这个数据,只有当sel信号再次为0时,输入数据才会被再次锁存,而在这期间输入信号的改变,对输出是不起作用的。这一部分的代码如下:
1
2
3
4
5
6
7
8reg [15:0] data_lock;
//sel==0时将输入数据锁住,只有当sel再次为零的时候,输入的数据才有效
always@(posedge clk or negedge rst) begin
if (!rst)
data_lock <= 0;
else if(!sel)
data_lock <= d; //后续sel不等于0时都是对这个data_lock操作
enddata_cal.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57module data_cal(
input clk,
input rst,
input [15:0] d,
input [1:0] sel,
output [4:0] out,
output validout
);
reg [4:0] out_reg;
reg validout_reg;
reg [15:0] data_lock;
//sel==0时将输入数据锁住,只有当sel再次为零的时候,输入的数据才有效
always@(posedge clk or negedge rst) begin
if (!rst)
data_lock <= 0;
else if(!sel)
data_lock <= d;
end
always @(posedge clk or negedge rst) begin
if(~rst) begin
out_reg <= 5'b0;
validout_reg <= 1'b0;
end
else begin
case(sel)
2'b00: begin
out_reg <= 5'b0;
validout_reg <= 1'b0;
end
2'b01: begin
out_reg <= data_lock[3:0] + data_lock[7:4];
validout_reg <= 1'b1;
end
2'b10: begin
out_reg <= data_lock[3:0] + data_lock[11:8];
validout_reg <= 1'b1;
end
2'b11: begin
out_reg <= data_lock[3:0] + data_lock[15:12];
validout_reg <= 1'b1;
end
default: begin
out_reg <= 5'b0;
validout_reg <= 1'b0;
end
endcase
end
end
assign out = out_reg;
assign validout = validout_reg;
endmoduleRTL原理图
多功能数据处理器*
根据指示信号select的不同,对输入信号a,b实现不同的运算。输入信号a,b为8bit有符号数,当select信号为0,输出a;当select信号为1,输出b;当select信号为2,输出a+b;当select信号为3,输出a-b
此题提到了一个有符号数,我的理解是,在FPGA的加减法运算中,如果没有bit的溢出,那么其实你定义是无符号还是有符号,计算的结果都是一样的,那么为了防止溢出,在进行有符号加减法运算时,我们对输入的数据通常扩展一个符号位。对于加法运算通常将结果扩展为length(max(a,b)+1)
对于有符号数+无符号数的情况,有符号位前面扩展一个符号位,无符号数前面扩展0,结果用有符号数显示
对于无符号数+无符号数的情况,可以不扩展,因为要扩展也是扩展0,所有没必要
所以我的建议是,在进行加减运算时,其实不需要指定输入数据是否signed,运算时进行相应符号位扩展就可
data_select.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25module data_select(
input clk,
input rst_n,
input signed[7:0]a,
input signed[7:0]b,
input [1:0]select,
output reg signed [8:0]c
);
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
c <= 9'b0;
end
else begin
case(select)
2'b00: c <= {a[7], a};
2'b01: c <= {b[7], b};
2'b10: c <= {a[7], a} + {b[7], b};
2'b11: c <= {a[7], a} - {b[7], b};
default: c <= 9'b0;
endcase
end
end
endmoduledata_select_tb.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module data_select_tb;
reg clk;
reg rst_n;
reg signed[7:0]a;
reg signed[7:0]b;
reg [1:0]select;
wire signed [8:0]c;
data_select u1(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.select(select),
.c(c)
);
//时钟信号的产生
initial clk = 0;
always #(`CLK_PERIOD/2) clk = ~clk;
//复位和结束信号的产生
initial begin
rst_n = 0;
#11 rst_n = 1;
#1000 $stop;
end
initial begin
a = 8'b0;
b = 8'b0;
select = 2'b0;
#12 a = 8'd127;
b = -8'd100;
select = 2'd2;
#20 a = -8'd125;
b = -8'd128;
select = 2'd3;
end
endmodule结果如下:
求两个数的差值
根据输入信号a,b的大小关系,求解两个数的差值:输入信号a,b为8bit位宽的无符号数。如果a>b,则输出a-b,如果a≤b,则输出b-a。
data_minus.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22module data_minus(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
output reg [8:0]c
);
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
c <= 9'b0;
end
if(a > b) begin
c <= a - b;
end
else begin
c <= b - a;
end
end
endmodule这里有个点是:在Verilog HDL中,仍然有必要根据设计需要采用关键字signed对信号进行声明。 例如,在进行比较运算时,对于无符号数据,1000大于0100;对于有 符号数据,1000小于0100。(虽然在此题中都是无符号,所以不用考虑)
使用generate…for语句简化代码*
在某个module中包含了很多相似的连续赋值语句,请使用generata…for语句编写代码,替代该语句,要求不能改变原module的功能。使用Verilog HDL实现以上功能并编写testbench验证。
1
2
3
4
5
6
7
8
9
10
11
12
13
14module template_module(
input [7:0] data_in,
output [7:0] data_out
);
assign data_out [0] = data_in [7];
assign data_out [1] = data_in [6];
assign data_out [2] = data_in [5];
assign data_out [3] = data_in [4];
assign data_out [4] = data_in [3];
assign data_out [5] = data_in [2];
assign data_out [6] = data_in [1];
assign data_out [7] = data_in [0];
endmodulegen_for_module.v
1
2
3
4
5
6
7
8
9
10
11
12module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
genvar i;
generate for(i = 0; i < 8; i = i + 1) begin: bit_block_name
assign data_out[i] = data_in[7 - i];
end
endgenerate
endmodule在begin之后的bit_block_name,表示该generate…for语句块的名称,可以根据需要修改。特别需要注意的是,即使只有一个语句,也需要使用begin…end。同时需要使用endgenerate表示结束
使用子模块实现三输入数的大小比较
请编写一个子模块,将输入两个8bit位宽的变量data_a,data_b,并输出data_a,data_b之中较小的数。并在主模块中例化,实现输出三个8bit输入信号的最小值的功能。
main_mod.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38module main_mod(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
input [7:0]c,
output [7:0]d
);
wire [7:0] temp1;
wire [7:0] temp2;
child_mod u1(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.d(temp1)
);
child_mod u2(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(c),
.d(temp2)
);
child_mod u3(
.clk(clk),
.rst_n(rst_n),
.a(temp1),
.b(temp2),
.d(d)
);
endmodulechild_mod.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25module child_mod(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
output [7:0]d
);
reg [7:0] d_reg;
always @ (posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
d_reg <= 8'b0;
end
else begin
if( a > b )
d_reg <= b;
else
d_reg <= a;
end
end
assign d = d_reg;
endmodule- RTL原理图
使用函数实现数据大小端转换
请用函数实现一个4bit数据大小端(高位bit与低位bit对应互换)转换的功能。实现对两个不同的输入分别转换并输出。
function_mod.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24module function_mod(
input [3:0]a,
input [3:0]b,
output [3:0]c,
output [3:0]d
);
assign c = data_bit_converse(a, 4);
assign d = data_bit_converse(b, 4);
function [3:0] data_bit_converse;
input [3:0] data; //输入变量
input [2:0] data_bit;
integer i; //函数内的变量
begin
for(i = 0; i < data_bit; i = i + 1) begin
data_bit_converse[i] = data[data_bit - i - 1];
end
end
endfunction
endmodule
4位数值比较器电路
某4位数值比较器的功能表如下。请用Verilog语言采用门级描述方式,实现此4位数值比较器
先用门级电路描述1bit比较器
A B F(A>B) F(A<B) F(A=B) 0 0 0 0 1 0 1 0 1 0 1 0 1 0 0 1 1 0 0 1 - 则有:
$$
F(A>B) = A\bar B\
F(A<B) = \bar A B\
F(A=B) = \bar A\bar B + AB =\overline{A\bar B+\bar AB}
$$
- 则有:
最后再调用四个1bit比较器,根据题目所述表格将其串起来
comparator_4.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42module comparator_4(
input [3:0] A,
input [3:0] B,
output wire Y2, //A>B
output wire Y1, //A=B
output wire Y0 //A<B
);
wire W_y2[3:0];
wire W_y1[3:0];
wire W_y0[3:0];
genvar gen_i;
for (gen_i = 0; gen_i < 4; gen_i = gen_i + 1) begin
compare_1 compare_1_u(
.A (A[gen_i] ),
.B (B[gen_i] ),
.Y2(W_y2[gen_i]),//A>B
.Y1(W_y1[gen_i]),//A=B
.Y0(W_y0[gen_i]) //A<B
);
end
assign Y2 = W_y2[3] | ((W_y1[3]) & (W_y2[2])) | ((W_y1[3]) & (W_y1[2]) & (W_y2[1])) | ((W_y1[3]) & (W_y1[2]) & (W_y1[1]) & (W_y2[0]));
assign Y0 = W_y0[3] | ((W_y1[3]) & (W_y0[2])) | ((W_y1[3]) & (W_y1[2]) & (W_y0[1])) | ((W_y1[3]) & (W_y1[2]) & (W_y1[1]) & (W_y0[0]));
assign Y1 = W_y1[3] & W_y1[2] & W_y1[1] & W_y1[0];
endmodule
module compare_1(
input A,
input B,
output Y2,//A>B
output Y1,//A=B
output Y0 //A<B
);
assign Y2 = A & (!B);
assign Y0 = (!A) & B;
assign Y1 = !(Y2 | Y0);
endmodule
4bit超前进位加法器
请用Verilog语言采用门级描述方式,实现此4bit超前进位加法器,其逻辑表达式如下:
- 中间变量:$G_i = A_i B_i,P_i = A_i \bigoplus B_i$
- 和:$S_i = P_i\bigoplus C_{i-1}$
- 进位:$C_i = G_i + P_i C_{i-1}$
lca_4.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28module lca_4(
input [3:0] A_in,
input [3:0] B_in,
input C_1,
output wire CO,
output wire [3:0] S
);
wire [3:0] G;
wire [3:0] P;
wire [3:0] C;
assign G = A_in & B_in; //按位与就ok
assign P = A_in ^ B_in;
assign C[0] = G[0] | P[0] & C_1;
assign C[1] = G[1] | P[1] & C[0];
assign C[2] = G[2] | P[2] & C[1];
assign C[3] = G[3] | P[3] & C[2];
assign S[0] = P[0] ^ C_1;
assign S[1] = P[1] ^ C[0];
assign S[2] = P[2] ^ C[1];
assign S[3] = P[3] ^ C[2];
assign CO = C[3];
endmoduleRTL原理图
根据状态转移图或表写状态机
某同步时序电路转换表如下,请使用D触发器和必要的逻辑门实现此同步时序电路,用Verilog语言描述。
以上状态转移表对应的状态转移图如下:
Machine_state_design1.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53module Machine_state_design1(
input A ,
input clk ,
input rst_n,
output wire Y
);
//状态定义
parameter S0 = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
//状态的中间变量
reg [1:0] state, next_state;
//状态跳转
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
state <= S0;
end
else
state <= next_state;
end
//次态判断
always @(*) begin
case(state)
S0: begin
if(A) next_state = S3;
else next_state = S1;
end
S1: begin
if(A) next_state = S0;
else next_state = S2;
end
S2: begin
if(A) next_state = S1;
else next_state = S3;
end
S3: begin
if(A) next_state = S2;
else next_state = S0;
end
default: next_state = S0;
endcase
end
//输出
assign Y = (state == S3) ? 1'b1: 1'b0;
endmodule
ROM的简单实现
实现一个深度为8,位宽为4bit的ROM,数据初始化为0,2,4,6,8,10,12,14。可以通过输入地址addr,输出相应的数据data。接口信号图如下:
ROM_easy_design.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31module ROM_easy_design(
input clk,
input rst_n,
input [7:0]addr,
output [3:0]data
);
reg [3:0] value [7:0];
reg [3:0] data_r;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
data_r <= 4'b0;
value[0] <= 4'd0;
value[1] <= 4'd2;
value[2] <= 4'd4;
value[3] <= 4'd6;
value[4] <= 4'd8;
value[5] <= 4'd10;
value[6] <= 4'd12;
value[7] <= 4'd14;
end
else begin
data_r <= value[addr];
end
end
assign data = data_r;
endmodule
Reference
移位运算和乘法这一题有人(移位运算与乘法_牛客题霸_牛客网 (nowcoder.com))提供了更加简单的写法(它这相当于将cnt当成4个状态了,并用一段式状态机描述):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module multi_sel(
input [7:0]d ,
input clk,
input rst,
output reg input_grant,
output reg [10:0]out
);
reg [1:0]cnt;
reg [7:0]din;
always@(posedge clk or negedge rst) begin
if(!rst) begin
cnt <= 0;
out <= 0;
input_grant <= 0;
din <= 0;
end
else begin
cnt <= cnt+1;
case (cnt)
0: begin
din <= d;
input_grant <= 1;
out <= d;
end
1: begin
input_grant <= 0;
out <= (din<<2)-din;
end
2: begin
input_grant <= 0;
out <= (din<<3)-din;
end
3: begin
input_grant <= 0;
out <= (din<<3);
end
endcase
end
end
endmodule关于子模块实现三输入数的大小比较时,需要调用3个模块,这里很多同学可能疑惑为什么用3个而不是2个。参考:FPGA数字IC牛客网Verilog刷题09-子模块例化_哔哩哔哩_bilibili,解释如下:
- 第一个模块:比较 T 时刻的 a 和 b,T+1 时刻出来 tmp1; 第二个模块:比较 T 时刻的 a 和 c,T+1 时刻 出来 tmp2; 第三个模块:比较 T+1 时刻的 tmp1 和 tmp2,T+2 时刻出来 d;
- 如果只用 2 个子模块,那么 T时刻比较 a和 b得到 tmp1 ,再比较 tmp1和 c的时候是 T+1时刻的 c 和 T+1时刻的 tmp1 ,而 tmp1代表的是 T时刻 a和 b的较小值,所以这时候比较的 T时刻的 a 、 b 和 T+1时刻的 c ,显然不符合要求。