在B站【FPGA探索者】录制了试题讲解视频,本文更新了第6-9题文字解析。
目的:不仅仅是解题,更多的是想从真实的FPGA和数字IC实习秋招和实际工程应用角度,解读一些【笔试面试】所注意的知识点,做了一些扩展。
第六题——多功能数据处理(有符号数、case语句,简单)
第七题——求两个数的差值(if...else,==和===的区别,有2个笔试注意点,建议看一看)
第八题——使用generate…for语句简化代码(for和generate...for用法、区别,给出两种实现方式)
第九题——子模块例化(时序逻辑和组合逻辑两种实现方式,问题集中在时序逻辑时使用2个子模块造成答案错误,需要分析下信号的波形,可以多体会一下;给出两种实现方式)
第六题——多功能数据处理(有符号数、case语句,简单不过多讲)
有符号数和case可以参考:
题解 | Verilog刷题解析及对应笔试面试注意点【1-5】(涉及复位、有符号数问题等)
根据指示信号select的不同,对输入信号a,b实现不同的运算。输入信号a、b为8bit有符号数,
当select[1:0] = 0,输出a;
当select[1:0] = 1,输出b;
当select[1:0] = 2,输出a+b;
当select[1:0] = 3,输出a-b。
`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
);
endmodule
有符号数,输入8-bit,输出9-bit,最好是手动扩展符号位再输出,加、减操作时也是手动扩展符号位再加减。
本题目比较简单,但是还是有一些值得注意的点,中间改动一些东西可能就会出错。因为输入输出都已经直接定义了signed有符号数类型,所以直接相加、相减也没有问题,不会出现运算错误。
有符号数 + 有符号数 = 有符号数
这其中,如果加数中有无符号数,那么就会按照无符号运算。
如果表达式中有一个无符号数,则所有的操作数都会被强行转换为无符号数;
即:有符号A + 无符号B时,会将补码表示的有符号A当成无符号数A1,,再计算A1+B,这样得到的结果就是错的了。
两种解决方法:
(1)涉及到有符号数运算时,和有符号相关的输入、输出、中间变量均定义成signed有符号数,这样全部遵循有符号数运算规则;
(2)用位拼接符补齐符号位;
// Verilog 使用位拼接,扩展符号位
// 作者:FPGA探索者
`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[7], a};
end
2'b01 : begin
c <= {b[7], b};
end
2'b10 : begin
c <= {a[7], a} + {b[7], b};
end
2'b11 : begin
c <= {a[7], a} - {b[7], b};
end
default : begin
c <= 9'b0;
end
endcase
end
end
endmodule
第七题——求两个数的差值(if...else,有个笔试注意点,建议看一看)
本题目虽然简单,但是值得探究if...else仿真时的注意点,笔试遇到过,你不一定能答对哦~
根据输入信号a,b的大小关系,求解两个数的差值:输入信号a、b为8bit位宽的无符号数。
如果a>b,则输出a-b;
如果a≤b,则输出b-a。
`timescale 1ns/1ns
module data_minus(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
output reg [8:0]c
);
endmodule
if..else语句即可。关注点还是和第6题一样,有符号数问题。
if...else的注意点:
if里只能判断条件是0还是1,如果出现X或者Z,则判断不出来。
答案:不一样!
(1)
对于
always @ (*)
begin
if(a == 1'b0)
b = 1'b0;
else
b = 1'b1;
end
当输入a是0时,b输出0,否则b输出1(否则的意思是只要不等于0!包括了x和z也是不等于0,不是只有1不等于0);
其他两种情况夏是一样的,if里面只能判断0和1,当出现x和z时到else里。
具体仿真图如下:
有人要问了,如果直接判断if(a == 1’bx)或者if(a == 1’bz)呢?
当a = 1’bx时,if里的a==1’bx结果不为真!
这一部分又涉及到了两个等号==和三个等号===。
参考夏宇闻老师的书籍:
if(A==1’bx) $display(“AisX”); (当A等于X时,这个语句不执行)
if(A===1’bx) $display(“AisX”); (当A等于X时,这个语句执行)
注意,这部分在很多笔试题中出现过,判断条件和对应输出。
`timescale 1ns/1ns
module 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 <= 8'b0;
end
else begin
if( a > b ) begin
c <= a - b;
end
else begin
c <= b - a;
end
end
end
endmodule
第八题——使用generate…for语句简化代码(for和generate...for用法、区别)
在某个module中包含了很多相似的连续赋值语句,请使用generata…for语句编写代码,替代该语句,要求不能改变原module的功能。使用Verilog HDL实现以上功能并编写testbench验证。
module 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];
endmodule
for循环,必须在always块里使用。对应的,always块内的变量要声明成reg类型。
for(表达式1;表达式2;表达式3),执行时对表达式1、2、3和C语言中一样:
(1)执行表达式1,一般是循环变量赋初值;
(2)执行表达式2,若结果为真则执行for里面的内容,否则结束for语句;
(3)执行完for里面的语句,执行表达式3,一般是循环变量自增、自减、移位等操作,回到(2);
verilog的for和C语言的for的不同点;
C语言的for里面的语句是串行顺序执行,而verilog的for内的语句实际是并行的,只是为了写代码方便才用for对多个同样的结构赋值。
比如:
实现移位寄存器时:
integer i;
always @ (posedge clk)
begin
data_reg[0] <= data_in;
for(i = 0; i < 4; i = i+1) begin
data_reg[i+1] <= data_reg[i];
end
end
等效的语句:
always @ (posedge clk)
begin
data_reg[0] <= data_in;
data_reg[1] <= data_reg[0];
data_reg[2] <= data_reg[1];
data_reg[3] <= data_reg[2];
data_reg[4] <= data_reg[3];
end
当相同结构的赋值语句较多时,使用for语句能够简化代码,并不会影响实际综合后的电路结构。
genvar i;
generate for(i=0;表达式2;表达式3)
begin : 起个名字
end
endgenerate
作用上:和for是一样的;
区别:
(1)generate for的循环变量必须用genvar声明,for的变量用integer整数类型声明;
(2)for只能用在always块里面,generate for可以做assign赋值,用always块话always写在generate for里;
(3)generate for后面必须给这个循环起一个名字,for不需要;
(4)generate for还可以用于例化模块;
上面的for如果用generate...for写:
always @ (posedge clk)
begin
data_reg[0] <= data_in;
end
genvar i;
generate for(i = 0; i < 4; i = i+1) begin : shift_reg
always @ (posedge clk)
begin
data_reg[i+1] <= data_reg[i];
end
end
endgenerate
`timescale 1ns/1ns
module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
// 2. for,必须写在always块里
integer i;
reg [7:0] dout_reg;
always @ (*) begin
for(i = 0; i < 8; i = i+1) begin
dout_reg[i] = data_in[7-i];
end
end
assign data_out = dout_reg;
endmodule
`timescale 1ns/1ns
module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
// 必须使用genvar 声明循环变量
genvar ii;
generate for(ii = 0; ii < 8; ii = ii+1)
begin : aaa_i
assign data_out[ii] = data_in[7-ii];
end
endgenerate
endmodule
第九题——子模块例化(时序逻辑和组合逻辑两种实现方式,问题集中在时序逻辑时使用2个子模块造成答案错误,需要分析下信号的波形)
module的使用和例化,这个题的问题主要集中在为什么使用2个子模块不对,用3个才对。
实际上,对于组合逻辑实现的子模块,可以用2个,但是要打两拍才和给的波形一致。
对于时序逻辑实现的子模块,更值得大家仔细思考一下波形时序,2个确实不对,发生了比较错位,下面将详细说明。
在数字芯片设计中,通常把完成特定功能且相对独立的代码编写成子模块,在需要的时候再在主模块中例化使用,以提高代码的可复用性和设计的层次性,方便后续的修改。
请编写一个子模块,将输入两个8bit位宽的变量data_a,data_b,并输出data_a,data_b之中较小的数。并在主模块中例化,实现输出三个8bit输入信号的最小值的功能。
子模块的信号接口图如下:
主模块的信号接口图如下:
使用Verilog HDL实现以上功能并编写testbench验证。
输入描述:
clk:系统时钟
rst_n:异步复位信号,低电平有效
a,b,c:8bit位宽的无符号数
输出描述:
d:8bit位宽的无符号数,表示a,b,c中的最小值
一个.v文件中可以写多个模块module(一般是一个文件写一个module),其中主模块的名字和.v文件名相同。
module 模块名 (
端口描述
);
...
endmodule
例化:
模块名 例化名(
端口参数传递(按位置或者按名字均可)
);
比如模块名是“人”,例化名可以起名为“张三”、“李四”,这样就例化了2个“人”。
两种情况:
(1)子模块是纯组合逻辑
如果你的子模块用的全部是组合逻辑实现的比较,那么可以使用2个子模块,和波形不对应的原因在于提前了1个时钟。使用2个组合逻辑得到最小值后,再主模块里要对这个值打两拍,这样时序和题目答案的波形一致。
(2)子模块是时序逻辑
子模块里面的比较也选择时序逻辑寄存输出。
先说使用三个子模块的做法,a、b比较,在T+1时刻输出最小值tmp1,同时a、c比较在T+1时刻得到最小值tmp2,然后是tmp1和tmp2比较在T+2时刻得到最小值d;
一定注意这个同时的含义。T时刻输入a、b、c,T+1时刻得到tmp1和tmp2,T+2时刻得到d;
如果是使用2个子模块,T时刻比较a、b,在T+1时刻输出最小值tmp1,然后T+1时刻比较时才是c和tmp1比,在T+2时刻得到T+1时的c和tmp1的最小值;
注意,这样比较完就是拿T时刻的a、b和T+1时刻的c比较!
`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] tmp1; // a b 的最小值
child_mod U0(
.a ( a ),
.b ( b ),
.d ( tmp1 )
);
wire [7:0] tmp2; // a c 的最小值
child_mod U1(
.a ( tmp1 ),
.b ( c ),
.d ( tmp2 )
);
reg [7:0] d_reg;
reg [7:0] d_reg2;
always @ (posedge clk or negedge rst_n)
begin
if( ~rst_n ) begin
d_reg <= 8'b0;
d_reg2 <= 8'b0;
end
else begin
d_reg <= tmp2;
d_reg2 <= d_reg;
end
end
assign d = d_reg2;
endmodule
module child_mod(
input [7:0]a,
input [7:0]b,
output [7:0]d
);
assign d = (a>b) ? b : a;
endmodule
`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] tmp1; // a b 的最小值
child_mod U0(
.clk ( clk ),
.rst_n ( rst_n ),
.a ( a ),
.b ( b ),
.d ( tmp1 )
);
wire [7:0] tmp2; // a c 的最小值
child_mod U1(
.clk ( clk ),
.rst_n ( rst_n ),
.a ( tmp1 ),
.b ( c ),
.d ( tmp2 )
);
child_mod U2(
.clk ( clk ),
.rst_n ( rst_n ),
.a ( tmp1 ),
.b ( tmp2 ),
.d ( 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
d_reg <= tmp2;
end
end
assign d = d_reg;
endmodule
module 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