本次带来牛客网Verilog的第一题到第五题,本人才疏学浅,如有不对之处欢迎评论区指正。
VL1 四选一多路器
一个四选一MUX,题目本身没有难度,但是这个题目很坑,做的时候要看清状态转移关系。它提供的波形图是不太完整的。
sel | 输出 |
00 | d3 |
01 | d2 |
10 | d1 |
11 | d0 |
具体的思路就是采用三目运算符,首先判断sel高比特,再判断sel的低比特。三目运算符本身就是一个2选一的MUX。当然这个题也可以用case语句做,那样逻辑更加清晰。
`timescale 1ns/1ns module mux4_1( input [1:0]d1,d2,d3,d0, input [1:0]sel, output[1:0]mux_out ); //*************code***********// assign mux_out = sel[1] ? (sel[0] ? d0 : d1) : (sel[0] ? d2 : d3); //*************code***********// endmodule
VL2 异步复位的串联T触发器
这个题目信号给的是rst,结果波形是低电平复位。大家学习的时候千万不要这么写,低电平复位用rst_n,resetn等都是可以的,用于表示negative。
回到题目本身,T触发器是指当输入为1时,对当前输出取反,否则保持不变。假设不知道这点也没关系,大家仔细观察波形应该可以发现,在第一轮data拉低的时候,q保持不变。而在data拉高以后,连续采集到两个高电平以后拉高,又过了两个时钟周期拉低。然后在第二轮data拉低的时候,q发生变化。这中间一定是什么影响了q的输出。
由于这是串联逻辑,可以思考一下q和data之间的信号temp是如何变化的。由于第一轮data拉低的时候q始终保持不变,因此可假设data拉低的时候T触发器的输出保持不变(而不是为0,思考一下为什么)。当data拉高两个周期以后,q拉高,然后又拉低。因此可以认为在data拉高的时候T触发器输出的值会发生翻转(而不是变为1,思考一下为什么)。将上述推理带入完整的波形,发现符合要求。因此可以实现代码如下:
`timescale 1ns/1ns module Tff_2 ( input wire data, clk, rst, output reg q ); //*************code***********// reg tmp; always @(posedge clk, negedge rst) begin if(~rst) begin q <= 1'b0; tmp <= 1'b0; end else begin tmp <= data^tmp; q <= tmp^q; end end //*************code***********//
当然最简单的方法还是知道T触发器的原理。关于T触发器,大家心里有个概念即可。实际的数字电路设计中已经没有使用T触发器的了。
VL3 奇偶校验
奇偶校验就是数1的个数!注意:无论是奇校验还是偶校验跟0都没有关系。奇校验就是要使得1的个数为奇数,偶校验就是要保证1的个数为偶数。
基于此,我们首先要判断输入d中的1的个数是奇数还是偶数。如何判断呢?熟悉数字电路的小伙伴应该知道了,使用异或操作符即可。奇数个1互相异或得到的结果是1,而偶数个1互相异或得到的结果是0,而任何数和0异或的结果都是本身。因此对输入d进行异或如果为1,则代表d中1的个数为奇数。
因此当奇校验的时候,如果输入是奇数个1,则补0,反之补1。总而言之是要让1的个数为奇数。对于偶校验也是类似的道理。
回到题目本身,这个出题人题目应该出错了。根据它波形的意思,输入d有奇数个1且sel为1的时候,此时check为1,当输入d有奇数个1且sel为0的时候,此时check为0。d有偶数个1的时候则相反,此时应该叫做奇偶检测而不是奇偶校验。其代码如下。
`timescale 1ns/1ns module odd_sel( input [31:0] bus, input sel, output check ); //*************code***********// assign check=(^bus)?sel:~sel; //*************code***********// endmodule
VL4 移位运算与乘法
对于硬件电路,其乘法本质都是转换成加法和移位来进行运算的。乘以2相当于左移。写代码的时候将乘法运算转换为位运算,是一种比较常见的定点数运算处理方式。这种技巧在C语言中也很常用
对于本题而言,可以看到输入不是每一拍都能采到的。该模块实际上是4个周期一个循环,只有第一个时钟周期才能采到输入进行运算。因此需要一个计数器来协助功能实现(计数器在Verilog设计中非常常见,很多波形,逻辑都能通过计数器凑出来,希望读者好好理解这一点),以下代码合并成一个always块了,实际写的时候最好将计数器单独弄一个always块。
always@(posedge clk or negedge rst) begin if(!rst) begin cnt <= 0; out <= 0; input_grant <= 0; din <= 0; end else begin cnt <= cnt+1; case (cnt) 0: begin din <= d; input_grant <= 1; out <= d; end 1: begin input_grant <= 0; out <= (din<<2)-din; end 2: begin input_grant <= 0; out <= (din<<3)-din; end 3: begin input_grant <= 0; out <= (din<<3); end endcase end end
VL5 位拆分与运算
寄存器的各个比特是可以分开单独运算的。其中每一段可能有不同的含义。这就是俗称的字段,在很多情况下一个输入既包括数据又包括地址等信息。
此外根据sel选择四个数据相加结果。换言之四个数据的相加结果都要算出来,通过sel进行选择即可。而多选一对应的是case语句。因此可以很容易的知道代码如下
`timescale 1ns/1ns module data_cal( input clk, input rst, input [15:0]d, input [1:0]sel, output [4:0]out, output validout ); //*************code***********// reg[15:0] d_in; reg[4:0] out; reg validout; always@(posedge clk or negedge rst) begin if (!rst) d_in <= 0; else if(!sel) d_in <= d; end always @(posedge clk, negedge rst) begin if(~rst) begin out <= 5'd0; validout <= 5'd0; end else begin case(sel) 2'b00: begin validout <= 1'b0; out <= 5'd0; end 2'b01: begin validout <= 1'b1; out <= d_in[3:0] + d_in[7:4]; end 2'b10: begin validout <= 1'b1; out <= d_in[3:0] + d_in[11:8]; end 2'b11: begin validout <= 1'b1; out <= d_in[3:0] + d_in[15:12]; end endcase end end //*************code***********// endmodule