1
\$\begingroup\$

I have been building a synchronized I2C slave receiver with Verilog.

The I2C slave receiver did not encounter any issues when I simulated it with Modelsim. However, it does not function properly when it has been programmed into a (Cyclone V) FPGA.

Below is the waveform from Modelsim simulation:

enter image description here

With the Signal-Tap analyzer on the Quatras program, I have observed two issues on the synchronized counter of the I2C slave. I do not know the root causes of the two issues, please give advice:

Bug #1: Counter number unstable.

enter image description here

Bug #2: Unable to detect input signal(SCK) rising edge.

enter image description here

Here's the Verilog code I used for the counter to detect the SCK rising edge, I have altered the counter to a switch statement as an attempt to fix counter number unstable, but it does not work:

always @(posedge clk)
begin
    if  (SCL_buf == 0 && SCL == 1) // posedge SCL
        begin
            // Read the Value
            if(WR_Active == 1)
            begin
                // Count Up until Byte Write is completed
                if(Bit_Count < 8)
                begin
                    Bit_Count_wr = 7 - Bit_Count;
                    Byte_in[Bit_Count_wr] = SDA;
                    //Bit_Count <= Bit_Count + 1;
                    case(Bit_Count)
                        4'b0000 : Bit_Count <= 4'b0001;
                        4'b0001 : Bit_Count <= 4'b0010;
                        4'b0010 : Bit_Count <= 4'b0011;
                        4'b0011 : Bit_Count <= 4'b0100;
                        4'b0100 : Bit_Count <= 4'b0101;
                        4'b0101 : Bit_Count <= 4'b0110;
                        4'b0110 : Bit_Count <= 4'b0111;
                        4'b0111 : Bit_Count <= 4'b1000;
                    endcase
                end
                // Acknowledge + Address Check/ Write Byte Process
                else
                begin
                    Bit_Count = 0;
                    //........
                    end 
                end
            end 
        end
        SCL_buf <= SCL;
    end
\$\endgroup\$
2
  • 1
    \$\begingroup\$ I think you should try synchronizing every asynchronous input in your module. Even it doesn't work in your case, it is always a good practise. Here is some information about the concept: www.doulos.com/knowhow/fpga/synchronisation/ \$\endgroup\$ – packt Feb 9 '20 at 2:15
  • \$\begingroup\$ Are you feeding your external signals through a chain of at least two-flip flops before using them so your external signals to your internal clock domain? You get metastability issues which you don't which can appear as jitter in your output signals. \$\endgroup\$ – DKNguyen Feb 11 '20 at 21:15
3
\$\begingroup\$

It looks like you are using SCL without ever synchronizing it to the clock. This is likely causing most of your strange behavior. Never use an external signal without first passing it through at least one register.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for your response. Do you mean the issue is resolved because the "SCL" signal has first passed the "reg [2:0] SCLSynch" register? In my code, I have also used "if(SDA_buf && ~SDA) // negedge SDA" and "else if (~SDA_buf && SDA) // posedge SDA" statements, but did not encounter similar issue. Is is likely due to the the higher frequency and complexity of "SCL" edge statements? \$\endgroup\$ – Eric Chuang Feb 18 '20 at 19:28
  • \$\begingroup\$ I don't think the issue is completely resolved in your new code. You're still using SCL directly in an if statement. You should only be using the registered version. And you should also synchronize SDA in the same way as it has potential to cause the same issue. \$\endgroup\$ – alex.forencich Feb 18 '20 at 19:31
0
\$\begingroup\$
  1. First make sure your simulation use the same clock frequency as your actual circuit.

  2. It is better to add a filter to SCL and SDA to remove the noise and sync asynchronous signals to clock.

  3. Please check your logic for SCL_BUF. I think this should not be under the condition when there is rising edge of SCL.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your comments. I will check the clock and evaluate if it needed to add filters. For #3, to clarify, my design is comparing the value of "SCL" and its value of previous rising edge of the sampling clock "CLK", where I store it as "SCL_BUF". So there when SCL_BUF=0, and SCL=1 means there is a rising edge. \$\endgroup\$ – Eric Chuang Feb 10 '20 at 3:45
0
\$\begingroup\$

