这个问题我纠结了好久,本来以为写对了的代码,结果发现再添加了延时操作以后,时序仿真中出现了延拍现象,导致后面的时序都错了。今天找到了解决方法,特此写一个博客记录一下:
首先我想到的是方法如下:
always@( posedge scl )
begin
s0 <= sda;
end
always@( negedge scl )
begin
s1 <= sda;
end
always@( negedge scl )
begin
if( (s0 == 1 & s1 == 0 ) )
start_state <= 1;
else if ( s0 == 0 & s1 == 1 )
stop_state <= 1;
else begin
start_state <= 0;
stop_state <= 0;
end
end
在时序仿真的时候,上段代码在没有加上#2延时的时候居然没有问题….囧…归结原因就是start_state这个寄存器通过某种形式与s1形成了由同一个时钟沿构成了级联方式,这样的理论上start_state,以及stop_state都会延时一拍,到下个时钟下降沿才会出现正脉冲。而不加#2延时符号,仿真器还识别不出来这个设计瑕疵,加上延时,就可以看到时序全部乱了。
后来想用组合逻辑来解决,代码如下:
always@( sda or scl )
begin
//用来捕捉SCL高电平处,SDA下降的状态机
if( scl == 0 ) begin
capture_start_state = start_step1;
start_state = 0;
end
else if( scl == 1 && sda == 1 && capture_start_state == start_step1 )
capture_start_state = start_step2;
else if( scl == 1 && sda == 0 && capture_start_state == start_step2 )
begin
start_state = 1;
capture_start_state = start_step1;
end
end
以上定义了一个状态机,当scl的时候start_state 以及状态机初始化,当scl与sda同时为1的时候调到step2态,如果step2态中在一个scl = 1与sda = 0,那么就可以判断start_state为0了
但是这个start信号宽度太窄,想了很多办法,都没法做到使得start_state脉冲能够持续半个以上SCL周期。有同学想到办法可以跟我交流一下。所以这种方法,也只能放弃/(ㄒoㄒ)/~~
最后一种方法还是网上查到的方法
always@( posedge scl )
begin
s0 <= sda;
end
always@( negedge scl )
begin
s1 <= sda;
end
always@( negedge scl )
begin
s2 <= s0;
end
assign start_state = ( s2 ) & ( !s1 );
assign stop_state = ( s1 ) & ( !s2 );
首先仍然是采样上升沿信号s0与下降沿信号s1,然后在下降沿的时候采样s0得到s2信号,这样做的作用是将
s0信号延时半个周期,s2与s1也就可以进行对齐的比较了,比较的结果,就是持续一个下降沿到下降沿周期的一个结果,这个结果是该下降沿所对应的SCL高电平期的两个沿的布尔结果(s2 & ( ! s1) 或者 !(s2) & s1)或是启动信号或者是终止信号,在SCL高电平上存在电平跳变的时候,start_state 或者stop_state这两根线上,会出现持续一个下降沿到下降沿的周期跳变
时序图如上,如果加上延时,更能反映真实时序了。
最后是我同事写的一个代码
always@( posedge scl )
s0 <= sda;
always@( negedge scl )
s1 <= sda;
always@( posedge scl )begin //将s0信号延时半拍形成,此时s2信号如果
start_state <= ( ( s0 ) && ( !s1 ) );
stop_state <= ( ( s1 ) && ( !s0) );
end
start_state 与 stop_state 在上升沿处进行s0与s1的布尔逻辑判断,由于采集s0的时候在在上升沿后延时一定时间,s0才会发生跳变,即假设采集到s0的时钟沿是第一个上升沿,在第二上升沿的时候,start_state与stop_state进行布尔运算的时候对应的s0仍然是第一个上升沿的采样值,依旧是说,在此处进行的布尔运算的s0和s1正是我们对应SCL高电平两个沿的SDA值,所以如果SCL高电平期间SDA变动,将会得到从正沿到正沿的一个周期的start_state 或者 stop_state的脉冲信号。 时序如下:
这种情况下,我们如果接受主always块中,还是以上升沿作为敏感信号,那么不能准确的捕捉到start_state信号,因为start_state会相对正沿会有一定的延时,那么此时捕捉到的应该是上个正沿的start_state的值,如果我们以负沿作为敏感信号,那么可以稳定的捕捉到start_state信号,那么需要我的主程序有一定的修改。
——————————–2016.5.5—————————————————
今天针对这个问题又发现了一点新的东西如下,针对start_state出现的不定态的情况,这是由于start_state <= ( ( s0 ) && ( !s1 ) );这句话,由于s0 ,s1在某一时刻确实可能同时为不定态,那么按照真值表不定态相与,为不定态,所以,在SDA上出现一个周期的高阻的时候,start_state信号会变成不定态,这种情况当然最好不要出现的。所以我想利用三态逻辑?:来实现一个判断,代码如下:
always@( posedge scl )
//s0 <= sda?1:0;
if( sda == 1 )
s0 <= 1 ;
else
s0 <= 0 ;
always@( negedge scl )
s1 <= sda?1:0;
assign w_start = ( s0 == 1 && s1 ==0 )?1:0,
w_stop = ( s0 == 0 && s1 == 1 )?1:0;
always@( posedge scl )begin //将s0信号延时半拍形成,此时s2信号如果
start_state <= (w_start)?1:0;
stop_state <= ( w_stop )?1:0;
end
我的想法当然是s0 == 1 && s1 == 0 这个布尔表达式如果不等于1,则为1,否则就为0,
但这样情况s0为不定态,以及s1为不定态的时候,w_start线还是会出现不定态。
而下面这段代码,却没有问题
always@(posedge scl )
s1 <= sda ? 1 : 0;
always@(negedge scl )
s2 <= sda ? 1 : 0;
always@(posedge scl )
start_state <= start_flag;
always@(*)
begin
if(s1 == 1 && s2 == 0)
start_flag = 1;
else
start_flag = 0;
end
这段代码可以得到没有不定态的结果。
原因我想了想,按照某个视频教程上说的,s2 <= sda ? 1 : 0,应该被综合成一个与门,即sda与1进行与。
那么问题就来了,如果sda为高阻的时候,sda与1的结果是不定态,这就造成了最后有s1上有不定态产生,同理s2,w_start上也是一样。 而下面的if else语句十有八九是综合出了比较器,那么只要上面的布尔表达式的值不等于1,那么start_flag就置为0.
那么从根本上杜绝这种出现不定态的情况,就从s0和s1信号着手解决
always@( posedge scl )
if( sda == 1 )
s0 <= 1 ;
else
s0 <= 0 ;
always@( negedge scl )
if( sda == 1 )
s1 <= 1;
else
s1 <= 0;
assign w_start = ( s0 == 1 && s1 ==0 )?1:0,
w_stop = ( s0 == 0 && s1 == 1 )?1:0;
always@( posedge scl )begin //将s0信号延时半拍形成,此时s2信号如果
start_state <= (w_start)?1:0;
stop_state <= ( w_stop )?1:0;
end
这里可以看到,不定态问题彻底消除啦。。