vivado技巧|如何在fpga内部实现i2c信号透传(fpga内部两组i2c inout信号互连)

本文介绍了如何在FPGA内部通过三态门实现I2C信号的透传,包括双向IO口的原理和使用状态机控制信号传输。作者通过实例展示了如何在FPGA中实现实际电路中导线连接的功能,并成功进行了I2C读写操作的验证。
摘要由CSDN通过智能技术生成

大家好,我是数字小熊饼干,一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结,并通过汇总成文章的形式进行输出,相信无论你是在职的还是已经还准备入行,看过之后都会有有一些收获,如果看完后喜欢的话就请关注我吧~谢谢~

最近遇到了这么一个问题:我有一个i2c master,它的管脚与fpga相连接,在fpga内部有一个i2c slave,在fpga外部还有一个i2c slave,但是它的管脚直接与fpga管脚连接了,并没有多余的地方让我能够直接接线和i2c master连接。因此,问题就转换成了——如何在fpga内部实现i2c信号透传,即在fpga内部实现相当于实际用导线直接把两个端口连接在一起的功能。

一、三态门

i2c的scl和sda信号的输出都是漏极开路,只能输出低电平和高阻态,且外接了一个上拉电阻,将i2c总线空闲时的输出电平拉至高电平,最终实现了线与的功能。

而在fpga内部,我们通常将端口处i2c的scl和sda信号定义为inout类型的信号,用verilog代码描述则为:

	inout scl;
    wire scl_out, scl_in;
