FPGA(五)RTL代码之一(跨时钟域设计)


前言

这不马上要面试了嘛,有些慌!HDLbits的题目已经刷完了,但又知道自己还远远不够,就从一个B站UP主那里截取了一个刷题的题库,只有题目,代码都要自己去写,所以写的代码可能存在问题,我打算分成好几节把刷的题目代码放出来。仅供自己学习。如果有需要的也可以看看,若有错误,还望指出!谢谢!
请添加图片描述


一、异步FIFO

关于这部分内容,我在上一篇博客FPGA(四)数字IC面试的四个基本问题的第四章第二部分详细讲过,也是有代码的,采用的格雷码的方法。

二、异步复位同步释放

详细解释可参考异步复位同步释放原理

简单解释就是:

异步复位,同步释放就是指在复位信号到来的时候不受时钟信号的同步,而是在复位信号释放的时候受到时钟信号的同步。

RTL代码如下:

module Sys_Rst(
	input 	clk		,
	input 	rst_n	,
	output 	sys_rst	
);
reg rst_r0;
reg rst_r1;
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		rst_r0 <= 1'b0;
		rst_r1 <= 1'b0;
	end
	else begin
		rst_r0 <= 1'b1;
		rst_r1 <= rst_r0;
	end
end
assign sys_rst = rst_r1;
endmodule

三、两级触发器同步

两级触发器一般就是我们常说的打两拍,可以有效降低亚稳态产生的风险,如下所示:
在这里插入图片描述
RTL代码

module abit_Pulse(
    input           clk		    ,       //输入时钟
    input           rst_n       ,         //复位信号
    input           asynch_in   ,       //输入信号    
    output          synch_out  
);
reg [1:0] signal_r;
//在快时钟域下,将传递来的信号打两拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        signal_r <= 2'b00;
    else
        signal_r <= {signal_r[0],asynch_in};
end

assign  synch_out = signal_r[1];

endmodule

四、握手CDC设计

FPGA逻辑设计回顾(7)多比特信号的CDC处理方式之握手同步
一种基于握手机制的跨时钟域方法及其Verilog实现

握手指的是两个设备之间通信的一种方式,用来通信的信号就是握手信号。最简单的握手信号是 valid 和 ready,也可以叫 request 和 grant。假设设备1向设备2发送数据,设备1不知道设备2什么时候可以接收数据,设备2也不知道设备1什么时候会发送数据,那么它们之间如果用握手通信可能是这样的顺序:

  1. 设备1将 valid 信号置1,告诉设备2,数据准备就绪了,请查收
  2. 设备2此刻正处于忙碌状态无法接收数据,设备2将 ready 信号保持为0
  3. 设备2空闲了,将 ready 信号置1接收设备1的数据
  4. 设备1看到设备2的 ready 为1,它知道设备2已经接收好数据了,将 valid 置0同时撤销数据,准备下一次发送
  5. 可以看到因为有握手控制,可以确保数据的正确传输,不会丢失。跨时钟域的握手设计就是利用握手控制这种优势,从而避免因为跨时钟域引起的数据传输错误

RTL代码

