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:
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.
Bug #2: Unable to detect input signal(SCK) rising edge.
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
-
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– packt2020年02月09日 02:15:36 +00:00Commented Feb 9, 2020 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– DKNguyen2020年02月11日 21:15:23 +00:00Commented Feb 11, 2020 at 21:15
4 Answers 4
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.
-
\$\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– Eric Chuang2020年02月18日 19:28:14 +00:00Commented Feb 18, 2020 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– alex.forencich2020年02月18日 19:31:19 +00:00Commented Feb 18, 2020 at 19:31
First make sure your simulation use the same clock frequency as your actual circuit.
It is better to add a filter to SCL and SDA to remove the noise and sync asynchronous signals to clock.
Please check your logic for SCL_BUF. I think this should not be under the condition when there is rising edge of SCL.
-
\$\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– Eric Chuang2020年02月10日 03:45:01 +00:00Commented Feb 10, 2020 at 3:45
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
```
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 :
- Please use CDC techniques for async CDC
- Please use #TCQ in NBA assignments in sequential blocks
- Please use standard FSM coding techniques for ease of coding,debugging
https://www.eetimes.com/understanding-clock-domain-crossing-issues/
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