触发器的应用案例

描述

今天群友遇到一个在综合的时候报错ambiguous clock in event control的问题,我们就来看看一个always块会生成什么样的电路。

案例一

首先从最简单的一段代码来看:

 

always @(posedge clk ) begin
    c <= b;
end

 

在上面的代码里面敏感信号只有一个posedge clk,begin end中间也只有一个赋值语句,仅仅是一个打拍的操作,那么我们来看一下,他生成的电路是什么样子的。

复位信号

可以看到,clk和b经过一个IBUF后接入触发器的C端(时钟端)和D端(数据输入端),然后输出Q端经过一个OBUF连接到c。其中IBUF和OBUF以及BUFG是vivado自动帮我们插入的,当信号是顶层信号的时候vivado就会帮我们自动插入IBUF和OBUF,时钟信号帮我们自动挂到了时钟树上面。

案例二:

那么vivado是怎么认出来我们代码里面写的clk就是时钟信号呢,是靠clk这个名字吗,让我们把clk换成rst来看一下,代码如下:

 

always @(posedge rst ) begin
        c <= 1'b1;
end

 

复位信号

可以看到上面代码生成的电路和案例一中的电路是一样的,vivado也是把posedge rst认为是时钟信号接到了触发器的时钟端,可见vivado并不是靠名字来识别哪个信号是时钟,哪个信号是复位的。

案例三:

那么我们应该怎么生成一个复位信号呢,先看一下同步复位的情况,代码如下:

 

