FPGA学习笔记——Double Buffer

FPGA学习笔记——Double Buffer

本文实例引自网络视频教程

————实例完成的功能是将100M时钟域输入的data_in[7:0]通过输入二路数据选择器分别写入两个RAM(8x1024_r16x512)中,在75M的时钟域,通过输出二路选择器将RAM中的数据读出到data_out[15:0],实现跨时钟域输入输出仿真,练习数据帧的读写操作,以及跨时钟域标志信号传递。


d_buf模块代码如下:

    module d_buf(

            input                       clk,//写入时钟100M
            input                       rst_n,
            input                       r_clk,//读时钟75M
            input       [7:0]           data_in,
            input                       data_v,

            output                  data_ov,
            output  [15:0]      data_out

            );
            parameter           READ_END = 256-1;
            reg                 w_sel;//为0时,写入ram1,1则写入ram2
            reg                 data_v_dly;
            reg                 data_v_dly1;
            wire                    w_ram_en1,w_ram_en2;
            reg [9:0]           w_addr1,w_addr2;
            wire    [7:0]           w_data_1,w_data_2;
            reg                 r_start_flag;//跨时钟域的读使能信号
            reg [2:0]           r_start_buf;
            reg                 r_flag;
            reg [7:0]           r_cnt;
            reg                 r_flag_dly;
            reg                 r_sel;
            wire    [8:0]           r_addr1,r_addr2;
            wire   [15:0]       d_out_1,d_out_2;

            //延时data_v 一拍
            always@(posedge clk)
                data_v_dly <= data_v;

            always@(posedge clk)
                data_v_dly1 <= data_v_dly;

            always@(posedge clk,negedge rst_n)
                if(!rst_n)
                    w_sel <= 1'b0;
                else    if(data_v_dly && (~data_v))
                            w_sel <= ~w_sel;

            //写使能信号
            assign w_ram_en1 = data_v && (~w_sel);
            assign w_ram_en2 = w_sel && data_v;

            //产生写地址
            always@(posedge clk,negedge rst_n)
                if(!rst_n)
                    w_addr1 <= 'd0;
                else    if(w_ram_en1)
                            w_addr1 <= w_addr1 + 1'b1;
                else
                            w_addr1 <= 'd0;

            //产生写地址
            always@(posedge clk,negedge rst_n)
                if(!rst_n)
                    w_addr2 <= 'd0;
                else    if(w_ram_en2)
                            w_addr2 <= w_addr2 + 1'b1;
                else
                            w_addr2 <= 'd0;

            //利用data_v产生读开始标志(写时钟域:保持两个时钟周期,在r_clk中可踩到)         
            always@(posedge clk,negedge rst_n)
                if(!rst_n)
                    r_start_flag <= 1'b0;
                else    if(data_v == 1'b0 && data_v_dly1 == 1'b1)
                    r_start_flag <= 1'b1;
                else 
                    r_start_flag <= 1'b0;

            ram_w8x1024_r16x512 ram1_inst (
            .data ( data_in ),
            .rdaddress ( r_addr1 ),
            .rdclock ( r_clk ),
            .wraddress ( w_addr1 ),
            .wrclock ( clk ),
            .wren ( w_ram_en1 ),
            .q ( d_out_1 )
            );

            ram_w8x1024_r16x512 ram2_inst (
            .data ( data_in ),
            .rdaddress ( r_addr2 ),
            .rdclock ( r_clk ),
            .wraddress ( w_addr2 ),
            .wrclock ( clk ),
            .wren ( w_ram_en2 ),
            .q ( d_out_2 )
            );

            /* 
            //xilinx 的ram
            ram_w8x1024_r16x512 ram1(
            .clka       (clk),
            .wea        (w_ram_en1),
            .addra      (w_addr1),
            .dina       (data_in),
            .clkb       (r_clk),
            .addrb      (r_addr1),
            .doutb      (d_out_1)
            );

            ram_w8x1024_r16x512 ram2(
            .clka       (clk),
            .wea        (w_ram_en2),
            .addra      (w_addr2),
            .dina       (data_in),
            .clkb       (r_clk),
            .addrb      (r_addr2),
            .doutb      (d_out_2)
            );
             */
            //r_clk
            //寄存两拍
            always@(posedge r_clk)
                r_start_buf <= {r_start_buf[1:0],r_start_flag};

            //出现上升沿时,产生读开始标志r_flag
            always@(posedge r_clk)
                if(r_start_buf[2:1] == 2'b01)
                            r_flag <= 1'b1;
                else if ( r_cnt == READ_END)
                            r_flag <= 1'b0;

            //读开始,并计数
            always@(posedge r_clk,negedge rst_n)
                    if(!rst_n)
                        r_cnt <= 'd0;
                    else if(r_flag== 1'b1)
                        r_cnt <= r_cnt + 1'b1;
                    else
                        r_cnt <= 'd0;
            //r_flag延后一拍,产生读选r_sel信号
            always@(posedge r_clk)
                r_flag_dly <= r_flag;

            always@(posedge r_clk,negedge rst_n)
                if(!rst_n)
                    r_sel <= 1'b0;
                else    if(r_flag == 1'b0 && r_flag_dly == 1'b1)
                    r_sel <= ~r_sel;

            assign r_addr1  = r_cnt;
            assign r_addr2  = r_cnt;

            assign  data_out = r_sel?d_out_2:d_out_1;
            assign  data_ov = r_flag_dly;


            endmodule

testbench代码如下:

    `timescale 1ns/1ps
    module d_buf_tb();

        reg                 clk;
        reg                 rst_n;
        reg                 r_clk;//读时钟75M
        reg [7:0]           data_in;
        reg                 data_v;

        wire                    data_ov;
        wire    [15:0]      data_out;


        initial     
            begin
                clk = 0;
                r_clk = 0;
                data_v = 0;
                data_in= 0;
                rst_n = 0;
                #100 rst_n = 1;

            end

        always #5 clk = ~clk;//写入时钟100M
        always #6.6 r_clk = ~r_clk;//读时钟75M

        d_buf d_buf_inst(

        .clk                (clk),
        .rst_n              (rst_n),
        .r_clk              (r_clk),
        .data_in            (data_in),
        .data_v             (data_v),

        .data_ov            (data_ov),
        .data_out           (data_out)

    );

        initial
            begin
                #200;
                @(posedge clk)
                gen_frame();

                #500 $stop;
            end

        task gen_data();
            integer i;
            begin
            for(i=0;i<512;i=i+1)
                begin
                    @(posedge clk)
                    data_v <= 1'b1;
                    data_in <= i&8'hff;
                end
                @(posedge clk)
                    data_v = 0;
            end
        endtask

        task gen_rest();
            integer i;
            for(i=0;i<16;i=i+1)
                begin
                    @(posedge clk);
                end
        endtask

        task gen_frame();
            integer i;
            begin
                for(i=0;i<32;i=i+1)
                    begin
                        gen_data();
                        gen_rest();
                    end
            end
        endtask
    endmodule

RTL视图

FPGA学习笔记——Double Buffer_第1张图片

Modelsim仿真

你可能感兴趣的:(FPGA学习笔记)