1.VL45 异步FIFO
1.题目:
请根据题目中给出的双口RAM代码和接口描述,实现异步FIFO,要求FIFO位宽和深度参数化可配置。电路的接口如下图所示。
2.解题思路
2.1 格雷码 的4 位表格
2.2 格雷码的 得到的公式
2.3 没搞的太懂。
一个链接:FIFO设计-异步FIFO篇 - 知乎 (zhihu.com)
自己的理解:
a.首先是计数到 格雷码的转换。
b.然后是为什么要使用格雷码,为了异步时钟 要延时 2个周期, 因为 这里是 4 的地址存储。
3.解题代码
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
reg[$clog2(DEPTH):0] waddr, raddr;
reg[$clog2(DEPTH):0] gray_waddr, gray_raddr;
/***************二进制地址自增,格雷码转换*****************************************/
always@(posedge wclk or negedge wrstn)begin
if(~wrstn)begin
waddr <= 0;
gray_waddr <= 0;
end
else begin
waddr <= wenc? waddr+1: waddr;
gray_waddr <= (waddr>>1) ^ waddr;
end
end
always@(posedge rclk or negedge rrstn)begin
if(~rrstn) begin
raddr <= 0;
gray_raddr <= 0;
end
else begin
raddr <= renc? raddr+1: raddr;
gray_raddr <= (raddr>>1) ^ raddr;
end
end
/******************将格雷码地址打两拍同步时钟域*****************************************/
wire wenc, renc;
reg[$clog2(DEPTH):0] gray_waddr1, gray_waddr2;
always@(posedge rclk or negedge rrstn)begin
if(~rrstn)begin
gray_waddr1 <= 0;
gray_waddr2 <= 0;
end
else begin
gray_waddr1 <= gray_waddr;
gray_waddr2 <= gray_waddr1;
end
end
assign rempty = gray_raddr==gray_waddr2? 1: 0; //读空信号
assign renc = rinc && (!rempty); //读使能
reg[$clog2(DEPTH):0] gray_raddr1, gray_raddr2;
always@(posedge wclk or negedge wrstn)begin
if(~wrstn)begin
gray_raddr1 <= 0;
gray_raddr2 <= 0;
end
else begin
gray_raddr1 <= gray_raddr;
gray_raddr2 <= gray_raddr1;
end
end
assign wfull = gray_waddr==(gray_raddr2+DEPTH+(DEPTH>>1))? 1: 0; //写满信号
assign wenc = winc && (!wfull); //写使能
dual_port_RAM #(DEPTH, WIDTH) dut(
.wclk(wclk)
,.wenc(wenc)
,.waddr(waddr[$clog2(DEPTH)-1 : 0]) //深度对2取对数,得到地址的位宽。
,.wdata(wdata) //数据写入
,.rclk(rclk)
,.renc(renc)
,.raddr(raddr[$clog2(DEPTH)-1 : 0]) //深度对2取对数,得到地址的位宽。
,.rdata(rdata));
endmodule
2.VL46 同步FIFO
1.题目:
根据题目提供的双口RAM代码和接口描述,实现同步FIFO,要求FIFO位宽和深度参数化可配置。电路的接口如下图所示。
2.解题思路
2.1 这个比上一个好理解。主要是是在同步时钟周期里面,我们可以很好的比较数据。
2.2 再开计数器的时候注意 开大一点,不然不好比较 写满不好判断。
3.解题代码
`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
//同步FIFO 是可以直接根据同步时序来对比
//首先计数, 分别计数, 写计数, 读计数,
//创建一个寄存器
reg[$clog2(DEPTH):0] waddr; //写的地址寄存器
reg[$clog2(DEPTH):0] raddr; //读的地址寄存器
//防止数据覆盖我们需要 判读写的时候 是否 寄存器是满的。
wire wenc; //可以持续赋值
assign wenc = winc && !wfull; //使能的时候 寄存器是有位置的
//防止读的时候没有数据可以读取,我们需要判读 是否寄存器是空的。
wire renc; //可以持续赋值
assign renc = rinc && !rempty; //使能的时候,寄存器是有数据的。
always@(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
waddr <=0;
end
else
begin
if(wenc ==1'b1)
begin
waddr <= waddr +1;
end
end
end
always@(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
raddr <=0;
end
else
begin
if(renc == 1'b1)
begin
raddr <= raddr +1;
end
end
end
always@(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
wfull <=0;
rempty <=0;
end
else
begin
wfull <= (waddr == raddr + DEPTH )? 1:0;
rempty <= (waddr == raddr) ? 1 :0;
end
end
dual_port_RAM #(.DEPTH (DEPTH),
.WIDTH (WIDTH))
dual_port_RAM(
.wclk (clk),
.wenc (wenc),
.waddr (waddr),
.wdata (wdata),
.rclk (clk),
.renc (renc),
.raddr (raddr),
.rdata (rdata)
);
endmodule
3.VL 47 格雷码计数器
1.题目:
实现4bit位宽的格雷码计数器。
电路的接口如下图所示。
2. 解题思路
2.1 了解 格雷码的生成 和推算公式。
2.2 测试了一下,两个周期计数一下。 这个要注意。
3.解题代码
`timescale 1ns/1ns
module gray_counter(
input clk,
input rst_n,
output reg [3:0] gray_out
);
reg[4:0] cnt;
always@(posedge clk or negedge rst_n)
begin
if(~rst_n)
begin
cnt <=0;
end
else
begin
cnt <= cnt+1;
end
end
always@(posedge clk or negedge rst_n or cnt)
begin
if(~rst_n)
begin
gray_out = 4'd0;
end
else
begin
gray_out = cnt[4:1] ^ ((cnt[4:1]) >>1);
end
end
endmodule
4.VL48 多bit MUX同步器
1.题目:
在data_en为高期间,data_in将保持不变,data_en为高至少保持3个B时钟周期。表明,当data_en为高时,可将数据进行同步。
本题中data_in端数据变化频率很低,相邻两个数据间的变化,至少间隔10个B时钟周期。
电路的接口如下图所示。端口说明如下表所示。
2.解题思路
2.1多bit MUX同步器 是什么?
MUX/DMUX同步器主要是用于带有数据有效标志信号的多比特数据跨时钟域问题,且多比特数据要保持一段时间
//我不是很详细的知道 MUX 同步器的 意义,
//具体是: 有快慢的时钟 需要我们在不同的时钟 中转换数据。
//具体的 快慢时钟的差有多少,
//题目说 data_in 有效信号为 3 个 B 的时钟周期,
// 可以猜到 一般 data_en 为 1 的时候输出 data_in
// 所以我们的 clk_a => clk_b , 所以我们认为是 3 周期
// 但是 一般的数据输出是在 输入的下一个时钟周期。 所以数据需要延时
3.解题代码
`timescale 1ns/1ns
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
);
//我不是很详细的知道 MUX 同步器的 意义,
//具体是: 有快慢的时钟 需要我们在不同的时钟 中转换数据。
//具体的 快慢时钟的差有多少,
//题目说 data_in 有效信号为 3 个 B 的时钟周期,
// 可以猜到 一般 data_en 为 1 的时候输出 data_in
// 所以我们的 clk_a => clk_b , 所以我们认为是 3 周期
// 但是 一般的数据输出是在 输入的下一个时钟周期。 所以数据需要延时
//
reg[3:0] data_in_reg; //
reg data_en_a; //
reg data_en_b1; //
reg data_en_b2; //
always@(posedge clk_a or negedge arstn)
begin
if(!arstn)
begin
data_in_reg <= 4'b0;
data_en_a <=1'b0;
end
else
begin
data_in_reg <= data_in;//A时钟数据输入
data_en_a <= data_en;//A时钟数据有效信号,高电平有效。
end
end
always@(posedge clk_b or negedge brstn)
begin
if(!brstn)
begin
data_en_b1 <=1'b0;
data_en_b2 <=1'b0;
end
else
begin
data_en_b1 <= data_en_a;//推迟一个周期
data_en_b2 <= data_en_b1;//推迟一个周期
end
end
always@(posedge clk_b or negedge brstn)
begin
if(!brstn)
begin
dataout <= 4'b0;
end
else
begin
if(data_en_b2)//输入有效的延时3个周期的信号
begin
dataout <= data_in_reg;//数据输入
end
else
begin
dataout <= dataout;
end
end
end
endmodule
5.VL49 脉冲同步电路
1.题目
从A时钟域提取一个单时钟周期宽度脉冲,然后在新的时钟域B建立另一个单时钟宽度的脉冲。
A时钟域的频率是B时钟域的10倍;A时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题。
电路的接口如下图所示。data_in是脉冲输入信号,data_out是新的脉冲信号;clk_fast是A时钟域时钟信号,clk_slow是B时钟域时钟信号;rst_n是异步复位信号。
2.解题思路
2.1 脉冲同步器的基本原理:
将src_clk时钟域的输入脉冲转换为src_clk时钟域的电平信号src_state
;
对src_state电平信号进行打拍(打两拍)同步到dst_clk时钟域
对dst_clk时钟域的电平信号进行检测,产生dst_clk时钟域脉冲
;
2.2 信号到 脉冲的过程。
2.3 脉冲到信号的过程。
3.解题代码
`timescale 1ns/1ns
module pulse_detect(
input clk_fast ,
input clk_slow ,
input rst_n ,
input data_in ,
output dataout
);
reg data_in_reg;
reg [2:0] data_slow_reg;
always@(posedge clk_fast or negedge rst_n)
begin
if(!rst_n)
data_in_reg <= 1'b0;
else if(data_in)
data_in_reg <= ~data_in_reg; //只要信号来就跳转一次,即把其转换为电平信号,然后让slow时钟进行抓取
end
always@(posedge clk_slow or negedge rst_n)
begin
if(!rst_n)
data_slow_reg <= 3'b0;
else
data_slow_reg <= {data_slow_reg[1:0] , data_in_reg}; //这也是打两拍的方式,主要就是把打拍数据放一起,
end
//打三拍主要是怕第一个数据刚同步过来不稳定,所以选择三拍,特别是我们的输出是组合逻辑,所以必须三拍,两拍同步,一拍取稳定
assign dataout = data_slow_reg[2] ^ data_slow_reg[1]; //把第三拍数据和第二拍数据进行求异或就能得到信号的边沿,从而实现输出信号是一个脉冲
endmodule