08 SCL&SDA,类IIC协议(附源码)

08 SCL&SDA,类IIC协议(附源码)一 Overview 1 Theory 2 Demand 二 InterfaceDes 三 Timeing 1 M1 Timing 2 M2 Timing 四 DesignandFun 1 M1 RTL 2 M1 TestBench 3 M2 RTL 4 M2 TestBench 五 Result 1 M1




一、Overview

(1)Theory

  虽然使用了SCL、SDA和ACK,但这真的不是IIC协议呀,这个模块功能是实现并行数据流转换为一种特殊串行数据流模块的设计。

  • 模块框图:

在这里插入图片描述

  • 通信协议:

在这里插入图片描述

  • 模块时序:

在这里插入图片描述

(2)Demand

  • 设计两个可综合的电路模块:
      第一个模块(M1)能把4位的并行数据转换为符合以下协议的串行数据流,数据流用scl和sda两条线传输,sclk为输入的时钟信号,data[3:0]为输入数据,ack为数据输入的使能信号。
      第二个模块(M1)能把串行数据流内的信息接收到,并转换为相应16条信号线的高电平,即若数据为1,则第一条线路为高电平,数据为n,则第N条线路为高电平。
      M0为测试用信号模块。该模块接收M1发出的ack信号,产生新的测试数据data[3::0]。






  • 通信协议:
      scl为不断输出的时钟信号,如果scl为高电平时,sda由高变低时刻,串行数据流开始;如果scl为高电平时,sda由低变高,申行数据结束。sda信号的串行数据位必须在scl为低电平时变化,若变为高则为1,否则为零。


二、Interface Description

Signal NameWidthDirectionDescription
sclk1inputSystem clk signal, xxMhz
rst_n1inputSystem reset signal
data_i4inputSent data bus
ack1outputRequest send signal
scl1outputTwo divided-frequency signal
sda1outputData signal
outhigh16outputOut signal

三、Timeing

(1)M1-Timing

在这里插入图片描述

(2)M2-Timing

在这里插入图片描述


四、Design and Functional Verification

(1)M1-RTL

//-- modified by xlinxdu, 2022/05/06 module ptosda ( input sclk_i , input rst_n_i, input [3:0] data_i , output reg ack_o , output reg scl_o , output reg sda_o ); parameter IDLE = 7'b000_0001; parameter START = 7'b000_0010; parameter BIT1 = 7'b000_0100; parameter BIT2 = 7'b000_1000; parameter BIT3 = 7'b001_0000; parameter BIT4 = 7'b010_0000; parameter STOP = 7'b100_0000; reg sda_en; reg [3:0] data_buf; reg [6:0] cur_state; reg [6:0] nxt_state; /*-----------------------------------------------\ ------------ set flag --> sda_en --------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin sda_en <= 1'b0; end else if (ack_o) begin sda_en <= 1'b1; end else if((cur_state == STOP) && (ack_o == 1'b0)) begin sda_en <= 1'b0; end end /*-----------------------------------------------\ ---------------- updata data_buf ------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin data_buf <= 4'b0; end else if (ack_o) begin data_buf <= data_i; end end /*-----------------------------------------------\ ----------------- sclk --> scl ----------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin scl_o <= 1'b0; end else begin scl_o <= ~scl_o; end end /*-----------------------------------------------\ -------------------- ack signal ---------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin ack_o <= 1'b0; end else if ((cur_state == IDLE) && (sda_en == 1'b0)) begin ack_o <= 1'b1; end else begin ack_o <= 1'b0; end end /*-----------------------------------------------\ --------- updata current state of FSM ---------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin cur_state <= IDLE; end else begin cur_state <= nxt_state; end end always @ (*) begin case(cur_state) IDLE: if(sda_en && (scl_o == 1'b1))begin sda_o = 1'b0; nxt_state = START; end else begin sda_o = 1'b1; nxt_state = IDLE; end START:if(sda_en && (scl_o == 1'b0))begin sda_o = data_buf[0]; nxt_state = BIT1; end else begin sda_o = 1'b0; nxt_state = START; end BIT1: if(sda_en && (scl_o == 1'b0))begin sda_o = data_buf[1]; nxt_state = BIT2; end else begin sda_o = data_buf[0]; nxt_state = BIT1; end BIT2: if(sda_en && (scl_o == 1'b0))begin sda_o = data_buf[2]; nxt_state = BIT3; end else begin sda_o = data_buf[1]; nxt_state = BIT2; end BIT3: if(sda_en && (scl_o == 1'b0))begin sda_o = data_buf[3]; nxt_state = BIT4; end else begin sda_o = data_buf[2]; nxt_state = BIT3; end BIT4: if(sda_en && (scl_o == 1'b0))begin sda_o = 1'b0; nxt_state = STOP; end else begin sda_o = data_buf[3]; nxt_state = BIT4; end STOP: if(!sda_en && (scl_o == 1'b1))begin sda_o = 1'b1; nxt_state = IDLE; end else begin sda_o = 1'b0; nxt_state = STOP; end default:begin sda_o = 1'b1; nxt_state = IDLE; end endcase end endmodule 