/*************************************************************************************
输入:
·clk_a, clk_b      源时钟与目的市长
·rst                复位信号
·a_en,data_a       可以当成一种协议,检测到a_en有效(下降沿)的时候,输入数据就更新,
                    更新数据一直持续到a_en的下一个下降沿

输出:
·b_en               对a_en的同步
·data_b_out         对b的同步
·ack_a              是b时钟域的响应信号ack,同步到a时钟域后的信号,通知a时钟域(下降沿通知)
                    可以发送下一个数据了。事实上,a_en就是该信号的下降沿
*************************************************************************************/
module syn_handshake(
    input   wire       clk_a        ,     //源时钟
    input   wire       clk_b        ,     //目的时钟
    input   wire       rst          ,
    input   wire       a_en         , //来自于外部的使能信号,脉冲持续一个时钟周期
    input   wire [3:0] data_a_in    , //外部输入信号
    output  reg  [3:0] data_b_out   ,
    output  wire       b_en         ,
    output  wire       ack_a            
    );

    //生成a_en的下降沿 只用延迟一个周期即可
    reg     a_en_d1     ;
    //reg     a_en_d2     ;
    reg     a_en_neg    ;
    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a_en_d1 <= 1'd0;
    //        a_en_d2 <= 1'd0;
            a_en_neg <= 1'd0;
        end
        else begin
            a_en_d1 <= a_en;
        //    a_en_d2 <= a_en_d1;
            // a_en_neg <= ~a_en_d1 && a_en_d2;
            a_en_neg <= ~a_en && a_en_d1;
        end
    end

    //生成请求信号
    reg     a_req   ; 
    wire    a2_q_pos;
    always@(posedge clk_a or posedge rst) begin
        if(rst) 
            a_req <= 1'd0;
        else if(a_en_neg)
            a_req <= 1'd1;
        else if(a2_q_pos) //a_req 拉低条件
            a_req <= 1'd0;
        else 
            a_req <= a_req;    
    end

    //请求信号a_req跨时钟域处理
    reg     b1_q, b2_q;
    always@(posedge clk_b or posedge rst) begin
        if(rst) begin
            b1_q <= 1'd0;
            b2_q <= 1'd0;
        end
        else begin
            b1_q <= a_req;
            b2_q <= b1_q;
        end
    end

    //生成时钟域b内的数据使能信号
    //b2_q信号的上升沿
    //b时钟域对a时钟域的响应信号
    reg     b2_q_d1;
    reg     b2_q_d2;
    reg     b2_q_d3;
    reg     ack_b;
    always@(posedge clk_b or posedge rst) begin
        if(rst) begin
            b2_q_d1 <= 1'd0;
            b2_q_d2 <= 1'd0;
            b2_q_d3 <= 1'd0;
            ack_b <= 1'd0;            
        end
        else begin
            b2_q_d1 <= b2_q;
            b2_q_d2 <= b2_q_d1;
            b2_q_d3 <= b2_q_d2;
            ack_b <= b2_q_d2;
        end
    end
    assign b_en = ~b2_q_d3 && b2_q_d2;

    //b_en有效,则表示数据有效,可以采样a时钟域的数据了
    always@(posedge clk_b or posedge rst) begin
        if(rst)
            data_b_out <= 4'd0;
        else if(b_en)
            data_b_out <= data_a_in;
    end


    //响应信号同步到a时钟域
    //a2_q上升沿作为a_req拉低的条件
    reg a1_q, a2_q;
    reg a3_q ;
    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a1_q <= 1'd0;
            a2_q <= 1'd0;
            a3_q <= 1'd0;
        end
        else begin
            a1_q <= ack_b;
            a2_q <= a1_q;
            a3_q <= a2_q;
        end
    end
    assign a2_q_pos = ~a3_q && a2_q;

    assign ack_a = a2_q; //此信号作为a_en的反馈信号,a_en取此信号的下降沿
endmodule

仿真文件

