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


虚拟机:VMware -14.0.0.24051
环 境:ubuntu 18.04.1
脚 本:makefile(点击直达
应用工具:vcs 和 verdi



一、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联调时,波形如上,输出符合预期时序要求,前仿真功能正常。


作者:xlinxdu
版权:本文是作者原创,版权归作者所有。
转载:未经作者允许,禁止转载,转载必须保留此段声明,必须在文章中给出原文连接。

  • 92
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xlinxdu

你的鼓励是我最大的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值