【牛客】6 跨时钟域传输

VL45 异步FIFO

很经典的手撕题,这道题要求产生的格雷码要在本时钟域中打一拍,其实不打也没关系。

 

 

 主要要记住

1、bin2gray的方法:右移一位与移位前异或;

2、格雷码比较方法:空:读指针格雷码和写指针同步过来的格雷码相同;满:写指针格雷码高两位与读指针同步过来的格雷码正好相反,低位相同。

`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
);
parameter ADDR_WIDTH = $clog2(DEPTH);
reg [ADDR_WIDTH:0]waddr,raddr;//多一位用于比较空满
always@(posedge wclk or negedge wrstn)
begin
    if(~wrstn)
        waddr <= 'd0;
    else if(winc&&~wfull)
        waddr <= waddr + 1;
end
always@(posedge rclk or negedge rrstn)
begin
    if(~rrstn)
        raddr <= 'd0;
    else if(rinc&&~rempty)
        raddr <= raddr + 1;
end
reg [ADDR_WIDTH:0]waddr_gray,raddr_gray;//格雷码
reg [ADDR_WIDTH:0]waddr_gray_sync1,raddr_gray_sync1;//打一拍后
reg [ADDR_WIDTH:0]waddr_gray_sync2,raddr_gray_sync2;//打两拍后
/*bin2gray*/
// always@(*)
// begin
//     waddr_gray = (waddr>>1)^waddr;
//     raddr_gray = (raddr>>1)^raddr;
// end
always@(posedge wclk or negedge wrstn)
begin
    if(~wrstn)
        waddr_gray <= 'd0;
    else
        waddr_gray <= (waddr>>1)^waddr;
end
always@(posedge rclk or negedge rrstn)
begin
    if(~rrstn)
        raddr_gray <= 'd0;
    else
        raddr_gray <= (raddr>>1)^raddr;
end
/*把格雷码打两拍传输*/
always@(posedge wclk or negedge wrstn)
begin
    if(~wrstn)begin
        raddr_gray_sync1 <= 'd0;
        raddr_gray_sync2 <= 'd0;
    end else begin
        raddr_gray_sync1 <= raddr_gray;
        raddr_gray_sync2 <= raddr_gray_sync1;        
    end
end
always@(posedge rclk or negedge rrstn)
begin
    if(~rrstn)begin
        waddr_gray_sync1 <= 'd0;
        waddr_gray_sync2 <= 'd0;
    end else begin
        waddr_gray_sync1 <= waddr_gray;
        waddr_gray_sync2 <= waddr_gray_sync1;        
    end
end
/*空满判断*/
assign wfull = (waddr_gray[ADDR_WIDTH:ADDR_WIDTH-1]==~raddr_gray_sync2[ADDR_WIDTH:ADDR_WIDTH-1])&&(waddr_gray[ADDR_WIDTH-2:0]==raddr_gray_sync2[ADDR_WIDTH-2:0]);

assign rempty = (raddr_gray==waddr_gray_sync2);

dual_port_RAM #(.WIDTH(WIDTH),.DEPTH(DEPTH))
U0(
    .wclk(wclk),
    .wenc(winc&&~wfull),
    .waddr(waddr[ADDR_WIDTH-1:0]),
    .wdata(wdata),
    .rclk(rclk),
    .renc(rinc&&~rempty),
    .raddr(raddr[ADDR_WIDTH-1:0]),
    .rdata(rdata) ); 
endmodule

 VL46 同步FIFO

比异步FIFO简单多了。不过感觉有问题,满之后过一个周期才会输出满。

`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
);
parameter ADDR_WIDTH = $clog2(DEPTH);
reg [ADDR_WIDTH:0]waddr,raddr;//多一位用于比较空满
always@(posedge clk or negedge rst_n)
begin
    if(~rst_n)
        waddr <= 'd0;
    else if(winc&&~wfull)
        waddr <= waddr + 1;
end
always@(posedge clk or negedge rst_n)
begin
    if(~rst_n)
        raddr <= 'd0;
    else if(rinc&&~rempty)
        raddr <= raddr + 1;