`timescale 1ns / 1ps
module tb_syn_handshake;
    reg         clk_a        ;
    reg         clk_b        ;
    reg         rst          ;
    reg         a_en         ; //来自于外部的使能信号,脉冲持续一个时钟周期
    reg [3:0]   data_a_in    ; //外部输入信号
    wire [3:0]  data_b_out   ;
    wire        b_en         ;
    wire        ack_a        ;

    initial begin
        clk_a = 1'd0;
        forever begin
            #2 clk_a = ~clk_a;
        end
    end

    initial begin
        clk_b = 1'd0;
        forever begin
            #3 clk_b = ~clk_b;
        end
    end

    initial begin
        rst = 1'd1;
        #15
        @(negedge clk_a);
        rst = 1'd0;
    end

    reg ack_a_d1;

    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a_en <= 1'd1;
            ack_a_d1 <= 1'd0;
        end
        else begin
            ack_a_d1 <= ack_a;
            a_en <= ~ack_a && ack_a_d1;
        end
    end

    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            data_a_in <= 4'd0;
        end
        else if(a_en) begin
            data_a_in <= $random;
        end
    end

    syn_handshake u_syn_handshake(
        .clk_a       ( clk_a       ),
        .clk_b       ( clk_b       ),
        .rst         ( rst         ),
        .a_en        ( a_en        ),
        .data_a_in   ( data_a_in   ),
        .data_b_out  ( data_b_out  ),
        .b_en        ( b_en        ),
        .ack_a       ( ack_a       )
    );
endmodule

仿真结果
在这里插入图片描述

五、异步双口RAM

数字IC笔试基础:同步与异步双端口RAM实现

在FPGA设计中,经常会用到RAM,这里的RAM一般指的是静态的RAM(SRAM)。一般FPGA中就有所谓的block RAM, 它就是现成的RAM资源。通常情况下,我们使用IP核,进行设计,我们来设计自己的IP核。
RAM分为单端、伪双端、真双端三种模式。

  1. 单端RAM读写共享一个地址
  2. 伪双端RAM一个写端口,一个读端口,各自的地址和使能信号,写端口只负责写,读端口只负责读,所以称为伪双端
  3. 真双口RAM可以在任意时间访问任意地址,两个端口的地址是一样的,即共享内存和地址。这就会带来一个问题:同时读写一个地址会发生冲突。基于这个点矛盾就要设置限制条件
    请添加图片描述
    接下来,用Verilog实现一个异步双端口RAM,深度128,位宽16bit。A口写入,B口读出。支持片选,读写请求,要求代码可综合。

RTL代码

/*************************************************************************
实现一个异步双端口RAM,深度128,位宽16bit。A口写入,B口读出。支持片选,读写请求,要求代码可综合
*************************************************************************/
module Dual_Port_Sram #(
    parameter ADDR_WIDTH = 7,               //地址位宽
    parameter DATA_WIDTH = 16,              //数据位宽
    parameter DATA_DEPTH = 1 << ADDR_WIDTH  //数据深度  = 2^ADDR_WIDTH
)   (
    input                       clka    ,
    input                       clkb    ,
    input                       rst_n   ,
    input                       csen_n  ,   //片选信号,低有效
/*************Port A Signal Write********************************/
    input [ADDR_WIDTH-1:0]      addra   ,
    input [DATA_WIDTH-1:0]      data_a  ,
    input                       wrena_n ,
/*************Port B Signal Read********************************/
    input [ADDR_WIDTH-1:0]      addrb   ,
    input                       rdenb_n ,
    output [DATA_WIDTH-1:0]     data_b
);
/*************    A口写入逻辑    *********************************/
integer i;
reg [DATA_WIDTH-1:0] register[DATA_DEPTH-1:0];  //存储器定义
always @(posedge clka or negedge rst_n) begin
    if(!rst_n) begin
      for(i = 0; i < DATA_DEPTH; i = i + 1)
        register[i] <= 0;
    end
    else if(!wrena_n && !csen_n)
        register[addra] <= data_a;
end
/*************    B口读出逻辑    *********************************/
reg [DATA_WIDTH-1:0] rddata_b;  
always @(posedge clkb or negedge rst_n) begin
    if(!rst_n)
        rddata_b <= 1'b0;
    else if(!rdenb_n && !csen_n)
        rddata_b <= register[addrb];
    else
        rddata_b <= rddata_b;
end
assign data_b = rddata_b;
endmodule

仿真文件

`timescale 1ns/1ps
module tb_Dual_Port_Sram;
    parameter ADDR_WIDTH = 7;               //地址位宽
    parameter DATA_WIDTH = 16;              //数据位宽
    parameter DATA_DEPTH = 1 << ADDR_WIDTH;  //数据深度  = 2^ADDR_WIDTH

    reg                       clka      ;
    reg                       clkb      ;
    reg                       rst_n     ;
    reg                       csen_n    ;
    reg [ADDR_WIDTH-1:0]      addra     ;
    reg [DATA_WIDTH-1:0]      data_a    ;
    reg                       wrena_n   ;
    reg [ADDR_WIDTH-1:0]      addrb     ;
    reg                       rdenb_n   ;
    wire [DATA_WIDTH-1:0]     data_b    ;

    Dual_Port_Sram #(
    .ADDR_WIDTH (ADDR_WIDTH),
    .DATA_WIDTH (DATA_WIDTH),
    .DATA_DEPTH (DATA_DEPTH)
    )u_Dual_Port_Sram(
        .clka     (clka   ),
        .clkb     (clkb   ),
        .rst_n    (rst_n  ),
        .csen_n   (csen_n ),
        .addra    (addra  ),
        .data_a   (data_a ),
        .wrena_n  (wrena_n),
        .addrb    (addrb  ),
        .rdenb_n  (rdenb_n),
        .data_b   (data_b )
    ); 
    initial begin
        clka = 1'd0;
        forever begin
            #2 clka = ~clka;
        end
    end
    initial begin
        clkb = 1'd0;
        forever begin
            #3 clkb = ~clkb;
        end
    end

    initial begin
        rst_n = 1'b0;
        csen_n = 1'b1;
        wrena_n = 1'b1;
        addra = 1'b0;
        data_a = 1'b0;
        rdenb_n = 1'b1;
        addrb = 1'b0;

        #50
        rst_n = 1'b1;
        csen_n = 1'b0;

        #10
        wrena_n = 1'b0;
        repeat  (DATA_DEPTH-1) begin
            @(posedge clka) begin
                addra = addra + 1;
                data_a = data_a + 1;
            end
        end

        #35
        wrena_n = 1'b1;
        rdenb_n = 1'b0;
        repeat (DATA_DEPTH-1) begin
            @(posedge clkb) begin
              addrb = addrb + 1;
            end
        end

        #50
        rdenb_n = 1'b1;
        csen_n = 1'b1;
        #100 $stop;
    end
