前言
这不马上要面试了嘛,有些慌!HDLbits的题目已经刷完了,但又知道自己还远远不够,就从一个B站UP主那里截取了一个刷题的题库,只有题目,代码都要自己去写,所以写的代码可能存在问题,我打算分成好几节把刷的题目代码放出来。仅供自己学习。如果有需要的也可以看看,若有错误,还望指出!谢谢!
一、异步FIFO
关于这部分内容,我在上一篇博客FPGA(四)数字IC面试的四个基本问题的第四章第二部分详细讲过,也是有代码的,采用的格雷码的方法。
二、异步复位同步释放
详细解释可参考异步复位同步释放原理
简单解释就是:
异步复位,同步释放就是指在复位信号到来的时候不受时钟信号的同步,而是在复位信号释放的时候受到时钟信号的同步。
RTL代码如下:
module Sys_Rst(
input clk ,
input rst_n ,
output sys_rst
);
reg rst_r0;
reg rst_r1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rst_r0 <= 1'b0;
rst_r1 <= 1'b0;
end
else begin
rst_r0 <= 1'b1;
rst_r1 <= rst_r0;
end
end
assign sys_rst = rst_r1;
endmodule
三、两级触发器同步
两级触发器一般就是我们常说的打两拍,可以有效降低亚稳态产生的风险,如下所示:
RTL代码:
module abit_Pulse(
input clk , //输入时钟
input rst_n , //复位信号
input asynch_in , //输入信号
output synch_out
);
reg [1:0] signal_r;
//在快时钟域下,将传递来的信号打两拍
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
signal_r <= 2'b00;
else
signal_r <= {signal_r[0],asynch_in};
end
assign synch_out = signal_r[1];
endmodule
四、握手CDC设计
FPGA逻辑设计回顾(7)多比特信号的CDC处理方式之握手同步
一种基于握手机制的跨时钟域方法及其Verilog实现
握手指的是两个设备之间通信的一种方式,用来通信的信号就是握手信号。最简单的握手信号是 valid 和 ready,也可以叫 request 和 grant。假设设备1向设备2发送数据,设备1不知道设备2什么时候可以接收数据,设备2也不知道设备1什么时候会发送数据,那么它们之间如果用握手通信可能是这样的顺序:
- 设备1将 valid 信号置1,告诉设备2,数据准备就绪了,请查收
- 设备2此刻正处于忙碌状态无法接收数据,设备2将 ready 信号保持为0
- 设备2空闲了,将 ready 信号置1接收设备1的数据
- 设备1看到设备2的 ready 为1,它知道设备2已经接收好数据了,将 valid 置0同时撤销数据,准备下一次发送
- 可以看到因为有握手控制,可以确保数据的正确传输,不会丢失。跨时钟域的握手设计就是利用握手控制这种优势,从而避免因为跨时钟域引起的数据传输错误
RTL代码:
/*************************************************************************************
输入:
·clk_a, clk_b 源时钟与目的市长
·rst 复位信号
·a_en,data_a 可以当成一种协议,检测到a_en有效(下降沿)的时候,输入数据就更新,
更新数据一直持续到a_en的下一个下降沿
输出:
·b_en 对a_en的同步
·data_b_out 对b的同步
·ack_a 是b时钟域的响应信号ack,同步到a时钟域后的信号,通知a时钟域(下降沿通知)
可以发送下一个数据了。事实上,a_en就是该信号的下降沿
*************************************************************************************/
module syn_handshake(
input wire clk_a , //源时钟
input wire clk_b , //目的时钟
input wire rst ,
input wire a_en , //来自于外部的使能信号,脉冲持续一个时钟周期
input wire [3:0] data_a_in , //外部输入信号
output reg [3:0] data_b_out ,
output wire b_en ,
output wire ack_a
);
//生成a_en的下降沿 只用延迟一个周期即可
reg a_en_d1 ;
//reg a_en_d2 ;
reg a_en_neg ;
always@(posedge clk_a or posedge rst) begin
if(rst) begin
a_en_d1 <= 1'd0;
// a_en_d2 <= 1'd0;
a_en_neg <= 1'd0;
end
else begin
a_en_d1 <= a_en;
// a_en_d2 <= a_en_d1;
// a_en_neg <= ~a_en_d1 && a_en_d2;
a_en_neg <= ~a_en && a_en_d1;
end
end
//生成请求信号
reg a_req ;
wire a2_q_pos;
always@(posedge clk_a or posedge rst) begin
if(rst)
a_req <= 1'd0;
else if(a_en_neg)
a_req <= 1'd1;
else if(a2_q_pos) //a_req 拉低条件
a_req <= 1'd0;
else
a_req <= a_req;
end
//请求信号a_req跨时钟域处理
reg b1_q, b2_q;
always@(posedge clk_b or posedge rst) begin
if(rst) begin
b1_q <= 1'd0;
b2_q <= 1'd0;
end
else begin
b1_q <= a_req;
b2_q <= b1_q;
end
end
//生成时钟域b内的数据使能信号
//b2_q信号的上升沿
//b时钟域对a时钟域的响应信号
reg b2_q_d1;
reg b2_q_d2;
reg b2_q_d3;
reg ack_b;
always@(posedge clk_b or posedge rst) begin
if(rst) begin
b2_q_d1 <= 1'd0;
b2_q_d2 <= 1'd0;
b2_q_d3 <= 1'd0;
ack_b <= 1'd0;
end
else begin
b2_q_d1 <= b2_q;
b2_q_d2 <= b2_q_d1;
b2_q_d3 <= b2_q_d2;
ack_b <= b2_q_d2;
end
end
assign b_en = ~b2_q_d3 && b2_q_d2;
//b_en有效,则表示数据有效,可以采样a时钟域的数据了
always@(posedge clk_b or posedge rst) begin
if(rst)
data_b_out <= 4'd0;
else if(b_en)
data_b_out <= data_a_in;
end
//响应信号同步到a时钟域
//a2_q上升沿作为a_req拉低的条件
reg a1_q, a2_q;
reg a3_q ;
always@(posedge clk_a or posedge rst) begin
if(rst) begin
a1_q <= 1'd0;
a2_q <= 1'd0;
a3_q <= 1'd0;
end
else begin
a1_q <= ack_b;
a2_q <= a1_q;
a3_q <= a2_q;
end
end
assign a2_q_pos = ~a3_q && a2_q;
assign ack_a = a2_q; //此信号作为a_en的反馈信号,a_en取此信号的下降沿
endmodule
仿真文件
`timescale 1ns / 1ps
module tb_syn_handshake;
reg clk_a ;
reg clk_b ;
reg rst ;
reg a_en ; //来自于外部的使能信号,脉冲持续一个时钟周期
reg [3:0] data_a_in ; //外部输入信号
wire [3:0] data_b_out ;
wire b_en ;
wire ack_a ;
initial begin
clk_a = 1'd0;
forever begin
#2 clk_a = ~clk_a;
end
end
initial begin
clk_b = 1'd0;
forever begin
#3 clk_b = ~clk_b;
end
end
initial begin
rst = 1'd1;
#15
@(negedge clk_a);
rst = 1'd0;
end
reg ack_a_d1;
always@(posedge clk_a or posedge rst) begin
if(rst) begin
a_en <= 1'd1;
ack_a_d1 <= 1'd0;
end
else begin
ack_a_d1 <= ack_a;
a_en <= ~ack_a && ack_a_d1;
end
end
always@(posedge clk_a or posedge rst) begin
if(rst) begin
data_a_in <= 4'd0;
end
else if(a_en) begin
data_a_in <= $random;
end
end
syn_handshake u_syn_handshake(
.clk_a ( clk_a ),
.clk_b ( clk_b ),
.rst ( rst ),
.a_en ( a_en ),
.data_a_in ( data_a_in ),
.data_b_out ( data_b_out ),
.b_en ( b_en ),
.ack_a ( ack_a )
);
endmodule
仿真结果
五、异步双口RAM
在FPGA设计中,经常会用到RAM,这里的RAM一般指的是静态的RAM(SRAM)。一般FPGA中就有所谓的block RAM, 它就是现成的RAM资源。通常情况下,我们使用IP核,进行设计,我们来设计自己的IP核。
RAM分为单端、伪双端、真双端三种模式。
- 单端RAM读写共享一个地址
- 伪双端RAM一个写端口,一个读端口,各自的地址和使能信号,写端口只负责写,读端口只负责读,所以称为伪双端
- 真双口RAM可以在任意时间访问任意地址,两个端口的地址是一样的,即共享内存和地址。这就会带来一个问题:同时读写一个地址会发生冲突。基于这个点矛盾就要设置限制条件
接下来,用Verilog实现一个异步双端口RAM,深度128,位宽16bit。A口写入,B口读出。支持片选,读写请求,要求代码可综合。
RTL代码
/*************************************************************************
实现一个异步双端口RAM,深度128,位宽16bit。A口写入,B口读出。支持片选,读写请求,要求代码可综合
*************************************************************************/
module Dual_Port_Sram #(
parameter ADDR_WIDTH = 7, //地址位宽
parameter DATA_WIDTH = 16, //数据位宽
parameter DATA_DEPTH = 1 << ADDR_WIDTH //数据深度 = 2^ADDR_WIDTH
) (
input clka ,
input clkb ,
input rst_n ,
input csen_n , //片选信号,低有效
/*************Port A Signal Write********************************/
input [ADDR_WIDTH-1:0] addra ,
input [DATA_WIDTH-1:0] data_a ,
input wrena_n ,
/*************Port B Signal Read********************************/
input [ADDR_WIDTH-1:0] addrb ,
input rdenb_n ,
output [DATA_WIDTH-1:0] data_b
);
/************* A口写入逻辑 *********************************/
integer i;
reg [DATA_WIDTH-1:0] register[DATA_DEPTH-1:0]; //存储器定义
always @(posedge clka or negedge rst_n) begin
if(!rst_n) begin
for(i = 0; i < DATA_DEPTH; i = i + 1)
register[i] <= 0;
end
else if(!wrena_n && !csen_n)
register[addra] <= data_a;
end
/************* B口读出逻辑 *********************************/
reg [DATA_WIDTH-1:0] rddata_b;
always @(posedge clkb or negedge rst_n) begin
if(!rst_n)
rddata_b <= 1'b0;
else if(!rdenb_n && !csen_n)
rddata_b <= register[addrb];
else
rddata_b <= rddata_b;
end
assign data_b = rddata_b;
endmodule
仿真文件
`timescale 1ns/1ps
module tb_Dual_Port_Sram;
parameter ADDR_WIDTH = 7; //地址位宽
parameter DATA_WIDTH = 16; //数据位宽
parameter DATA_DEPTH = 1 << ADDR_WIDTH; //数据深度 = 2^ADDR_WIDTH
reg clka ;
reg clkb ;
reg rst_n ;
reg csen_n ;
reg [ADDR_WIDTH-1:0] addra ;
reg [DATA_WIDTH-1:0] data_a ;
reg wrena_n ;
reg [ADDR_WIDTH-1:0] addrb ;
reg rdenb_n ;
wire [DATA_WIDTH-1:0] data_b ;
Dual_Port_Sram #(
.ADDR_WIDTH (ADDR_WIDTH),
.DATA_WIDTH (DATA_WIDTH),
.DATA_DEPTH (DATA_DEPTH)
)u_Dual_Port_Sram(
.clka (clka ),
.clkb (clkb ),
.rst_n (rst_n ),
.csen_n (csen_n ),
.addra (addra ),
.data_a (data_a ),
.wrena_n (wrena_n),
.addrb (addrb ),
.rdenb_n (rdenb_n),
.data_b (data_b )
);
initial begin
clka = 1'd0;
forever begin
#2 clka = ~clka;
end
end
initial begin
clkb = 1'd0;
forever begin
#3 clkb = ~clkb;
end
end
initial begin
rst_n = 1'b0;
csen_n = 1'b1;
wrena_n = 1'b1;
addra = 1'b0;
data_a = 1'b0;
rdenb_n = 1'b1;
addrb = 1'b0;
#50
rst_n = 1'b1;
csen_n = 1'b0;
#10
wrena_n = 1'b0;
repeat (DATA_DEPTH-1) begin
@(posedge clka) begin
addra = addra + 1;
data_a = data_a + 1;
end
end
#35
wrena_n = 1'b1;
rdenb_n = 1'b0;
repeat (DATA_DEPTH-1) begin
@(posedge clkb) begin
addrb = addrb + 1;
end
end
#50
rdenb_n = 1'b1;
csen_n = 1'b1;
#100 $stop;
end
endmodule
仿真结果
总结
这是第一部分,可能也是面试中比较难的一部分,但应该也是常考的一部分。要仔细研究代码,电路图和时序图都要能够做到胸有成竹。
因为有一些东西是参考网上的,如果存在侵权,请告知,然后我删除或标注出来。