题解 | Verilog刷题解析及对应笔试面试注意点【1-5】(涉及复位、有符号数问题等)

2022-05-26 15:40:05 浏览数 (1)

在B站录制了试题讲解视频,更新题目解析文章本文更新了第1-5题

目的:不仅仅是解题,更多的是想从真实的FPGA和数字IC实习秋招和实际工程应用角度,解读一些【笔试面试】所注意的知识点,做了一些扩展

刷题地址:https://www.nowcoder.com/exam/oj?tab=Verilog篇&topicId=302&fromPut=pc_zh_s_1540795715

第一题——四选一选择器(三目运算符?:和case语句)

第二题——T触发器(异步复位和同步复位)

第三题——奇偶校验(实际上这个题应该是奇偶检测)

第四题——移位拼接乘法(有符号数和无符号数的乘法和除法)

第五题——位拆分

视频:

文章:

第一题——四选一选择器

1. 题目

四选一选择器。

2. 代码

2.1 写法1——三目运算符?:

assign语句 三目运算符?:

代码语言:javascript复制
`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语句

代码语言:javascript复制
`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

3. 解析

3.1 三目运算符

三目运算符?:,使用方法:

代码语言:javascript复制
d = c ? a : b;

其中,a、b、c均可以是表达式也可以是变量等,等效于if...else语句。

if...else只能在always语句描述中使用,所以有时候为了在描述组合逻辑时,一般就用?:来实现这种条件判断效果。

3.2 case语句

代码语言:javascript复制
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触发器

1. 题目

用verilog实现两个串联的异步低电平复位的T触发器的逻辑。这个题目的重点是要关注异步低电平复位

不得不读的 FPGA 设计白皮书——Xilinx FPGA 复位策略白皮书翻译(WP272)【FPGA探索者】

联发科数字IC简答题(9)——异步复位同步释放问题

2. 解析

2.1 T触发器

边沿T触发器:输入为1时下个时钟触发沿输出翻转;输入为0时下个时钟触发沿输出保持。

边沿D触发器,输入为1时下个时钟触发沿输出为1,输入为0时下个时钟触发沿输出为0。

所以关于T触发器:

代码语言:javascript复制
if(data_in == 1’b1)
data_out <= ~data_out;
else
data_out <= data_out;

2.2 异步复位

异步复位,说明复位是异步的,和时钟触发边沿无关,复位信号一旦来临就使得寄存器进行复位操作,复位信号出现在always块的敏感列表里。

对于异步的低电平复位,以下降沿作为触发边沿(高电平变为低电平的时刻),并且触发后判断复位是否为低电平,即:

always @ (posedge clk or negedge rst)

begin

if( ~rst )

...;

else

...;

end

对于异步的高电平复位,以上升沿作为触发边沿(低电平变为高电平的时刻),并且触发后判断复位是否为高电平,即:

always @ (posedge clk or posedge rst)

begin

if( rst )

...;

else

...;

end

2.3 同步复位

同步复位时,复位与时钟触发沿有关,所以在always的敏感变量中,只有时钟触发边沿,然后根据高电平或者低电平再判断复位电平。

同步低电平复位:

always @ (posedge clk)

begin

if( ~rst )

...;

else

...;

end

同步高电平复位:

always @ (posedge clk)

begin

if( rst )

...;

else

...;

end

2.4 同步复位和异步复位的优缺点

异步复位:反应快,复位电平可以小于一个时钟周期,有些触发器只有异步复位端口;

同步复位:稳定,不易受毛刺干扰,有些模块只有同步复位端口;

3. 代码

代码语言:javascript复制
`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

第三题——奇偶校验(奇偶检测)

1. 题目

用verilog实现对输入的32位数据进行奇偶校验,根据sel输出校验结果(sel=1输出奇校验,sel=0输出偶校验)。

代码语言:javascript复制
`timescale 1ns/1ns
module odd_sel(
input [31:0] bus,
input sel,
output check
);
//*************code***********//
 
//*************code***********//
endmodule

2. 解析

2.1 奇偶校验

通常所说的奇偶校验:

奇校验:对输入数据添加1位0或者1,使得添加后的数包含奇数个1;

比如100,有奇数个1,那么奇校验结果就是0,这样补完0以后还是奇数个1;

奇校验:对输入数据添加1位0或者1,使得添加后的数包含偶数个1;

回到这个题目,应该是出题人搞反了,按照出题的意思,应该不能叫奇偶校验,应该是叫奇偶检测

奇检测:输入的数据里有奇数个1就输出1;

偶检测:输入的数据里有偶数个1就输出1;

2.2 单目运算符

红框里的内容在视频讲解时有误,已更正。

单目运算符使用时,输入的数据的每一位进行运算,最后结果一定是1 bit的。

用处:

3. 代码

代码语言:javascript复制
`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

第四题——移位拼接乘法

1. 题目

已知d为一个8位数,请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效(d给出的信号的上升沿表示写入有效)。

2. 解析

2.1 移位运算实现乘法

移位运算实现乘法和无符号除法

位拼接运算符实现拼接和复制

位拼接运算符实现乘法和除法

关于有符号数和无符号数,可以参考【FPGA探索者】的相关文章【Verilog学习笔记——有符号数的乘法和加法】:

Verilog学习笔记——有符号数的乘法和加法

2.2 题目波形分析进行寄存

如下图所示的红框和绿框内的数据非常关键。如果对输入的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。

3. 代码

代码语言:javascript复制
`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

第五题——位拆分

1. 题目

输入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]

2. 解析

本题目的sel选择输出比较简单,可以用if...else if...else来完成,也可以用第1题提到的case语句实现。

问题的关键在于还是要像第4题一样做寄存。从波形上没有特别明显看出,但是要注意条件:

只有sel=0时的输入有效!所以在sel=0时用d_reg寄存,而sel不等于0时,相加操作用的是寄存数据d_reg拆分相加,而不是用当前的输入d。

这时候我们再仔细观察下波形,发现确实需要寄存。

3. 代码

代码语言:javascript复制
`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

0 人点赞