系列文章目录
文章目录
前言
这不马上要面试了嘛,有些慌!HDLbits的题目已经刷完了,但又知道自己还远远不够,就从一个B站UP主那里截取了一个刷题的题库,只有题目,代码都要自己去写,所以写的代码可能存在问题,我打算分成好几节把刷的题目代码放出来。仅供自己学习。如果有需要的也可以看看,若有错误,还望指出!谢谢!
一、同步FIFO
详细内容请参考我的另一篇博客FPGA(四)数字IC面试的四个基本问题第四章第二节。
二、ALU算术逻辑单元
参考:verilog实现算术逻辑单元ALU
Verilog组合设计:ALU算术逻辑单元
RTL代码
module alu (
input [11:0] alu_control,
input [31:0] alu_src1,
input [31:0] alu_src2,
output [31:0] alu_result
);
//control
wire op_add = alu_control[0];
wire op_sub = alu_control[1];
wire op_slt = alu_control[2];
wire op_sltu = alu_control[3];
wire op_and = alu_control[4];
wire op_nor = alu_control[5];
wire op_or = alu_control[6];
wire op_xor = alu_control[7];
wire op_sll = alu_control[8];
wire op_srl = alu_control[9];
wire op_sra = alu_control[10];
wire op_lui = alu_control[11];
//result
wire [31:0] add_sub_res;
wire [31:0] slt_res;
wire [31:0] sltu_res;
wire [31:0] and_res;
wire [31:0] nor_res;
wire [31:0] or_res;
wire [31:0] xor_res;
wire [31:0] sll_res;
wire [31:0] srl_res;
wire [31:0] sra_res;
wire [31:0] lui_res;
//logic
assign and_res = alu_src1 & alu_src2;
assign or_res = alu_src1 | alu_src2;
assign xor_res = alu_src1 ^ alu_src2;
assign nor_res = ~or_res;
assign lui_res = {alu_src2[15:0], 16'b0};
//adder
wire [31:0] adder1;
wire [31:0] adder2;
wire adder_cin;
wire [31:0] adder_res;
wire adder_cout;
assign adder1 = alu_src1;
assign adder2 = (op_sub | op_slt | op_sltu) ? ~alu_src2 : alu_src2;
assign adder_cin = (op_sub | op_slt | op_sltu) ? 1'b1 : 1'b0;
assign {adder_cout,adder_res} = adder1 + adder2 + adder_cin;
assign add_sun_res = adder_res;
//slt && sltu
assign slt_res [31:1] = 31'b0;
assign sltu_res[31:1] = 31'b0;
assign sltu_res[0] = ~adder_cout;
assign slt_res [0] = (alu_src1[31] & ~alu_src2[31])
| ( ~(alu_src1[31] ^ alu_src2[31]) & adder_res[31]) ;
//sll && srl && sra
assign sll_res = alu_src2 << alu_src1[4:0];
assign srl_res = alu_src2 >> alu_src1[4:0];
assign sra_res = ($signed(alu_src2)) >>> alu_src1[4:0];
//cout_result
assign alu_result = ({32{op_add | op_sub}} & add_sub_res)
| ({32{op_slt }} & slt_res)
| ({32{op_sltu }} & sltu_res)
| ({32{op_and }} & and_res)
| ({32{op_nor }} & nor_res)
| ({32{op_or }} & or_res)
| ({32{op_xor }} & xor_res)
| ({32{op_sll }} & sll_res)
| ({32{op_srl }} & srl_res)
| ({32{op_sra }} & sra_res)
| ({32{op_lui }} & lui_res);
endmodule
三、二进制转格雷码
详细解释请参考:Verilog实现二进制码与格雷码的转换
二进制 | 格雷码 |
---|---|
0000 | 0000 |
0001 | 0001 |
0010 | 0011 |
0011 | 0010 |
0100 | 0110 |
0101 | 0111 |
0110 | 0101 |
0111 | 0100 |
1000 | 1100 |
1001 | 1101 |
1010 | 1111 |
1011 | 1110 |
1100 | 1010 |
1101 | 1011 |
1110 | 1001 |
1111 | 1000 |
RTL代码:
module test #(
parameter WIDTH = 8
)(
input [WIDTH-1:0] Binary,
output[WIDTH-1:0] Gray
);
reg [WIDTH-1:0] BGray;
always @(*) begin
BGray = (Binary >> 1) ^ Binary;
end
assign Gray = BGray;
endmodule
四、格雷码转二进制
原理: 若二进制格雷码表示为:
G
[
N
−
1
]
G
[
N
−
2
]
…
G
[
2
]
G
[
1
]
G
[
0
]
;
G[N-1]G[N-2]…G[2]G[1]G[0];
G[N−1]G[N−2]…G[2]G[1]G[0];
相应地, 则二进制码表示为:
B
[
N
−
1
]
B
[
N
−
2
]
…
B
[
2
]
B
[
1
]
B
[
0
]
.
B[N-1]B[N-2]…B[2]B[1]B[0].
B[N−1]B[N−2]…B[2]B[1]B[0].
其中最高位保留:
B
[
N
−
1
]
=
G
[
N
−
1
]
B[N-1] = G[N-1]
B[N−1]=G[N−1];其他各位:
B
[
i
−
1
]
=
G
[
i
−
1
]
x
o
r
B
[
i
]
.
(
i
=
1
,
2
,
…
,
n
−
1
)
B[i-1] = G[i-1] xor B[i]. (i = 1, 2, …, n-1)
B[i−1]=G[i−1]xorB[i].(i=1,2,…,n−1)
module test #(
parameter WIDTH = 8
) (
input [WIDTH-1:0] Gray,
output [WIDTH-1:0] Bin_out
);
reg [WIDTH-1:0] Binary;
integer i;
always @ (*)begin
Binary[WIDTH-1] = Gray[WIDTH-1];
for(i = (WIDTH-2); i >= 0; i = i-1)
Binary[i] = Binary[i+1] ^ Gray[i];
end
assign Bin_out = Binary;
endmodule
五、二进制转BCD码-Double dabble
详细参考维基百科Double dabble
双涉猎算法用于将二进制数转换为二进制编码的十进制(BCD)表示法。它也被称为shift-and-add-3算法,可以使用计算机硬件中的少量门来实现,但以高延迟为代价。
算法操作如下:
- 假设要转换的原始数字存储在 n 位宽的寄存器中。保留一个足够宽的暂存空间,以容纳原始数字及其BCD表示形式; n + 4 × c e i l ( n / 3 ) n + 4×ceil(n/3) n+4×ceil(n/3)位就足够了。最多需要 4 位二进制文件来存储每个十进制数字。
- 然后将暂存空间划分为 BCD 数字(左侧)和原始寄存器(右侧)。例如,如果要转换的原始数字为八位宽,则暂存空间将按如下方式分区:
上图显示了原始寄存器中 24 3 10 243_{10} 24310 的二进制表示形式,左侧显示了 243 的 BCD 表示形式。
暂存空间初始化为全部零,然后要转换的值被复制到右侧的“原始寄存器”空间中。 - 然后,该算法迭代 n 次。在每次迭代中,任何至少为5(二进制为0101)的BCD数字都会递增3(0011);然后整个划痕空间左移一点。增量可确保值 5(递增和左移)变为 16 (10000),从而正确地“携带”到下一个 BCD 数字中。
从本质上讲,该算法的工作原理是每次迭代时将左侧的 BCD 值加倍,并根据原始位模式添加一个或零个。左移可同时完成这两项任务。如果任何数字为 5 或更高,则添加 3 以确保以 10 为基数的值“携带”。
对值
24
3
10
243_{10}
24310 执行的双涉猎算法如下所示:
RTL代码:
// parametric Verilog implementation of the double dabble binary to BCD converter
// for the complete project, see
// https://github.com/AmeerAbdelhadi/Binary-to-BCD-Converter
module bin2bcd
#( parameter W = 18) // input width
( input [W-1 :0] bin , // binary
output reg [(W+(W-4))/3:0] bcd ); // bcd {...,thousands,hundreds,tens,ones}
integer i,j;
always @(bin) begin
for(i = 0; i <= W+(W-4)/3; i = i+1)
bcd[i] = 0; // initialize with zeros
bcd[W-1:0] = bin; // initialize with input vector
for(i = 0; i <= W-4; i = i+1) // iterate on structure depth
for(j = 0; j <= i/3; j = j+1) // iterate on structure width
if (bcd[W-i+4*j -: 4] > 4) // if > 4
bcd[W-i+4*j -: 4] = bcd[W-i+4*j -: 4] + 4'd3; // add 3
end
endmodule
六、二进制转BCD码-基于Double dabble的有限状态机
可实现位宽可变。
第一来源为Convert Binary numbers to BCD in VHDL and Verilog
RTL代码:
///
// File Downloaded from http://www.nandland.com
///
module Binary_to_BCD
#(parameter INPUT_WIDTH,
parameter DECIMAL_DIGITS)
(
input i_Clock,
input [INPUT_WIDTH-1:0] i_Binary,
input i_Start,
//
output [DECIMAL_DIGITS*4-1:0] o_BCD,
output o_DV
);
parameter s_IDLE = 3'b000;
parameter s_SHIFT = 3'b001;
parameter s_CHECK_SHIFT_INDEX = 3'b010;
parameter s_ADD = 3'b011;
parameter s_CHECK_DIGIT_INDEX = 3'b100;
parameter s_BCD_DONE = 3'b101;
reg [2:0] r_SM_Main = s_IDLE;
// The vector that contains the output BCD
reg [DECIMAL_DIGITS*4-1:0] r_BCD = 0;
// The vector that contains the input binary value being shifted.
reg [INPUT_WIDTH-1:0] r_Binary = 0;
// Keeps track of which Decimal Digit we are indexing
reg [DECIMAL_DIGITS-1:0] r_Digit_Index = 0;
// Keeps track of which loop iteration we are on.
// Number of loops performed = INPUT_WIDTH
reg [7:0] r_Loop_Count = 0;
wire [3:0] w_BCD_Digit;
reg r_DV = 1'b0;
always @(posedge i_Clock)
begin
case (r_SM_Main)
// Stay in this state until i_Start comes along
s_IDLE :
begin
r_DV <= 1'b0;
if (i_Start == 1'b1)
begin
r_Binary <= i_Binary;
r_SM_Main <= s_SHIFT;
r_BCD <= 0;
end
else
r_SM_Main <= s_IDLE;
end
// Always shift the BCD Vector until we have shifted all bits through
// Shift the most significant bit of r_Binary into r_BCD lowest bit.
s_SHIFT :
begin
r_BCD <= r_BCD << 1;
r_BCD[0] <= r_Binary[INPUT_WIDTH-1];
r_Binary <= r_Binary << 1;
r_SM_Main <= s_CHECK_SHIFT_INDEX;
end
// Check if we are done with shifting in r_Binary vector
s_CHECK_SHIFT_INDEX :
begin
if (r_Loop_Count == INPUT_WIDTH-1)
begin
r_Loop_Count <= 0;
r_SM_Main <= s_BCD_DONE;
end
else
begin
r_Loop_Count <= r_Loop_Count + 1;
r_SM_Main <= s_ADD;
end
end
// Break down each BCD Digit individually. Check them one-by-one to
// see if they are greater than 4. If they are, increment by 3.
// Put the result back into r_BCD Vector.
s_ADD :
begin
if (w_BCD_Digit > 4)
begin
r_BCD[(r_Digit_Index*4)+:4] <= w_BCD_Digit + 3;
end
r_SM_Main <= s_CHECK_DIGIT_INDEX;
end
// Check if we are done incrementing all of the BCD Digits
s_CHECK_DIGIT_INDEX :
begin
if (r_Digit_Index == DECIMAL_DIGITS-1)
begin
r_Digit_Index <= 0;
r_SM_Main <= s_SHIFT;
end
else
begin
r_Digit_Index <= r_Digit_Index + 1;
r_SM_Main <= s_ADD;
end
end
s_BCD_DONE :
begin
r_DV <= 1'b1;
r_SM_Main <= s_IDLE;
end
default :
r_SM_Main <= s_IDLE;
endcase
end // always @ (posedge i_Clock)
assign w_BCD_Digit = r_BCD[r_Digit_Index*4 +: 4];
assign o_BCD = r_BCD;
assign o_DV = r_DV;
endmodule // Binary_to_BCD
仿真文件参考:FPGA Verilog实现二进制转BCD码
`timescale 1ns/1ps
module Binary_to_BCD_tb;
reg clk;
reg rst_n;
reg start;
reg [7:0] bin;
wire [11:0] dec;
wire done;
wire [3:0] dec_b = dec[11:8];
wire [3:0] dec_s = dec[7:4];
wire [3:0] dec_g = dec[3:0];
wire [7:0] dec_real = dec_b * 100 + dec_s * 10 + dec_g;
reg [7:0] idx;
always # (10/2) clk <= !clk;
initial begin
clk = 0;
rst_n = 0;
start = 0;
bin = 0;
idx = 0;
#50
@(posedge clk);
rst_n = 1;
#50
@(posedge clk);
for(idx = 0; idx < 254; idx = idx + 1)
trig_bcd_covert(idx);
#2000;
$stop();
$display("stop");
end
task trig_bcd_covert(
input [7:0] i_bin
);
begin
#80
@(posedge clk);
bin = i_bin;
start = 1;
@(posedge clk);
start = 0;
@(posedge done);
if(dec_real == i_bin)
$display("ok:%3d -> %1x%1x%1x", bin, dec_b, dec_s, dec_g);
else
$display("err:%3d -> %1x%1x%1x", bin, dec_b, dec_s, dec_g);
end
endtask
Binary_to_BCD #(
.INPUT_WIDTH(8),
.DECIMAL_DIGITS(3)
)Binary_to_BCD_ut0(
//Inputs
.i_Clock(clk),
.i_Rst_n(rst_n),
.i_Binary(bin[7:0]),
.i_Start(start),
//Outputs
.o_BCD(dec[11:0]),
.o_DV(done)
);
endmodule
另外,基于查找表的方法可以参考:FPGA Verilog实现二进制转BCD码2
七、自动售货机
直接参考这个教学文章:6.3 Verilog 状态机
八、秒计数器设计
秒计数器,就是根据时钟频率数数,时钟频率有多大就数多少个数。比如时钟频率为Fre Hz,那么数到(Fre-1)就是1s了。但因为数字电路中的时序逻辑原因,不能等数到(Fre-1)再去操作,而是应该在(Fre-2)的时候去“写”操作,这样在(Fre-1)时才能执行操作。但是,要数到(Fre-1)才能去清零,去数下一轮。
RTL代码:
module clock
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [5:0] cnt_s ,
output reg [5:0] cnt_m
);
reg [13:0] cnt_1000;
reg en_1;
/****************************************************
这里是以1kHz举例的,如果是更高的频率,数就会更大
****************************************************/
/*****************< 分频 >****************************/
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1000 <= 12'd0;
else if(cnt_1000 == 999)
cnt_1000 <= 12'd0;
else
cnt_1000 <= cnt_1000 +12'd1;
/*****************< 重要标志位 >****************************/
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
en_1 <= 1'b0;
else if (cnt_1000 == 998)
en_1 <= 1'b1;
else
en_1 <= 1'b0;
/*****************< 秒计数 >****************************/
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_s <= 6'd0;
else if ((cnt_s == 6'd59) && (en_1 == 1'b1))
cnt_s <= 6'd0;
else if(en_1 == 1'b1)
cnt_s <= cnt_s + 6'd1;
else
cnt_s <= cnt_s;
/*****************< 分计数 >****************************/
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt_m <= 1'b0;
else if ((cnt_s == 6'd59) && (en_1 == 1'b1))
cnt_m <= cnt_m + 1'b1;
else
cnt_m <= cnt_m;
endmodule
总结
代码为主!