暑期实习准备——手撕代码
- 牛客刷题笔记
- Verilog快速入门
- VL4 移位运算与乘法
- VL5 位拆分与运算
- VL6 多功能数据处理器
- VL8 使用generate…for语句简化代码
- VL9 使用子模块实现三输入数的大小比较
- VL11 4位数值比较器电路
- VL12 4bit超前进位加法器电路
- VL13 优先编码器电路①
- Verilog进阶挑战
- Verilog企业真题
- 常见数字IC手撕代码
牛客刷题笔记
牛客Verilog题库
Verilog快速入门
VL4 移位运算与乘法
- 要点1:题目用状态机实现,经典的两段式模板。
- 要点2:需要一个d_reg信号临时记录当前要加倍的d信号,这样才能保证d信号变化时,加倍的仍然是原本的d信号,注意d_reg<=d;赋值语句应该在11状态,即每一轮状态转换结束后,开启新一轮状态前。
- 要点3:always @ (posedge clk or negedge rst or current_state) 第二段的敏感事件表套模板的时候误删了current_state信号,导致所有的状态机状态转换被延长了一拍,因此这里的敏感时间表一定要将记住写current_state。
- 要点4:注意为状态机以及其他输出信号赋初始值。
- 要点5:1、3、7、8倍数通过移位运算和加运算实现,不需要乘法运算。
`timescale 1ns/1ns
module multi_sel(
input [7:0]d ,
input clk,
input rst,
output reg input_grant,
output reg [10:0]out
);
reg [1:0] current_state,next_state;
reg[7:0] d_reg;
//*************code***********//
always @ (posedge clk or negedge rst) begin
if(!rst) current_state<=2'b00;
else current_state<=next_state;//非阻塞赋值
end
always @ (posedge clk or negedge rst or current_state) begin
next_state<=2'b00;
if(!rst) begin
input_grant<=1'b0;
out<=11'b0;
d_reg<=d;
end
else begin
case(current_state)
2'b00:begin
input_grant<=1'b1;
out<=d_reg;
next_state<=2'b01;
end
2'b01:begin
input_grant<=1'b0;
out<= d_reg + {2'b0, d_reg, 1'b0};
next_state<=2'b10;
end
2'b10:begin
input_grant<=1'b0;
out<=d_reg + {2'b0, d_reg, 1'b0} + {1'b0 ,d_reg, 2'b00};
next_state<=2'b11;
end
2'b11:begin
input_grant<=1'b0;
out<={d_reg, 3'b000};
next_state<=2'b00;
d_reg<=d;
end
endcase
end
end
//*************code***********//
endmodule
VL5 位拆分与运算
- 要点1:仔细读题,注意题目中提到的只有在sel=0时输入才有效,因此需要reg_d把输入d锁存。
- 要点2:仔细读题,还是要理解题目的意思。
`timescale 1ns/1ns
module data_cal(
input clk,
input rst,
input [15:0]d,
input [1:0]sel,
output reg [4:0]out,
output reg validout
);
//*************code***********//
reg [15:0] reg_d;
always @ (posedge clk or rst or d) begin
if(!rst) begin
out<=5'b0;
validout<=0;
end
else begin
case(sel)
2'b00:begin out<=5'b0; validout<=0; reg_d<=d ;end
2'b01:begin out<=reg_d[3:0]+reg_d[7:4]; validout<=1; end
2'b10:begin out<=reg_d[3:0]+reg_d[11:8]; validout<=1; end
2'b11:begin out<=reg_d[3:0]+reg_d[15:12]; validout<=1; end
endcase
end
end
//*************code***********//
endmodule
VL6 多功能数据处理器
- 要点1:有符号数以补码形式存储,可以直接进行加减运算。
- 要点2:
always @ (posedge clk or negedge rst_n)
的写法是正确的,但有点疑惑是初始状态下就是低电平会不会触发,本题既然是正确的就说明初始低电平会触发。
always @ (posedge clk or rst_n)
的写法是错误的,会在rst_n由0变为1时触发,但此时可能并非时钟上升沿,造成异常输出。
always @ (posedge clk)
的写法会使得初始状态下输出信号处于无效状态,额外在always块前加上initial c<=9'b0;
这样可以通过测试用例,但是initial语句不可综合,最好不要使用。
`timescale 1ns/1ns
module 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:begin c<=a; end
2'b01:begin c<=b; end
2'b10:begin c<=a+b; end
2'b11:begin c<=a-b; end
endcase
end
end
endmodule
VL8 使用generate…for语句简化代码
- 要点1:必须使用
genvar
声明循环变量。 - 要点2:
begin-end
之间插入赋值语句,begin
后面必须声明循环实例的名称。 - 要点3:
generate-for
常用来简化assign的赋值,assign data_out[i]=data_in[7-i];
展开后的8条assign语句是并行赋值的。如,在6线-64线、8线-256线译码器中可以用来简化代码。
`timescale 1ns/1ns
module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
genvar i;
generate for(i=0;i<=7;i=i+1)
begin:label
assign data_out[i]=data_in[7-i];
end
endgenerate
endmodule
VL9 使用子模块实现三输入数的大小比较
- 要点1:两个比较器会导致进入比较器的三个输入时间不同步,需要使用三个子模块才可以正确实现。
在第一个上升沿,ab进入第一个比较器,c进入第二个比较器。但是比较器输出是需要时间的,在第一个上升沿,c立刻进入第二个比较器,但ab比较器的输出会较晚的进入第二个比较器,这就造成了输入时间不同步,从而造成输出错误。
使用三个比较器将c延迟一拍,就可以让输入子模块的时间都相等,从而不会出现进入时间不同导致的错误。
我们可以对波形进行分析来比较这两种写法的差别,testbench如下:
`timescale 1ns/1ns
module testbench();
reg signed [7:0] a,b,c;
reg clk,rst_n;
wire [7:0]d;
main_mod dut(
.clk(clk),
.rst_n(rst_n),
.a(a),
.b(b),
.c(c),
.d(d)
);
always #5 clk = !clk;
initial begin
clk=0;rst_n=0;
#5 rst_n=1;
#10 a=2;b=3;c=4;
#10 a=4;b=2;c=1;
#10 a=5;b=4;c=3;
end
endmodule
三个比较器的仿真波形如下。可以看到每一次比较的结果都在下一个时钟周期输出,分别为2,1,3。
二个比较器的仿真波形如下,此时比较结果为1,2,3,这是因为上一轮a,b的最小值实际上是与当前周期的c进行比较的,因此对于第一组输入,a和b的比较结果是2,2和下一组输入的c=1进行了比较,所以输出的最小值是1;同样第二组输出,a和b的比较结果是2,2和下一组输入的c=3进行了比较,所以输出为2;第二组输出,a和b的比较结果是4,4和下一组输入的c=3进行了比较,所以输出为3。
- 要点2:子模块中
c<=(a>b)?b:a;
语句需要使用非阻塞赋值。
不知道为什么非阻塞不会报错,阻塞会报错显示有用例不通过。使用vivado综合出来的电路这两个是一样的。待解决!!!
但还是复习一下阻塞赋值和非阻塞赋值
非阻塞赋值b <= a;
:非阻塞赋值中赋值并不是马上执行的,也就是说"always"块内的下一条语句执行后,b并不等于a,而是保持原来的值。"always"块结束后,才进行赋值。
阻塞赋值b=a;
:方式是马上执行的。也就是说执行下一条语句时,b已等于a。
`timescale 1ns/1ns
module 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] ab,ac;
sub_mod U1(.clk(clk),.rst_n(rst_n),.a(a),.b(b),.c(ab));
sub_mod U2(.clk(clk),.rst_n(rst_n),.a(a),.b(c),.c(ac));
sub_mod U3(.clk(clk),.rst_n(rst_n),.a(ab),.b(ac),.c(d));
endmodule
module sub_mod(clk,rst_n,a,b,c);
input clk,rst_n;
input[7:0] a,b;
output [7:0] c;
reg[7:0]c;
always@(posedge clk or negedge rst_n)begin
if(~rst_n)c<=8'b0;
else begin
c<=(a>b)?b:a;
end
end
endmodule
VL11 4位数值比较器电路
要点1:题目中说要用门级描述完成,对于1bit数的比较对应的门运算如下,A>B对应~A&B,A<B对应A&~B,A=B对应~(A^B)。
`timescale 1ns/1ns
module 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
);
assign Y2=(A[3]>B[3])|((A[3]==B[3])&(A[2]>B[2]))|((A[3]==B[3])&(A[2]==B[2])&(A[1]>B[1]))|((A[3]==B[3])&(A[2]==B[2])&(A[1]==B[1])&(A[0]>B[0]));
assign Y1=(A==B);
assign Y0=(A[3]<B[3])|((A[3]==B[3])&(A[2]<B[2]))|((A[3]==B[3])&(A[2]==B[2])&(A[1]<B[1]))|((A[3]==B[3])&(A[2]==B[2])&(A[1]==B[1])&(A[0]<B[0]));
endmodule
要点2:抽象描述可以写到直接比较A和B。
`timescale 1ns/1ns
module 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
);
assign Y2=(A>B)?1:0;
assign Y1=(A==B)?1:0;
assign Y0=(A<B)?1:0;
endmodule
VL12 4bit超前进位加法器电路
加法器与半加器
超前进位加法器
1比特进位加法器的两种实现:
//实现1:逻辑代数
assign sum=a^b^cin;
assign cout=a&b|(cin&(a^b));或者assign cout=(a&b)|(a&cin)|(b&cin);
//实现2:抽象描述
assign {cout,sum}=a+b+cin;
assign cout=(a&b)|(a&cin)|(b&cin);
很容易理解,即a,b,cin中任意两个或以上为1就进位。
assign cout=a&b|(cin&(a^b));
可以理解为除了a,b为1的情况外,还有c为1同时 a和b中任意一个为1(a^b
)。
多位数进位加法器
多位数进位加法器的实现有两种方式:串行进位加法器、超前进位加法器。
串行进位加法器就是将1比特加法器级联。
超前进位加法器是对串行全加器进行改良设计的并行加法器,以解决普通全加器串联互相进位产生的延迟。
一位全加器的进位的运算逻辑(前面的式子是(A^B)
这里是(A|B)
,对结果没有影响,|
包含了^
):
其中,令进位函数Gi = AiBi
, 令进位传送函数Pi = Ai + Bi
;
对于4比特超前进位加法器来说,进位输出如下:
Si=Ai^Bi^(CI)i
,对应的(CI)i=(CO)i-1
,即Si=Ai^Bi^(CO)i-1
。通过前面的推导已经得出了,可得:
- 要点1:需要明确4bit超前进位加法器的原理与推导过程,上一位运算的输出CO是下一位运算的输入CI,
COi=(Ai&Bi)|(CIi&(Ai|Bi))。
- 要点2:仿真自测时定义了一个8bit数num每个时钟周期累加1,将低4比特和高4比特赋值给A和B,需要注意的是给num赋初值!!!要不然仿真波形一片红!!!
`timescale 1ns/1ns
module 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,p,c;
assign p=A_in|B_in;
assign g=A_in&B_in;
assign c[0]=g[0]|(p[0]&C_1);
assign c[1]=g[1]|(p[1]&(g[0]|(p[0]&C_1)));
assign c[2]=g[2]|(p[2]&(g[1]|(p[1]&(g[0]|(p[0]&C_1)))));
assign c[3]=g[3]|(p[3]&(g[2]|(p[2]&(g[1]|(p[1]&(g[0]|(p[0]&C_1)))))));
assign CO=c[3];
assign S=A_in^B_in^{c[2:0],C_1};
endmodule
测试代码:
`timescale 1ns/1ns
module testbench();
reg[3:0] A_in,B_in;
reg C_1;
wire CO;
wire[3:0] S;
reg[7:0] num;
lca_4 U1(.A_in(A_in),.B_in(B_in),.C_1(C_1),.CO(CO),.S(S));
initial begin
C_1=0;
num=0;
end
always #10 begin
num=num+1;
A_in<=num[3:0];
B_in<=num[7:4];
end
endmodule
VL13 优先编码器电路①
要点1:题目给的是I1-I9,注意顺序。
要点2:case
、casez
和casex
三者都是可以综合的。case
进行全等匹配,casez
忽略?
或z
对应的位进行匹配,casex
忽略x
、?
或z
对应的位进行匹配。
`timescale 1ns/1ns
module encoder_0(
input [8:0] I_n,
output reg [3:0] Y_n
);
always@(I_n)begin
casez(I_n)
9'b111111111:Y_n<=4'b1111;
9'b0????????:Y_n<=4'b0110;
9'b10???????:Y_n<=4'b0111;
9'b110??????:Y_n<=4'b1000;
9'b1110?????:Y_n<=4'b1001;
9'b11110????:Y_n<=4'b1010;
9'b111110???:Y_n<=4'b1011;
9'b1111110??:Y_n<=4'b1100;
9'b11111110?:Y_n<=4'b1101;
9'b111111110:Y_n<=4'b1110;
endcase
end
endmodule
要点3:《CPU设计实战》这本书中提到在CPU设计中必须遵守的硬性规定是代码中禁止出现casez
、casex
,因此可以采用如下写法,看Y9~Y0
中出现的第一个0
的位置。
`timescale 1ns/1ns
module encoder_0(
input [8:0] I_n,
output [3:0] Y_n
);
assign Y_n=(~I_n[8])?4'b0110:
(~I_n[7])?4'b0111:
(~I_n[6])?4'b1000:
(~I_n[5])?4'b1001:
(~I_n[4])?4'b1010:
(~I_n[3])?4'b1011:
(~I_n[2])?4'b1100:
(~I_n[1])?4'b1101:
(~I_n[0])?4'b1110:
4'b1111;
endmodule
Verilog进阶挑战
Verilog企业真题
常见数字IC手撕代码
数字IC笔试面试题汇总
- 异步fifo。格雷码的镜像对称。格雷码和二进制的互相转换。
- 同步fifo。
- 除法器。
- Wallace乘法器。
- Booth乘法器。
- Booth+Wallace乘法器。
- 超前进位加法器。
- 边沿检测,输入消抖,毛刺消除。
- 异步复位同步释放。
- 三种计数器。二进制,移位,移位+反向。
- 无毛刺时钟切换。
- 串行-并行CRC。(ARM)。
- 线性反馈移位寄存器。
- 握手实现CDC。
- AXI-S接口,2T一次传输,1T一次传输,1T一次传输还要寄存器寄存。(Nvidia考题)Xilinx有例程。
- 其他简单功能的HDL实现以及状态转换图。(序列检测 ,回文序列检测(NVIDIA),奇、偶、半分频,小数分频,自动售货机)。