endmodule 

仿真结果
在这里插入图片描述

总结

这是第一部分,可能也是面试中比较难的一部分,但应该也是常考的一部分。要仔细研究代码,电路图和时序图都要能够做到胸有成竹。
因为有一些东西是参考网上的,如果存在侵权,请告知,然后我删除或标注出来。

  • 3
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSI-2(Camera Serial Interface 2)是一种用于高速串行传输图像和数据的协议,它可以在多个应用场景中使用,例如数字相机、手机、汽车后视摄像头等。为了实现CSI-2的RTL代码,您需要先了解CSI-2的协议规范和传输方式。 CSI-2协议规范定义了一系列传输协议和控制信号,其中包括: 1. 数据线(Data Lane):CSI-2使用多个数据线传输数据。在每个时钟周期内,每个数据线上传输一个数据位。 2. 时钟线(Clock Lane):CSI-2使用单个时钟线传输时钟信号,用于同步数据传输。 3. 同步信号(Sync Signal):CSI-2使用同步信号来标识一帧图像的开始和结束。 4. 控制信号(Control Signal):CSI-2使用一些控制信号来控制传输过程,例如数据格式、帧率等。 在RTL代码中,您需要实现CSI-2的数据接口和控制逻辑。具体实现步骤如下: 1. 实现数据接口:根据CSI-2规范定义的数据线和时钟线,设计并实现数据接口模块,用于接收和发送数据。 2. 实现控制逻辑:根据CSI-2规范定义的控制信号,设计并实现控制逻辑模块,用于控制数据传输过程,并发送同步信号。 3. 集成测试:将数据接口和控制逻辑模块集成到一起,并进行测试和验证,确保CSI-2协议的正确实现。 需要注意的是,CSI-2协议涉及到的数据传输速度较高,因此在实现RTL代码时需要考虑时序和时钟的问题。同时,为了提高数据传输的可靠性,还需要考虑数据校验和纠错等机制的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值