在B站录制了试题讲解视频,更新题目解析文章,本文更新了第1-5题。
目的:不仅仅是解题,更多的是想从真实的FPGA和数字IC实习秋招和实际工程应用角度,解读一些【笔试面试】所注意的知识点,做了一些扩展。
刷题地址:https://www.nowcoder.com/exam/oj?tab=Verilog%E7%AF%87&topicId=302&fromPut=pc_zh_s_1540795715
第一题——四选一选择器(三目运算符?:和case语句)
第二题——T触发器(异步复位和同步复位)
第三题——奇偶校验(实际上这个题应该是奇偶检测)
第四题——移位拼接乘法(有符号数和无符号数的乘法和除法)
第五题——位拆分
视频:
文章:
第一题——四选一选择器
四选一选择器。
assign语句+三目运算符?:
`timescale 1ns/1ns
module mux4_1(
input [1:0]d1,d2,d3,d0,
input [1:0]sel,
output [1:0]mux_out
);
assign mux_out = (sel == 2'b00) ? d3 : ((sel == 2'b01) ? d2 : (sel == 2'b10) ? d1 : d0);
endmodule
2.2 写法2——case语句
`timescale 1ns/1ns
module mux4_1(
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 = d0;
endcase
end
assign mux_out = mux_out_reg;
endmodule
三目运算符?:,使用方法:
d = c ? a : b;
其中,a、b、c均可以是表达式也可以是变量等,等效于if...else语句。
if...else只能在always语句描述中使用,所以有时候为了在描述组合逻辑时,一般就用?:来实现这种条件判断效果。
case语句:
case(表达式)
条件分支1: xxx;
条件分支2: xxx;
...
条件分支n: xxx;
缺省default: xxx;
endcase
case的使用注意点:
(1)要在always块里使用,如果是用always块描述组合逻辑,注意括号里的敏感变量列表都是电平触发,并且赋值时都要用阻塞赋值“=”;
(2)always块里的变量必须声明成reg类型,当然声明成reg类型不代表一定会综合成寄存器,只是语法要求always块里要这样;
(3)always块描述组合逻辑时,用*可以代表所有always块内敏感信号;
分支条件要写全,最好补齐default缺省条件,不然在组合逻辑中可能会由于条件不全导致出现锁存器Latch;
第二题——T触发器
用verilog实现两个串联的异步低电平复位的T触发器的逻辑。这个题目的重点是要关注异步低电平复位。
不得不读的 FPGA 设计白皮书——Xilinx FPGA 复位策略白皮书翻译(WP272)【FPGA探索者】
边沿T触发器:输入为1时下个时钟触发沿输出翻转;输入为0时下个时钟触发沿输出保持。
边沿D触发器,输入为1时下个时钟触发沿输出为1,输入为0时下个时钟触发沿输出为0。
所以关于T触发器:
if(data_in == 1’b1)
data_out <= ~data_out;
else
data_out <= data_out;
异步复位,说明复位是异步的,和时钟触发边沿无关,复位信号一旦来临就使得寄存器进行复位操作,复位信号出现在always块的敏感列表里。
对于异步的低电平复位,以下降沿作为触发边沿(高电平变为低电平的时刻),并且触发后判断复位是否为低电平,即:
always @ (posedge clk or negedge rst)
begin
if( ~rst )
...;
else
...;
end
对于异步的高电平复位,以上升沿作为触发边沿(低电平变为高电平的时刻),并且触发后判断复位是否为高电平,即:
always @ (posedge clk or posedge rst)
begin
if( rst )
...;
else
...;
end
同步复位时,复位与时钟触发沿有关,所以在always的敏感变量中,只有时钟触发边沿,然后根据高电平或者低电平再判断复位电平。
同步低电平复位:
always @ (posedge clk)
begin
if( ~rst )
...;
else
...;
end
同步高电平复位:
always @ (posedge clk)
begin
if( rst )
...;
else
...;
end
异步复位:反应快,复位电平可以小于一个时钟周期,有些触发器只有异步复位端口;
同步复位:稳定,不易受毛刺干扰,有些模块只有同步复位端口;
`timescale 1ns/1ns
module Tff_2 (
input wire data, clk, rst,
output reg q
);
// 1. 复位
//2. T触发器,D触发器
reg q1;
always @ (posedge clk or negedge rst)
begin
if(!rst) begin
q1 <= 1'b0;
end
else begin
if( data )
q1 <= ~q1;
else
q1 <= q1;
end
end
always @ (posedge clk or negedge rst)
begin
if(!rst) begin
q <= 1'b0;
end
else begin
if( q1 )
q <= ~q;
else
q <= q;
end
end
endmodule
第三题——奇偶校验(奇偶检测)
用verilog实现对输入的32位数据进行奇偶校验,根据sel输出校验结果(sel=1输出奇校验,sel=0输出偶校验)。
`timescale 1ns/1ns
module odd_sel(
input [31:0] bus,
input sel,
output check
);
//*************code***********//
//*************code***********//
endmodule
通常所说的奇偶校验:
奇校验:对输入数据添加1位0或者1,使得添加后的数包含奇数个1;
比如100,有奇数个1,那么奇校验结果就是0,这样补完0以后还是奇数个1;
奇校验:对输入数据添加1位0或者1,使得添加后的数包含偶数个1;
回到这个题目,应该是出题人搞反了,按照出题的意思,应该不能叫奇偶校验,应该是叫奇偶检测:
奇检测:输入的数据里有奇数个1就输出1;
偶检测:输入的数据里有偶数个1就输出1;
红框里的内容在视频讲解时有误,已更正。
单目运算符使用时,输入的数据的每一位进行运算,最后结果一定是1 bit的。
用处:
`timescale 1ns/1ns
module odd_sel(
input [31:0] bus,
input sel,
output check
);
//*************code***********//
wire check_tmp;
// 单目运算符
assign check_tmp = ^bus;
// assign check = (sel == 1'b1) ? check_tmp : ~check_tmp;
reg check_reg;
always @ (*) begin
if(sel) begin
check_reg = check_tmp;
end
else begin
check_reg = ~check_tmp;
end
end
assign check = check_reg;
//*************code***********//
endmodule
第四题——移位拼接乘法
已知d为一个8位数,请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效(d给出的信号的上升沿表示写入有效)。
移位运算实现乘法和无符号除法:
位拼接运算符实现拼接和复制:
位拼接运算符实现乘法和除法:
关于有符号数和无符号数,可以参考【FPGA探索者】的相关文章【Verilog学习笔记——有符号数的乘法和加法】:
如下图所示的红框和绿框内的数据非常关键。如果对输入的d在连续的4个时钟周期内分别进行d*1、d*3、d*7和d*8操作,那么当出现如红框内所示的6时,这个数据只持续了1个clk,显然这时候做的操作是:
6*1、128*3、129*7、129*8,和预期不符。
如何保证做的移位乘法都是基于第一次的输入呢?
答案:加一个寄存器,对输入寄存。
d_reg <= d;
后面的*3、*7、*8均对d_reg操作,执行完后再根据输入d更新d_reg。
`timescale 1ns/1ns
module multi_sel(
input [7:0]d ,
input clk,
input rst,
output reg input_grant,
output reg [10:0]out
);
//*************code***********//
reg [1:0] count; // 0 1 2 3
always @ (posedge clk or negedge rst)
begin
if(~rst) begin
count <= 2'b0;
end
else begin
count <= count + 1'b1;
end
end
// FSM
reg [7:0] d_reg;
always @ (posedge clk or negedge rst)
begin
if(~rst) begin
out <= 11'b0;
input_grant <= 1'b0;
d_reg <= 8'b0;
end
else begin
case( count )
2'b00 : begin
out <= d;
d_reg <= d;
input_grant <= 1'b1;
end
2'b01 : begin
out <= d_reg + {d_reg, 1'b0}; // *1 + *2
input_grant <= 1'b0;
end
2'b10 : begin
out <= d_reg + {d_reg, 1'b0} + {d_reg, 2'b0};
input_grant <= 1'b0;
end
2'b11 : begin
out <= {d_reg, 3'b0};
input_grant <= 1'b0;
end
default : begin
out <= d;
input_grant <= 1'b0;
end
endcase
end
end
//*************code***********//
endmodule
第五题——位拆分
输入16位数据d[15:0],按照sel选择输出,并输出valid_out信号(在不输出时候拉低)
sel = 0:不输出且只有此时的输入有效
sel = 1:输出d[3:0]+d[7:4]
sel = 2:输出d[3:0]+d[11:8]
sel = 3:输出d[3:0]+d[15:12]
本题目的sel选择输出比较简单,可以用if...else if...else来完成,也可以用第1题提到的case语句实现。
问题的关键在于还是要像第4题一样做寄存。从波形上没有特别明显看出,但是要注意条件:
只有sel=0时的输入有效!所以在sel=0时用d_reg寄存,而sel不等于0时,相加操作用的是寄存数据d_reg拆分相加,而不是用当前的输入d。
这时候我们再仔细观察下波形,发现确实需要寄存。
`timescale 1ns/1ns
module data_cal(
input clk,
input rst,
input [15:0]d,
input [1:0]sel,
output [4:0]out, // wire
output validout // wire
);
//*************code***********//
reg [15:0] d_reg;
wire [3:0] d0;
wire [3:0] d1;
wire [3:0] d2;
wire [3:0] d3;
assign d0 = d_reg[3:0];
assign d1 = d_reg[7:4];
assign d2 = d_reg[11:8];
assign d3 = d_reg[15:12];
reg [4:0] out_reg;
reg validout_reg;
always @ (posedge clk or negedge rst)
begin
if( ~rst ) begin
out_reg <= 5'b0;
validout_reg <= 1'b0;
d_reg <= 16'b0;
end
else begin
case( sel )
2'b00 : begin
d_reg <= d;
out_reg <= 5'b0;
validout_reg <= 1'b0;
end
2'b01 : begin
d_reg <= d_reg;
out_reg <= d_reg[3:0] + d_reg[7:4];// d0 + d1;
validout_reg <= 1'b1;
end
2'b10 : begin
d_reg <= d_reg;
out_reg <= d0 + d2;
validout_reg <= 1'b1;
end
2'b11 : begin
d_reg <= d_reg;
out_reg <= d0 + d3;
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;
//*************code***********//
endmodule