end
/*空满判断*/
always@(posedge clk or negedge rst_n)
begin
    if(~rst_n)begin
        wfull <= 'd0;
        rempty <= 'd0;
    end else begin
        wfull <= (waddr[ADDR_WIDTH]==~raddr[ADDR_WIDTH])&&(waddr[ADDR_WIDTH-1:0]==raddr[ADDR_WIDTH-1:0]);
        rempty <= (raddr==waddr);
    end
end

dual_port_RAM #(.WIDTH(WIDTH),.DEPTH(DEPTH))
U0(
    .wclk(clk),
    .wenc(winc&&~wfull),
    .waddr(waddr[ADDR_WIDTH-1:0]),
    .wdata(wdata),
    .rclk(clk),
    .renc(rinc&&~rempty),
    .raddr(raddr[ADDR_WIDTH-1:0]),
    .rdata(rdata) ); 

endmodule

参考之前看《硬件架构的艺术》写的代码【《硬件架构的艺术》读书笔记】03 处理多个时钟(2) - Magnolia666 - 博客园 (cnblogs.com)

可以提前一个周期输出空满,更合理。

VL47 格雷码计数器

垃圾题目,每两个周期加一次,看了一下题解,感觉确实用状态机更有道理,我这样写本质还是一个二进制计数器。

`timescale 1ns/1ns

module gray_counter(
   input   clk,
   input   rst_n,

   output  reg [3:0] gray_out
);
reg [3:0]bin;
reg flag;
always@(posedge clk or negedge rst_n)
begin
    if(~rst_n)
        flag <= 0;
    else
        flag <= ~flag;
end
always@(posedge clk or negedge rst_n)
begin
    if(~rst_n)
        bin <= 0;
    else
        bin <= flag?bin + 1:bin;
end
always@(*)
begin
    gray_out = (bin>>1)^bin;
end
endmodule

VL48 多bit MUX同步器

题目大概意思是通过MUX把四位数据从时钟域a同步到时钟域b。感觉怪怪的。。。。

看了一下要把数据和使能在时钟域a打一拍,再把使能信号在时钟域b打两拍控制MUX选通data信号。

`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
);
reg [3:0]data_in_rega;
reg data_en_rega;
always@(posedge clk_a or negedge arstn)
begin
    if(~arstn)begin
        data_en_rega <= 0;
        data_in_rega <= 0;
    end else begin
        data_en_rega <= data_en;
        data_in_rega <= data_in;        
    end
end

reg data_en_regb1;
reg data_en_regb2;
always@(posedge clk_b or negedge brstn)
begin
    if(~brstn)begin
        data_en_regb1 <= 0;
        data_en_regb2 <= 0;
    end else begin
        data_en_regb1 <= data_en_rega;
        data_en_regb2 <= data_en_regb1;
    end
end
always@(posedge clk_b or negedge brstn)
begin
    if(~brstn)begin
        dataout <= 0;
    end else begin
        dataout <= data_en_regb2?data_in_rega:dataout;
    end
end
endmodule

VL49 脉冲同步电路

邸志雄的课里看到过,参考芯动力——硬件加速设计方法_西南交通大学_中国大学MOOC(慕课) (icourse163.org),不过要注意这里快慢写反了。

 

 

`timescale 1ns/1ns

module pulse_detect(
    input                 clk_fast    , 
    input                 clk_slow    ,   
    input                 rst_n        ,
    input                data_in        ,

    output               dataout
);
reg toggle;
always@(posedge clk_fast or negedge rst_n)
begin
    if(~rst_n)
        toggle <= 1'b0;
    else
        toggle <= data_in?(~toggle):toggle;
end
reg data_syn0,data_syn1,data_syn2;
always@(posedge clk_slow or negedge rst_n)
begin
    if(~rst_n)begin
        data_syn0 <= 1'b0;
        data_syn1 <= 1'b0;
        data_syn2 <= 1'b0;
    end else begin
        data_syn0 <= toggle;
        data_syn1 <= data_syn0;
        data_syn2 <= data_syn1;
    end
end
assign dataout = data_syn2^data_syn1;
endmodule

 

posted @ 2023-03-05 20:30  Magnolia666  阅读(188)  评论(0编辑  收藏  举报