自己整理的FPGA题准备面试
一:复位设计
为什么需要复位呢?
系统发生错误时,能够初始化后回到正常状态。
用同步复位还是异步复位和芯片有关
Altera:
同步复位:一个选择器和一个寄存器构成。
异步复位:只有一个寄存器构成。
Altera的最小逻辑单元LE中的寄存器结构决定
Altera器件中的基本单元就是由异步复位且低有效结构的寄存器组成的。
Xilinx芯片时规则,尽量避免使用异步复位,且如果使用复位就用高复位(同步高复位)
同步复位的D触发器和异步复位的D触发器的不同点是复位有效的条件是“立刻”执行还是等待“沿”再执行的区别。
异步复位有一个明显的问题就是会产生亚稳态的问题。
因为时钟沿变化和异步复位都可以引起Q端数据变化。如果异步复位信号跟时钟在一定时间间隔内发生变化(Removal time和Recovery time,类似于建立/保持时间),就是说只要复位信号不在时钟有效沿附近变化(复位信号远离时钟有效沿),就可以保证电路的正常复位和撤销,否则Q值将无法确定即产生亚稳态现象。同步复位就没有这种问题。
所以用异步复位同步释放机制。
只要存在复位都会增加布局布线的负担,因为复位会像时钟一样连接到每一个寄存器上,是相当复杂的工程,会增加时序收敛的难度,
1:异步复位,同步释放
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
rst_s1 <= 1'b0;
rst_s2 <= 1'b0;
end
else begin
rst_s1 <= 1'b1 ;
rst_s2 <= rst_s1 ;
end
end
为了避免亚稳态,让拉高的复位信号打两拍,达到与时钟clk边沿同步的目的
1.2:各种复位的优缺点
1.2.1同步复位
实际电路是把复位信号rst_n作为输入逻辑的使能信号,所以同步复位导致额外增加FPGA内部的资源消耗。
1.2.2异步复位
结束于breg0和creg0的{tsu+th:建立时间+保持时间}之外
复位信号rst_n的撤销(由低电平变为高电平)出现在建立时间和保持时间内,此时clk检测到rst_n的状态就会是一个亚稳态(不确定是0还是1)。
1.2.3异步复位,同步释放
推荐
二 跨时钟域
2.1 单bit信号和多bit信号的跨时钟域处理
慢到快采用打两拍
慢时钟域信号寄存器输出
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
else
src_state <= din;
end
同步至快时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
end
end
assign dout = src_state_d1;
快时钟域到慢时钟域,脉冲同步器
将原时钟域下的脉冲信号,转化为电平信号,再进行同步,同步完成之后再把新时钟域下的电平信号转化为脉冲信号
原时钟域下脉冲信号转变为电平信号
always @(posedge clk_1 or negedge rst_n)
begin
if(rst_n == 1'b0)
src_state <= 1'b0;
else if(din == 1'b1) //MUX完成翻转,脉冲到来,从脉冲到电平的转换
src_state <= ~src_state;
else
src_state <= din ^ src_state; //异或
end
同步至新时钟域
always @(posedge clk_2 or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
src_state_d0 <= 1'b0;
src_state_d1 <= 1'b0;
src_state_d2 <= 1'b0;
end
else
begin
src_state_d0 <= src_state;
src_state_d1 <= src_state_d0;
src_state_d2 <= src_state_d1;
end
end
电平转换成脉冲(边沿检测产生新的脉冲)
assign dout = src_state_d1 ^ src_state_d2;
**输入脉冲的最小间隔必须等于两个新时钟的时钟周期。**否则会展宽
快时钟域到慢时钟域,先展宽再同步再变脉冲
/在clka下生成展宽信号singal_a/
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n)
singal_a <= 1'b0;
else if (pulse_ina)
singal_a <= 1'b1;
else if (singal_a_r1)
singal_a <= 1'b0;
else
singal_a <= singal_a;
end
/在clkb下同步singal_a/
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n)
singal_b <= 1'b0;
else
singal_b <= singal_a ;
end
/在clkb下生成脉冲信号和输出信号/
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n)
singal_b_r0 <= 1'b0;
singal_b_r1 <= 1'b0;
else
singal_b_r0 <= singal_b;
singal_b_r1 <= singal_b_r0 ;
end
assign pulse_outb = ~singal_b_r1 & singal_b_r0 ;
assign singal_outb = singal_b_r1 ;
/在clka下同步singal_b_r1;生成singal_a_r1,用于反馈拉低singal_a /
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n)
singal_a_r0 <= 1'b0;
singal_a_r1 <= 1'b0;
else
singal_a_r0 <= singal_b_r1 ;
singal_a_r1 <= singal_a_r0 ;
end
握手反馈机制
原理:双方电路在声明或终止各自的握手信号信号前都要等待对方的相应。
1:请求信号的产生:
2:请求信号的跨越与应答信号的产生:
3:应答信号的跨越与请求信号的清除:
4:应答信号的清除:
同步器空闲状态的判断:原时钟下:请求和应答信号同时无效
assign sync_idle = ~(src_sync_req | src_sync_ack );
同步失败的判断
always @(posedge clk_A or negedge rst_n_A)
begin
if(rst_n_A == 1'b0)
sync_fail <= 1'b0;
else if(din & (~sync_idle)) //源时钟脉冲到来,此时同步器不空闲,给出同步失败
sync_fail <= 1'b1;
else
sync_fail <= 1'b0;
end
原时钟产生请求信号,请求信号的产生相当于将脉冲转化为了电平
always @(posedge clk_A or negedge rst_n_A)
begin
if(rst_n_A == 1'b0)
src_sync_req <= 1'b0;
else if(din & sync_idle) //源时钟脉冲到来,且源时钟空闲,传递请求。同时完成了脉冲转电平
src_sync_req <= 1'b1;
else if(src_sync_ack) //检测到应答以后,清除请求
src_sync_req <= 1'b0;
end
同步原时钟请求信号到目的时钟,利用请求信号跨时钟域
always @(posedge clk_B or negedge rst_n_B)
begin
if(rst_n_B == 1'b0)
begin
req_state_dly1 <= 1'b0;
req_state_dly2 <= 1'b0;
req_state_dly3 <= 1'b0;
end
else
begin
req_state_dly1 <= src_sync_req;
req_state_dly2 <= req_state_dly1; //打两拍结束
req_state_dly3 <= req_state_dly2; //再外接一个寄存器,以保证脉冲输出
end
end
上升沿检测,产生输出脉冲
assign dout = (~req_state_dly3) & req_state_dly2; //完成输出脉冲
目的时钟产生应答信号
always @(posedge clk_B or negedge rst_n_B)
begin
if(rst_n_B == 1'b0)
dst_sync_ack <= 1'b0;
else if (req_state_dly2) //同步高电平已到达
dst_sync_ack <= 1'b1;
else
dst_sync_ack <= 1'b0;
end
同步目的时钟产生的应答信号到原时钟
always @(posedge clk_A or negedge rst_n_A)
begin
if(rst_n_A == 1'b0)
begin
ack_state_dly1 <= 1'b0;
ack_state_dly2 <= 1'b0;
end
else
begin
ack_state_dly1 <= dst_sync_ack;
ack_state_dly2 <= ack_state_dly1;
end
end
assign src_sync_ack = ack_state_dly2;
多bit跨时钟域
异步fifo
三:FIFO
fifo深度计算
fifo深度/(写入速率 - 读出速率) > 写入数据/写入速率
写时钟频率wclk 读时钟频率rclk
写时钟每B个时钟周期会有A个数据写入FIFO
读时钟每Y个时钟周期会有X个数据读出FIFO
depth = burst_length - (burst_length /wclk)(rclkX/Y)
burst_length = 2A
同步FIFO的Verilog实现
输入数据位宽16位,深度256
module(
input clk,
input rst_n,
input wr_en,
input [15:0] data_in,
input rd_en,
output [15:0] data_out,
output empty,
output full
);
reg [7:0] wr_cnt;
reg [7:0] rd_cnt
reg [7:0] status_cnt ;
reg [15:0] fifo_memory [7:0];
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
wr_cnt <= 8'd0;
else if (wr_en)
wr_cnt <= wr_cnt + 1'b1;
else
wr_cnt <= wr_cnt ;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
rd_cnt <= 8'd0;
else if (rd_en)
rd_cnt <= rd_cnt+ 1'b1;
else
rd_cnt <= rd_cnt;
end
interger i ;
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
for(i=0; i<=255; i=i+1)
fifo_memory [i] <= 16'd0;
end
else if(wr_en)
begin
fifo_memory [wr_cnt] <= data_in;
end
else
fifo_memory <= fifo_memory ;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
data_out <= 16'd0;
else if (rd_en)
data_out <= fifo_depth[rd_cnt];
else
data_out <= data_out ;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
status_cnt <= 8'd0;
else if (wr_en && (!rd_en) && (!full ))
status_cnt <= status_cnt + 1'b1;
else if (rd_en && (!wr_en) && (!empty ))
status_cnt <= status_cnt - 1'b1;
else
status_cnt <= status_cnt ;
end
asssign full =(status_cnt == 8'd255 )?1'b1:1'b0;
asssign empty =(status_cnt == 8'd0 )?1'b1:1'b0;
endmodule
异步FIFO的Verilog实现
数据位宽16位,深度为256
复位,读写指针相等,为空
写指针多写一圈,追上读指针,读写指针再次相等,则FIFO为满
读指针追上写指针,读写指针再次相等,则FIFO为空
指针多增加一位,最高位为循环圈数,读写指针完全相等,则FIFO为空,除了最高位外,读写指针其他完全相等,则FIFO位满
module(
input clk_1,
input clk_2;
input rst_n,
input wr_en,
input [15:0] data_in,
input rd_en,
output [15:0] data_out,
output full,
output empty
);
reg [8:0] wr_cnt_ptr; //写地址指针多一位,表示循环次数
reg [7:0] wr_cnt;
reg [8:0] rd_cnt_ptr; //读地址指针多一位,表示循环次数
reg [7:0] rd_cnt;
assign wr_cnt = wr_cnt_ptr[7:0];
assign rd_cnt = rd_cnt_ptr[7:0];
always @ (posedge clk_1 or negedge rst_n) //写指针加
begin
if (!rst_n)
wr_cnt_ptr <= 8'd0;
else if (wr_en)
wr_cnt_ptr <= wr_cnt_ptr+ 1'b1;
else
wr_cnt_ptr <= wr_cnt_ptr;
end
always @ (posedge clk_2 or negedge rst_n) //读指针加
begin
if (!rst_n)
rd_cnt_ptr <= 8'd0;
else if (rd_en)
rd_cnt_ptr <= rd_cnt_ptr+ 1'b1;
else
rd_cnt_ptr <= rd_cnt_ptr;
end
reg [15:0] fifo_memory [255:0]; //写入
interger i;
always @ (posedge clk_1 or negedge rst_n)
begin
if (!rst_n)
for(i=0;i<=255;i=i+1)
fifo_memory[i] <= 16'd0;
else if ((wr_en) && (~full))
fifo_memory[wr_cnt] <= data_in;
else
fifo_memory[wr_cnt] <= fifo_memory[wr_cnt];
end
always @ (posedge clk_2 or negedge rst_n) //读出
begin
if (!rst_n)
data_out<= 16'd0;
else if ((rd_en) && (~empty))
data_out<= fifo_memory[rd_cnt];
else
data_out<= data_out;
end
//格雷码转换
reg [8:0] gray_wr_ptr;
reg [8:0] gray_rd_ptr;
assign gray_wr_ptr = (wr_cnt_ptr>>1) ^(wr_cnt_ptr);
assign gray_rd_ptr = (rd_cnt_ptr >>1) ^(rd_cnt_ptr );
reg [8:0] gray_wr_ptr_d1;
reg [8:0] gray_wr_ptr_d2;
reg [8:0] gray_rd_ptr_d1;
reg [8:0] gray_rd_ptr_d2;
//写地址指针同步到读
//读地址指针同步到写
always @ (posedge clk_2 or negedge rst_n)
begin
if (!rst_n)
begin
gray_wr_ptr_d1 <= 8'd0;
gray_wr_ptr_d2 <= 8'd0;
end
else
begin
gray_wr_ptr_d1 <= gray_wr_ptr;
gray_wr_ptr_d2 <= gray_wr_ptr_d1;
end
end
always @ (posedge clk_1 or negedge rst_n)
begin
if (!rst_n)
begin
gray_rd_ptr_d1 <= 8'd0;
gray_rd_ptr_d2 <= 8'd0;
end
else
begin
gray_rd_ptr_d1 <= gray_rd_ptr;
gray_rd_ptr_d2 <= gray_rd_ptr_d1;
end
end
//写侧满,读侧空
assign full = (gray_wr_ptr =={~(gray_rd_ptr_d2[15:13]),gray_rd_ptr_d2[13:0]} )?1'b1:1'b0;
assign empty = (gray_rd_ptr == gray_wr_ptr_d2)?1'b1:1'b0;
{~gray_wr_ptr_d2[depth-1-:2];gray_wr_ptr_d2[depth-3:0]}
endmodule