always @(posedge clk ) begin
    if(rst == 1'b1)begin
        c <= 1'b0;
    end
    else begin
        c <= b;
    end       
end

 

复位信号

可以看到复位端rst接到了触发器的复位信号上。注意在上述代码中是高电平复位的,那么我们再看一下如果是低电平复位会产生什么样子的电路,代码如下:

 

always @(posedge clk ) begin
    if(rst == 1'b0)begin
        c <= 1'b0;
    end
    else begin
        c <= b;
    end       
end

 

复位信号

可以看到在rst信号之后多了一个LUT,这个LUT的目的是将rst信号取反,也就是说我们想让rst为0的时候进行复位,但是vivado在生成电路的时候会将其取反变为1之后接到触发器的复位端,这里在复位端插入一级LUT便会影响我们的时序,这也是为什么我们常说FPGA更推荐同步高复位的原因之一。

在UG901中有相关的描述如下:

复位信号

案例四:

在案例三中展示了同步复位的情况,注意这里生成的触发器都是FDRE,在xilinx的FPGA中一共有四种触发器,在UG901中有说明,如下图:

复位信号

可以看到对于同步复位的触发器有FDSE和FDRE两种,两者的区别就是FDSE在复位的时候输出是1,FDRE是0。,这也是为什么在案例三中无论是高复位还是低复位生成的都是FDRE。

那么我们改变一下代码,让c在复位的时候变为1,也就是c <= 1'b1,来看看生成的电路是什么样子的,代码如下:

 

always @(posedge clk ) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end       
end

 

复位信号

可以看到在这种情况下,我们的代码就映射到了一个FDSE上。

案例五:

第五个案例就来看一下vivado是怎么把rst认为是复位信号的。我们先来看如下代码:

 

always @(posedge clk ) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    if (a == 1'b1)begin
        c <= b;
    end       
end

 

复位信号

可以发现复位信号没有了,FDRE是复位端直接接了地,那么我们有理由怀疑是不是vivado在处理if else的时候会把if里面的信号认为是复位信号呢。上述代码里面两个if语句并列,生成的电路也是a  b rst经过一大段组合逻辑之后接入到FDRE是D端。

案例六:

将案例五的代码稍将改变,也就是在第二个if前面增加一个else,代码如下

 

always @(posedge clk ) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else if (a == 1'b1)begin
        c <= b;
    end       
end

 

复位信号

可以看到我们的复位端又重新回来了,那么也就印证了在案例五中的猜想,他是靠if else, if elseif这样的结构来识别是不是复位的,怎么映射过去的,需要注意的是案例六中因为我们写了a==1时才把b的值给到c,那么a就被接入到了触发器的CE端,当a的值是0时,CE端为0,Q端保持上一次的值不变。注意在时序电路里面,我们不写else也不会生成latch。

案例七:

上面代码都是在敏感信号里面只有一个posedge clk,那么如果我们写多个敏感信号呢,会变成什么样子。

 

always @(posedge clk or posedge rst) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    if (a == 1'b1)begin
        c <= b;
    end       
end

 

我们先来看一下上述代码,敏感列表里面有两个信号,posedge clk和posedge rst。并且在begin end里面也没有if else或者if else if这种结构的语句,按照我们同步复位的几个案例的推断,他会把clk和rst都推断为时钟信号,而实际上我们不可能给一个xilinx的FPGA的触发器同时接两个时钟信号,这个时候vivado在综合的时候就给我们报错了。

因为我们知道一个触发器只能有一个时钟信号,如果有多个时钟信号我们也需要做时钟切换电路,来确保在同一时刻只有一个时钟接到上面。vivado推断不出来上述代码究竟哪个信号是时钟信号,那他只能报错了,告诉我们当前时钟信号是模棱两可的。

复位信号

案例八:

那么案例七中的代码怎么改呢,第一种选择案例五中的方式,将敏感列表变为一个,那么时钟信号自然就明确了。

第二种就是敏感列表中另一个信号变为复位信号。这两种改法取决于我们想要实现的逻辑是什么样子的。

 

always @(posedge clk or posedge rst) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end       
end

 

复位信号

第二种改法被综合为一个FDPE。

案例九:

 

always @(posedge clk) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
    
    if(a == 1'b1)begin
        c <= d;
    end
         
end

 

我们再来看一段代码:

有一组if else,如果只有这一组的话,他就应该和案例4一样生成一个FDSE,但是我们下面又给他加了一句if,那么他便不会将rst认为是一个复位信号了,而是和其他if else一起生成一大堆组合逻辑。

复位信号

案例十:

在案例九里面是对同一个信号进行赋值,如果我们对不同的信号进行赋值呢。

 

always @(posedge clk) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
    
    if(a == 1'b1)begin
        e <= d;
    end
         
end

 

上述代码和案例九里面,只有当a==1时的操作不同,一个是对c进行赋值,另一个是对新的寄存器e进行赋值,那么vivado就会对c和e两个寄存器分别处理,生成如下电路:

复位信号

这个代码和我们分成两个always写是一样的:

 

always @(posedge clk) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
   
         
end


always @(posedge clk) begin
    if(a == 1'b1)begin
        e <= d;
    end
         
end

 

案例十一:

如果我案例九中的代码换成异步复位呢,会发生什么:

 

always @(posedge clk or negedge rst) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
    
    if(a == 1'b1)begin
        c <= d;
    end
         
end

 

复位信号

综合完直接报错了,那么为什么案例九中没报错呢。是因为我们敏感列表里面有clk和rst,他们肯定不是复位就是时钟信号嘛,vivado也会这么考虑,但是在begin end里面写的代码块,按第一个if else结构应该生成带复位的触发器,而第二个if结构,他又不应该生成,vivado就傻眼了,只能报错啊。而案例九里面,rst不在敏感列表里面,vivado还有一种选择就是将其当成普通信号,所以案例九生成了一大堆的组合逻辑。

案例十二:

也许会有一种想法就是把案例十一的代码像案例十那样改,也就是对两个寄存器进行赋值,如下代码:

 

always @(posedge clk or posedge rst) begin
    if(rst == 1'b1)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
    
    if(a == 1'b1)begin
        e <= d;
    end
         
end

 

不过不好意思,这种写法也是错误的,会报错

复位信号

这是因为我们在敏感列表里面写两个信号,但是对于e这个寄存器又都没有使用,不会生成复位信号,那可不就接着报ambiguous clock in event control了。在上述代码中对c的操作是不会出错的。

案例十三:

看到这里不知道大家有没有注意到,在异步复位里面,我们都是posedge rst和if(rst == 1'b1)也就是高电平复位,注意这两个是需要匹配的,如果我们写个posedge rst但是紧接着写if(rst == 1'b0),这样写是会报错的。

 

always @(posedge clk or posedge rst) begin
    if(rst == 1'b0)begin
        c <= 1'b1;
    end
    else begin
        c <= b;
    end  
   
         
end

 

复位信号

如上代码和报错,vivado也不知道他应该是生成一个高电平复位还是一个低电平复位的电路了,所以他报错了。

关于异步复位还是同步复位可以参考UG949中的描述:

复位信号

复位信号

复位信号

  审核编辑:汤梓红
 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分