​
    assign scl = (scl_out==1'b0) ? 1'b0 : 1'bz;
​
    assign scl_in = scl;

其中,scl为fpga端口处的scl信号,scl_out为内部输出的scl信号,scl_in为内部输入的scl信号。上述代码会被综合成三态门的形式,即:
在这里插入图片描述
之所以叫三态门,是因为它的第三个状态是高阻态z。在实际电路中高阻态意味着响应的管脚悬空、断开,输出电平既不是0,也不是1,对下级电路无任何影响,和没接一样。当三态门的控制信号EN为真时,三态门导通;控制信号为假时,三态门的输出端是高阻态。

对于inout端口这种双向IO口,是用了两个三态门来实现的,一个负责输出,一个负责输入,通过EN与EN’作为控制信号来实现一个高阻断路,另一个实现输出/输入。因此,虽然说是双向IO口,但是其实它的输入和输出不是同时进行的,需要有一个控制信号EN来控制端口什么时候为输出,什么时候为输入。

对于我们的i2c scl与sda信号来说,若EN有效,则三态门作为输出使用,输出0值,否则则则为高阻态z,此时则是作为输入使用。

二、fpga内部i2c信号的互连

在了解了三态门实现inout端口的原理之后,我们回到最初的那个问题:如何在fpga内部实现i2c信号透传,即实现相当于实际中用导线直接把两个端口连接在一起的功能。

其实问题的解决方法很简单!要想实现fpga内部实现i2c信号透传,关键在于控制一个inout端口为输入,一个inout端口为输出!也就是如下图所示:
在这里插入图片描述
于是问题又变成了如何控制三态门的使能,以在i2c master输出,i2c slave输入,与i2c master输入,i2c slave输出之间来回切换。

这个问题也很好解决,我们可以通过状态机中i2c地址阶段,地址应答阶段,读阶段,读应答阶段,写阶段,写应答阶段的跳转,结合对应的i2c信号输入是否为0,来对三态门的使能进行控制,直接上代码:

 localparam DRIVE_IDLE       = 3'b000;
    localparam DRIVE_ADDR       = 3'b001;
    localparam DRIVE_ADDR_ACK   = 3'b010;
    localparam DRIVE_WR         = 3'b011;
    localparam DRIVE_WR_ACK     = 3'b100;
    localparam DRIVE_RD         = 3'b101;
    localparam DRIVE_RD_ACK     = 3'b110;
    reg [2:0] i2c_drive_cs, i2c_drive_ns;
    reg rd_wt;
   
    always @(posedge clk_sys or negedge rst_n) begin
        if (!rst_n) begin
            rd_wt <= 1'b0;
        end else if (start_evt) begin
            rd_wt <= 1'b0;
        end else if ((i2c_drive_cs==DRIVE_ADDR) && (scl_pos_cnt==4'd8)) begin
            rd_wt <= sda_i_sync;
        end
     end
   
    always @(*) begin
        if (stop_evt)begin
            i2c_drive_ns = DRIVE_IDLE;
        end else if (start_evt && i2c_direct_access_en) begin    
            i2c_drive_ns = DRIVE_ADDR;
        end else begin
            i2c_drive_ns = i2c_drive_cs;
            case(i2c_drive_cs)
                //DRIVE_IDLE: i2c_drive_ns = start_evt && i2c_direct_access_en ? DRIVE_ADDR : i2c_drive_cs;
                DRIVE_ADDR: i2c_drive_ns = (scl_pos_cnt==4'd8) && scl_neg ? DRIVE_ADDR_ACK : i2c_drive_cs;
                DRIVE_ADDR_ACK: i2c_drive_ns = scl_neg ? (rd_wt ? DRIVE_RD : DRIVE_WR ) : i2c_drive_cs;
                DRIVE_WR :  i2c_drive_ns = (scl_pos_cnt==4'd8) && scl_neg ? DRIVE_WR_ACK : i2c_drive_cs;
                DRIVE_WR_ACK:   i2c_drive_ns = scl_neg ? DRIVE_WR : i2c_drive_cs;
                DRIVE_RD:   i2c_drive_ns = (scl_pos_cnt==4'd8) && scl_neg ? DRIVE_RD_ACK : i2c_drive_cs;
                DRIVE_RD_ACK:    i2c_drive_ns = scl_neg ? DRIVE_RD : i2c_drive_cs;
                default: i2c_drive_ns = i2c_drive_cs;
            endcase
        end
    end
    always @(posedge clk_sys or negedge rst_n) begin
        if (!rst_n) begin
            i2c_drive_cs <= DRIVE_IDLE;
        end else begin
            i2c_drive_cs <=  i2c_drive_ns;
        end
     end
    reg i2c_sda_mst_drive_oe, i2c_sda_drive_oe, i2c_scl_mst_drive_oe;  
    always @(posedge clk_sys or negedge rst_n) begin                    
        if (!rst_n) begin                                              
            i2c_sda_mst_drive_oe <= 1'b0;                              
            i2c_sda_drive_oe     <= 1'b0;                              
            i2c_scl_mst_drive_oe <= 1'b0;                              
        end else begin                                                  
            i2c_sda_mst_drive_oe <= ((i2c_drive_ns == DRIVE_ADDR) || (i2c_drive_ns == DRIVE_WR) || (i2c_drive_ns == DRIVE_RD_ACK)) && (sda_i_sync==1'b0);    
            i2c_sda_drive_oe     <= ((i2c_drive_ns == DRIVE_ADDR_ACK) || (i2c_drive_ns == DRIVE_WR_ACK) || (i2c_drive_ns == DRIVE_RD)) && (sda_master_in_sync==1'b0);      
            i2c_scl_mst_drive_oe <= (i2c_drive_ns != DRIVE_IDLE) && (scl_i_sync==1'b0);
        end                                                            
    end      
​
​
    assign sda =(i2c_sda_drive_oe) ? 1'b0 : 1'bz ;  
    assign scl = 1'bz ;                                                                                  
    assign sda_i = sda ;
    assign scl_i = scl ;
​
​
    assign sda_mst = (i2c_sda_mst_drive_oe) ? 1'b0 : 1'bz ;        
    assign scl_mst = (i2c_scl_mst_drive_oe) ? 1'b0 : 1'bz ;  
    assign sda_master_in = sda_mst ;
    assign scl_master_in = scl_mst ;


其中scl和sda为输入fpga的i2c master inout端口信号,scl_mst和sda_mst为连接fpga外部i2c slave inout端口信号。

使用vivado跑的波形仿真如下图所示:
在这里插入图片描述
第一帧访问fpga内部i2c salve下配置,第二帧为访问fpga外部i2c salve进行2c burst读操作,第三帧为访问fpga外部i2c salve进行i2c burst写操作,第四帧为访问fpga外部i2c salve进行i2c burst读,i2c读写正常工作。

然后我们将vivado生成的bit文件烧写到fpga上进行测试,逻辑分析仪ila的结果如下:
在这里插入图片描述
这是一个i2c读序列波形,i2c波形正常,说明我们的代码没有问题。

如果你喜欢这篇文章的话,请关注我的公众号-熊熊的ic车间,里面还有ic设计和ic验证的学习资料和书籍等着你呢~欢迎您的关注!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字ic小熊饼干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值