FYI, I have resolved the instability by altering my code to the following, my guess the instability is been caused by to many arguments in each rising edge of the sampling clock "CLK".

 reg [2:0] SCLSynch = 3'b000;  
 wire SCL_posedge = (SCLSynch[2:1] == 2'b01);  
 wire SCL_negedge = (SCLSynch[2:1] == 2'b10);  

 always @(posedge clk)
 begin
   SCLSynch <= {SCLSynch[1:0], SCL};

   if(SCL && SCLSynch[1] && ~WR_ACK)  
   begin
     if  (SCL_posedge && WR_Active) // posedge SCL
     begin
       // Read the Value
       // Count Up until Byte Write is completed
       if(~Bit_Count[3])
       begin
         Bit_Count_wr = 7 - Bit_Count;
         Byte_in[Bit_Count_wr] = SDA;
         Bit_Count <= Bit_Count + 1;
       end
       // Acknowledge + Address Check/ Write Byte Process
       else
       begin
         Bit_Count <= 0;
         // .....
       end
     end
   end
 end
```
\$\endgroup\$
0
\$\begingroup\$

Found some basic issues of coding ,use of Non-Blocking Assignments and Blocking Assignments in the same always procedural block is not recommended even though it supported by LRM.. Usually all the sequential code is written using NBA(<=) and combinatorial logic is written in BA (=).. I have just improved readability and replaced the BA in sequential block with NBA. I have seen similar kind of issue u faced with counters during meta stability in FPGA .After making the changes in ur code then bring up the functional simulation again and go to the Hardware Bringup.

please use the below code for the meta stability removal and i have added posedge,negedge detectors separately so that u can use them in ur FSM triggering logic.U can go through the below articles to understand completely y u have faced CDC issue. suggestions :

  1. Please use CDC techniques for async CDC
  2. Please use #TCQ in NBA assignments in sequential blocks
  3. Please use standard FSM coding techniques for ease of coding,debugging

https://www.eetimes.com/understanding-clock-domain-crossing-issues/

https://forums.xilinx.com/t5/Simulation-and-Verification/why-there-is-a-TCQ-delay-in-many-Xilinx-official-example-designs/td-p/682670

Best in class paper i have come across till now for ASYNC CDC and FSM design

http://www.sunburst-design.com/papers/CummingsSNUG2008Boston_CDC.pdf

http://www.sunburst-design.com/papers/CummingsSNUG2003SJ_SystemVerilogFSM.pdf

http://www.sunburst-design.com/papers/CummingsSNUG2019SV_FSM1.pdf

always @(posedge clk)
begin
    if  (SCL_buf == 0 && SCL == 1) begin // posedge SCL
            // Read the Value
            if(WR_Active == 1) begin
                // Count Up until Byte Write is completed
                if(Bit_Count < 8) begin
                    Bit_Count_wr          <= #10 7 - Bit_Count;
                    Byte_in[Bit_Count_wr] <= #10 SDA;
                    Bit_Count             <= #10 Bit_Count + 1;
                end
                // Acknowledge + Address Check/ Write Byte Process
                else
                begin
                    Bit_Count <= #10 0;
                    //........
                    end 
                end
            end 
        end
        SCL_buf <= SCL;
    end

   sync # (
        .SYNC_MTBF (2)
       ,.WIDTH     (1)
    ) u_sda_sync(
        .clk        (clk)
       ,.data_in    (SDA)
       ,.data_out   (SDA_sync)
    );

   sync # (
        .SYNC_MTBF (2)
       ,.WIDTH     (1)
    ) u_sck_sync(
        .clk        (clk)
       ,.data_in    (SCK)
       ,.data_out   (SCK_sync)
    );

reg sck_sync_r1,sda_sync_r1,
    sck_posedge,sck_negedge,
    sda_posedge,sda_negedge;

    always @(posedge clk) begin
        sck_sync_r1 <= #10  SCK_sync;
        sda_sync_r1 <= #10  SDA_sync;
        sda_negedge <= #10  sda_sync_r1 & ~SDA_sync;
        sda_posedge <= #10 ~sda_sync_r1 &  SDA_sync;
        sck_negedge <= #10  sck_sync_r1 & ~SCK_sync;
        sck_posedge <= #10 ~sck_sync_r1 &  SCK_sync;
    end


module sync #(
    parameter SYNC_MTBF = 2
   ,parameter WIDTH     = 1
   ,parameter TCQ       = 100
)(
    input              clk
   ,input  [WIDTH-1:0] data_in
   ,output [WIDTH-1:0] data_out
);

  for (genvar wd=0; wd<WIDTH; wd=wd+1) begin : SYNC
    (* dont_touch = "true" *) (* ASYNC_REG = "TRUE" *) reg [SYNC_MTBF-1:0] sync_reg;

    always @(posedge clk)
      sync_reg <= #TCQ {sync_reg[0+:SYNC_MTBF-1], data_in};

    assign data_out[wd] = sync_reg[SYNC_MTBF-1];
  end

endmodule
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.