(2)M1-Test Bench

module tb_ptosda; reg sclk_i ; reg rst_n_i; reg [3:0] data_i ; wire ack_o ; wire scl_o ; wire sda_o ; initial begin sclk_i = 0 ; rst_n_i = 1; data_i = 4'b0; #10 rst_n_i = 0; #10 rst_n_i = 1; data_i = 4'b1011; end always begin #5 sclk_i = ~sclk_i; end ptosda tb_ptosda( .sclk_i (sclk_i ), .rst_n_i(rst_n_i), .data_i (data_i ), .ack_o (ack_o ), .scl_o (scl_o ), .sda_o (sda_o ) ); initial begin #1000 $finish; $fsdbDumpfile("ptosda.fsdb"); $fsdbDumpvars ; $fsdbDumpMDA ; end endmodule 

(3)M2-RTL

//-- modified by xlinxdu, 2022/05/06 module out16hi ( input clk_i , input rst_n_i , input data_i , output reg [15:0] outhigh_o ); reg [1:0] dly_data; wire negedge_data_i; reg [3:0] data_buf; reg cnt_en; reg [2:0] cnt; always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin dly_data <= 2'b1; end else begin dly_data <= { 
         dly_data[0],data_i}; end end assign negedge_data_i = (dly_data == 2'b10)?1'b1:1'b0; always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin cnt_en <= 1'b0; end else if (negedge_data_i) begin cnt_en <= 1'b1; end else if(cnt == 3'd4)begin cnt_en <= 1'b0; end end always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin cnt <= 3'b0; end else if (cnt == 3'd4) begin cnt <= 3'b0; end else if (cnt_en) begin cnt <= cnt + 1'b1; end end always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin data_buf <= 4'b0; end else if(cnt == 3'd4) begin data_buf <= 4'b0; end else if (cnt_en) begin data_buf <= { 
         data_buf[2:0],data_i}; end end always @ (*)begin case(data_buf) 4'b0001: outhigh_o = 16'b0000_0000_0000_0001; 4'b0010: outhigh_o = 16'b0000_0000_0000_0010; 4'b0011: outhigh_o = 16'b0000_0000_0000_0100; 4'b0100: outhigh_o = 16'b0000_0000_0000_1000; 4'b0101: outhigh_o = 16'b0000_0000_0001_0000; 4'b0110: outhigh_o = 16'b0000_0000_0010_0000; 4'b0111: outhigh_o = 16'b0000_0000_0100_0000; 4'b1000: outhigh_o = 16'b0000_0000_1000_0000; 4'b1001: outhigh_o = 16'b0000_0001_0000_0000; 4'b1010: outhigh_o = 16'b0000_0010_0000_0000; 4'b1011: outhigh_o = 16'b0000_0100_0000_0000; 4'b1100: outhigh_o = 16'b0000_1000_0000_0000; 4'b1101: outhigh_o = 16'b0001_0000_0000_0000; 4'b1110: outhigh_o = 16'b0010_0000_0000_0000; 4'b1111: outhigh_o = 16'b0100_0000_0000_0000; 4'b0000: outhigh_o = 16'b1000_0000_0000_0000; default: begin outhigh_o = 16'b0000_0000_0000_0000; end endcase end 

(4)M2-Test Bench

module tb_ptosda; reg sclk_i ; reg rst_n_i; reg [3:0] data_i ; wire ack_o ; wire scl_o ; wire sda_o ; wire [15:0]outhigh_o; initial begin sclk_i = 0 ; rst_n_i = 1; data_i = 4'b0; #10 rst_n_i = 0; #10 rst_n_i = 1; data_i = 4'b1011; end always begin #5 sclk_i = ~sclk_i; end ptosda tb_ptosda( .sclk_i (sclk_i ), .rst_n_i(rst_n_i), .data_i (data_i ), .ack_o (ack_o ), .scl_o (scl_o ), .sda_o (sda_o ) ); out16hi tb_out16hi( .clk_i (scl_o ), .rst_n_i (rst_n_i ), .data_i (sda_o ), .outhigh_o(outhigh_o) ); initial begin #1000 $finish; $fsdbDumpfile("ptosda.fsdb"); $fsdbDumpvars ; $fsdbDumpMDA ; end endmodule 

五、Result

  

(1)M1

在这里插入图片描述

bug1:由仿真波形可以看出,请求信号占了时钟两个周期,与实际时序(一个周期)不符,定位到ack部分代码,分析是由sda_en使能信号延迟了一拍导致。

/*-----------------------------------------------\ -------------------- ack signal ---------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin ack_o <= 1'b0; end else if ((cur_state == IDLE) && (sda_en == 1'b0)) begin ack_o <= 1'b1; end else begin ack_o <= 1'b0; end end 

继续定位到sda_en代码块,发现sda_en是由ack_o控制拉高的,所以在ack_o在第一次被拉高时,使能信号是无法立刻被拉高的,依据实际功能,这里sda_en应该和请求信号ack同一拍产生。

/*-----------------------------------------------\ ------------ set flag --> sda_en --------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin sda_en <= 1'b0; end else if (ack_o) begin sda_en <= 1'b1; end else if((cur_state == STOP) && (ack_o == 1'b0)) begin sda_en <= 1'b0; end end 

更改:把产生高电平的条件改一下

/*-----------------------------------------------\ ------------ set flag --> sda_en --------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin sda_en <= 1'b0; end else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin sda_en <= 1'b1; end else if((cur_state == STOP) && (ack_o == 1'b0)) begin sda_en <= 1'b0; end end 

在这里插入图片描述

结果:在更改后,请求信号和使能信号均正常


在这里插入图片描述

bug2:由通信协议可知道,scl为高电平时,sda由低变高时刻,串行数据结束。由仿真波形可以看到,红框是已经发送完4bit的数据,产生stop信号时刻。在红框内,scl第一次为高电平时,stop信号没有产生,而是在scl的下一拍产生,多了一拍,时序错误。由仿真波形可以看到,产生这个bug的原因是sda_en拉低的时刻晚了一拍。定位到sda_en代码块。

/*-----------------------------------------------\ ------------ set flag --> sda_en --------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin sda_en <= 1'b0; end else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin sda_en <= 1'b1; end else if((cur_state == STOP) && (ack_o == 1'b0)) begin sda_en <= 1'b0; end end 
//第二处 BIT4: if(sda_en && (scl_o == 1'b0))begin sda_o = 1'b0; nxt_state = STOP; end else begin sda_o = data_buf[3]; nxt_state = BIT4; end 

更改:把sda_en在发送完第四个bit数据时产生,同时把状态机里面的BIT4条件改一下。因为sda_en发生了改变。

/*-----------------------------------------------\ ------------ set flag --> sda_en --------------- \-----------------------------------------------*/ always @ (posedge sclk_i or negedge rst_n_i) begin if (!rst_n_i) begin sda_en <= 1'b0; end else if ((cur_state == IDLE) && (ack_o == 1'b0)) begin sda_en <= 1'b1; end else if((cur_state == BIT4) && (ack_o == 1'b0)) begin sda_en <= 1'b0; end end 
//第二处 BIT4: if(!sda_en && (scl_o == 1'b0))begin sda_o = 1'b0; nxt_state = STOP; end else begin sda_o = data_buf[3]; nxt_state = BIT4; end 

