暑期实习准备——Verilog手撕代码(持续更新中。。。

Posted diamond_biu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了暑期实习准备——Verilog手撕代码(持续更新中。。。相关的知识,希望对你有一定的参考价值。

暑期实习准备——手撕代码

牛客刷题笔记

牛客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:casecasezcasex三者都是可以综合的。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'

以上是关于暑期实习准备——Verilog手撕代码(持续更新中。。。的主要内容,如果未能解决你的问题,请参考以下文章

(待更新)校招Verilog手撕代码(真题)VL16单bit信号毛刺滤除电路设计

大三计算机专业要不要去暑期实习呢?

2019字节跳动面试时手撕代码题(持续更新~)

大三萌新,给自己制定两个月的暑期实习准备计划!

大三萌新,给自己制定两个月的暑期实习准备计划!

FPGA/数字IC手撕代码3——通过纯verilog实现简单的ROM