继续牛客网的刷题,这一部分是跨时钟域传输,有下面的几道题:
FIFO的编写详见存储器篇:(2条消息) FPGA刷题——存储器(RAM和FIFO的Verilog实现)_居安士的博客-CSDN博客
目录
格雷码计数器
跨时钟多bit数据同步器
快慢时钟下的脉冲同步电路实现
握手信号法
边沿检测法
使用自然二进制码计数时,相邻数据之间可能会产生多bit的变化。这会产生较大的尖峰电流以及其他问题。格雷码是一种相邻数据只有1bit变化的码制。
转换公式如下:
最高位保留,gray[最高位]=bin[最高位]
低位由本位和高位异或得到, gray[i]=bin[i]^bin[i+1] 或者 gray=bin^(bin>>1)
module gray_counter(
input clk,
input rst_n,
output reg [3:0] gray_out
);
reg [3:0] bin_cnt;//二进制计数器
reg flag;//
always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
flag<=1'b0;
end
else begin
flag<=~flag;//根据波形图,计数器两个周期变化一次
end
end
always@(posedge clk or negedge rst_n)begin
if(~rst_n)begin
bin_cnt<=4'b0;
end
else begin
bin_cnt<=flag?bin_cnt+4'b1:bin_cnt;//产生二进制码
end
end
always@(*)begin
gray_out<=bin_cnt^(bin_cnt>>1);
end
endmodule
这个题要求我们把a时钟域的数据data_in传递到b时钟域的data_out,由a时钟域的使能信号控制
data_in
:数据输入;data_en
:输入数据有效;dataout
数据输出。
步骤如下:
(1)输入数据暂存在data_reg
中
(2)使能信号data_en
在A时钟域缓存,再打两拍的方式跨时钟域传输到时钟域B
(3)最后data_out
根据使能信号更新数据
module mux(
input clk_a ,
input clk_b ,
input arstn ,
input brstn ,
input [3:0] data_in ,
input data_en ,
output reg [3:0] dataout
);
reg [3:0] data_reg;
//a时钟域下数据缓存
always@(posedge clk_a or negedge arstn)begin
if(~arstn)begin
data_reg<=4'd0;
end
else begin
data_reg<=data_in;
end
end
//使能信号打拍
reg data_en_a, data_en_b0, data_en_b1;
always@(posedge clk_a or negedge arstn) begin//使能信号暂存
if(~arstn)
data_en_a <= 0;
else
data_en_a <= data_en;
end
always@(posedge clk_b or negedge brstn) begin//打2拍送到b时钟域
if(~brstn) begin
data_en_b0 <= 0;
data_en_b1 <= 0;
end
else begin
data_en_b0 <= data_en_a;
data_en_b1 <= data_en_b0;
end
end
//使能信号控制输出
always@(posedge clk_b or negedge brstn) begin
if(~brstn)
dataout <= 0;
else
dataout <= data_en_b1? data_reg: dataout;
end
endmodule
题目要求将高频率clk下的脉冲信号传递到低频率clk下,直接传输会导致低clk有可能采集不到脉冲信号,如下图:
`timescale 1ns/1ns
module pulse_detect(
input clk_fast ,
input clk_slow ,
input rst_n ,
input data_in ,
output dataout
);
reg fast_req, slow_ack;
reg [2:0] slow_req;
reg [2:0] fast_ack;
//fast时钟域
//将slow时钟域的应答信号打三拍,送到fast时钟域
always @(posedge clk_fast, negedge rst_n) begin
if(!rst_n) begin
fast_ack <= 3'b0;
end else begin
fast_ack <= {fast_ack[1:0], slow_ack};
end
end
//生成请求信号fast_req
always @(posedge clk_fast, negedge rst_n) begin
if(!rst_n) begin
fast_req <= 1'b0;
end else if (data_in) begin
fast_req <= 1'b1;
end else begin
fast_req <= (fast_ack[1] & (~fast_ack[2])) ? 1'b0 : fast_req;
end
end
//slow时钟域
//将fast时钟域的请求信号打三拍,送到slow时钟域
always @(posedge clk_slow, negedge rst_n) begin
if(!rst_n) begin
slow_req <= 3'b0;
end else begin
slow_req <= {slow_req[1:0], fast_req};
end
end
//生成应答信号slow_ack
always @(posedge clk_slow, negedge rst_n) begin
if(!rst_n) begin
slow_ack <= 1'b0;
end else if (slow_req[1] & (~slow_req[2])) begin
slow_ack <= 1'b1;
end else begin
slow_ack <= (slow_req[2] & (~slow_req[1])) ? 1'b0 : slow_ack;
end
end
assign dataout = (~slow_req[2]) & (slow_req[1]);
//
endmodule
边沿检测法适用于快频率clk下的脉冲信号相隔距离够远,否则低clk仍然可能会采集不到脉冲信号
步骤如下:
(1)在快clk下进行脉冲展宽:脉冲信号每高电平一次,脉冲展宽高低电平转换一次
(2)设置3bit寄存器缓存脉冲展宽值(理解为打三拍,取2、3拍的沿)(第一拍用于避免亚稳态)
(3)当脉冲展宽值不相同时说明脉冲信号输出
`timescale 1ns/1ns //二、基于边沿检测的方式
module pulse_detect(
input clk_fast ,
input clk_slow ,
input rst_n ,
input data_in ,
output dataout
);
reg req;
reg [2:0] req_r;
always @(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
req<=1'b0;
else if(data_in) //req检测到data_in拉高就翻转,保持住,相当于脉冲展宽。但有缺陷,如果data_in两次高电平变化太快,B来不及采样,就会漏采,产生错误。
req<=~req; //题目说A时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题,则不用考虑data_in变化太快。
end
always @(posedge clk_slow or negedge rst_n) begin
if(!rst_n)
req_r<=3'b0;
else
req_r<={req_r[1:0],req};
end
assign dataout=req_r[1] ^ req_r[2]; //用异或表示连续2次data_in变化都被采样到。
endmodule