在这里插入图片描述

结果:更改后,仿真波形与时序一致。

(2)M2

在这里插入图片描述

bug1:由仿真波形可以看到,传输的数据结束之后,data_buf,依然在缓冲数据,所以输出一直在变,不符合时序要求,问题定位到data_buf更新代码块。

always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin data_buf <= 4'b0; end else if (cnt_en) begin data_buf <= { 
          data_buf[2:0],data_i}; end else begin data_buf <= 4'b0; end end 

更正:在一次数据传输结束之后,data_buf清零,可解决上述bug。

always @ (posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin data_buf <= 4'b0; end else if(cnt == 3'd4) begin data_buf <= 4'b0; end else if (cnt_en) begin data_buf <= { 
          data_buf[2:0],data_i}; end end 

在这里插入图片描述

bug2:在解决bug1后,发现清零会带来一个问题,就是改数据被误认为是M1传输过来的,定位到输出代码块

always @ (*)begin case(data_buf) 4'b0001: outhigh_o = 16'b0000_0000_0000_0001; 4'b0010: outhigh_o = 16'b0000_0000_0000_0010; 4'b0011: outhigh_o = 16'b0000_0000_0000_0100; 4'b0100: outhigh_o = 16'b0000_0000_0000_1000; 4'b0101: outhigh_o = 16'b0000_0000_0001_0000; 4'b0110: outhigh_o = 16'b0000_0000_0010_0000; 4'b0111: outhigh_o = 16'b0000_0000_0100_0000; 4'b1000: outhigh_o = 16'b0000_0000_1000_0000; 4'b1001: outhigh_o = 16'b0000_0001_0000_0000; 4'b1010: outhigh_o = 16'b0000_0010_0000_0000; 4'b1011: outhigh_o = 16'b0000_0100_0000_0000; 4'b1100: outhigh_o = 16'b0000_1000_0000_0000; 4'b1101: outhigh_o = 16'b0001_0000_0000_0000; 4'b1110: outhigh_o = 16'b0010_0000_0000_0000; 4'b1111: outhigh_o = 16'b0100_0000_0000_0000; 4'b0000: outhigh_o = 16'b1000_0000_0000_0000; default: begin outhigh_o = 16'b0000_0000_0000_0000; end endcase end 

更正:加入计数控制条件,只有在数据传输过程(cnt=1~4)时,data_buf的0才有效,否则输出16‘b0。

always @ (*)begin case(data_buf) 4'b0001: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0001;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0010: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0010;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0011: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_0100;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0100: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0000_1000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0101: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0001_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0110: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0010_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0111: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_0100_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1000: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0000_1000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1001: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0001_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1010: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0010_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1011: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_0100_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1100: if(cnt !== 3'd0) begin outhigh_o = 16'b0000_1000_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1101: if(cnt !== 3'd0) begin outhigh_o = 16'b0001_0000_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1110: if(cnt !== 3'd0) begin outhigh_o = 16'b0010_0000_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b1111: if(cnt !== 3'd0) begin outhigh_o = 16'b0100_0000_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end 4'b0000: if(cnt !== 3'd0) begin outhigh_o = 16'b1000_0000_0000_0000;end else begin outhigh_o = 16'b0000_0000_0000_0000;end default: begin outhigh_o = 16'b0000_0000_0000_0000; end endcase end 

(3)Top

在这里插入图片描述

在进行M1-M2-M0联调时,波形如上,输出符合预期时序要求,前仿真功能正常。


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/226075.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 上午7:54
下一篇 2026年3月17日 上午7:54


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号