众所周知,我们在IC设计中采用异步复位的方式,那么异步复位寄存器在你的RTL下它到底长什么样子呢?
为此我找到一篇知乎前辈的文章,进行精选,下面详细的了解一下异步复位异步置位寄存器的生成以及结构和相关的timing约束如何进行。
verilog语法中always@(posedge clk or negedge clk)这种语句是我们最常用的语句之一。
IC设计中一般我们的写法是:
always @ (posedge clk or negedge resetn)begin
if(~resetn)
q <= 1'b0;
else
q <= 1'b1;
end
这里需要说明的是 always block块中
1:必须要有if 条件语句,否则dc 工具会报错,
2:if条件判断语句中的信号必须在always块的敏感信息列表中
3:if中条件如果为(resetn),则会报错,因为在敏感列表中写的是negedge resetn,如果敏感列表中为posedge resetn,则不会报错。
4:敏感信号列表中要么都是边沿触发信号,要么都是电平触发,不能混合。
规避掉以上问题,上面代码框中的RTL综合后你就会得到一个如下所示的寄存器:
这是一个带反相器的异步复位寄存器结构。
那么加入我把always块中的negedge resetn改为posedge resetn,综合出来的寄存器会是什么样子?
看,反相器没了。
此时我们可以暂时性的得出一个结论,异步复位寄存器都是high active。
我们继续往下看:
always块中不仅可以控制异步复位还可以控制异步置位。在敏感列表中添加更多的trigger signal,可以让dc产生带有异步置位pin的DFF。
TSMC的lib中,异步置位的pin都是SDN,low active,因此下面的电路图都是以SDN表示异步置位管脚。
always @ ( posedge clk or negedge resetn or negedge setn ) begin
if ( ~resetn ) q <= 1'b0;
else if ( ~setn ) q <= 1'b1;
else q <= d;
end
这是标准的同时带有异步复位和异步置位的verilog代码,综合后的电路如下:
DFF的异步复位输入pin是CD,它是high active,而异步置位输入pin是SDN,是low active,verilog代码使用的resetn和setn都是negedge trigger,因此异步复位会多出来一个INV cell,而SDN就不会。
如果把set_n放在rst_n前面,依然能综合成功,此时set_n在if条件中有更高的优先级,而且dc工具综合后的电路会多出来一些cell,用于判断setn优先级高于resetn。
always @ ( posedge clk or negedge resetn or negedge setn ) begin
if ( ~setn ) q <= 1'b1;
else if ( ~resetn ) q <= 1'b0;
else q <= d;
end
综合后的电路如下:
在TMSC的doc目录中找到描述cell的文档,能发现其实TSMC对CD和SDN是有要求的,要求CD和SDN
不能同时有效
,如下图中所示:
同时,对应的verilog model中,找到DFF对应的primitive原语,发现CD比SDN的优先级更高。如图中所示。
dc工具也认为异步复位比异步置位的优先级高,当verilog代码中把resetn放在前面时,~resetn和setn会被连接在CD和SDN上,而当代码中把setn放在前面时,综合后的电路会多出来一些cell,用于判断setn优先级高于resetn,如上述图中所示。
还要说明的一点是,always语句的敏感列表中的信号数不局限于2个或者3个,而是可以有更多,只要他们全都是edge trigger即可,那么假如在RTL时,我设置两个异步复位信号,综合以后的电路会是什么样子?
下面就举个例子简单的说明一下:
always @ ( posedge clk or negedge resetn or negedge setn or posedge resetp or posedge setp ) begin
if ( ~resetn ) q <= 1'b0;
else if ( resetp ) q <= 1'b0;
else if ( ~setn ) q <= 1'b1;
else if ( setp ) q <= 1'b1;
else q <= d;
end
调整4个signal的先后顺序,他们的优先级也随之改变,也会产生不同的电路结构,就不细说了。
接下来我要说的是,如何在sdc中对异步复位寄存器的复位端口和置位端口进行约束。
在说这个约束之前我们首先要了解一个概念,那就是时序弧(timing arc)什么是时序弧?
关于时序弧的理解:
(8条消息) 时序弧timing arc_动次打次小飞龙的博客-CSDN博客_时序弧和时序路径
总结来说即timing path上描述两个节点延时信息的数据,STA是基于timing arc数据的时序分析
以上述异步复位寄存器来说,我们要得到异步复位寄存器的SDN端到CD端的timing arc。
get_timing_arc -from cell/SDN -to cell/CD
get_timing_arc -from cell/CD -to cell/SDN
这样就get到了cell 复位和置位的时序弧。
然后我们再来了解一下set_false_path和set_disable_timing的区别:
(这些约束语句都是我们平时最常见的一些语句,但是我们往往忽略他们之间细微的差别)
set_false_path:
是用来设置timing_path,表示不用check这些path的timing,但是依旧会去计算这些path上的delay;
set_disable_timing:
是用来设置timing arc,表示打断这条时序弧,不去计算这段时序弧的delay,并且所有经过这段时序弧的path都会被打断。
举个例子,假设我们要打断上述异步复位置位寄存器之间的timing arc,那么在sdc中我们应该如何去约束呢?
set_disable_timing [get_timing_arc -from cell/SDN -to cell/CD]
set_disable_timing [get_timing_arc -from cell/CD -to cell/SDN]
这个理解起来很直接,先get到这个时序弧,然后再disable掉。
//——————————————————–分割线——————————————————–//
//——————————————————–分割线——————————————————–//
在这里顺表整理一下sdc约束中碰到的一些语法:
append_to_collection
xxx_reg_lst [get_cells u_xxx/u_yyy/u_aaa/u_bbb/abc_reg_*]
append_to_collection
xxx_reg_lst [get_cells u_zzz/u_ddd/u_ccc/u_eee/lmn_reg_*]
,,,,,,
append_to_collection(译:附加到集合)它的作用和add_to_collection类似,可以创建一个新的集合,把第二个集合放在第一个集合的尾部,但是它会改变第一个集合本身。
get_cells,get_pins,get_clocks
等get_*命令返回的是一个集合,我们把它称之为collection。
注意:get_clock,get_cell,get_pin 得到的是具体的对象
eg:dc_shell > get_clock gdc_wrap_aclk
{gdc_wrap_aclk}
get_attribute:
得到目标的某种属性
get_attribute [get_clock gdc_wrap_aclk] peroid
foreach_in_collection
var_name :
指定变量的名字
$collection:
指定集合的名字
{ body }:
指定要执行的语句
这个命令就和普通tcl语法中的
foreach
一样,可以遍历集合中的每个单元。
foreach_in_collection r_cd_sdn $ xxx_reg_lst {
set reg_cdn_sdn [get_object_name $r_cd_sdn]
set_disable_timing [get_timing_arc -from $reg_cdn_sdn/CD -to $reg_cdn_sdn/SDN]
}
set cdc_clock [get_object_name [get_clocks]]
foreach launchclk $sdc_clocks {
foreach captureclk $cdc_clocks{
set capture_period [get_attribute [get_clock $captureclk] period]
set launchclk_period [get_attribute [get_clock $launchclk] period]
if { $capture_period < $launchclk_period} {
set period [expr $capture_period * $cdc_ratio]
} else {
set period [expr $launchclk_period* $cdc_ratio]
} else {
echo "captureclk equals captureclk"
}
}
}
get_object_name :
dc_shell > get_object_name [get_clocks]
xxx_aclk xxx_vclk xxx_wrap_vpclk
这个命令可以返回指定集合中的object名字,比如上面我们提到的get_cells,get_pins等get_*命令都是返回一个集合,我们称之为collection。
collection是无法返回具体的名称的,如果你要抓取具体的名称,可以通过该命令将collection转化为名称。
remove_from_collection:
该tcl语法的用法一般形式为:remove_from_collection
[get_ports { A1 A2 A3 A4 B7 C8}] [get_ports {A1 A2}] 意思是在 { A1 A2 A3 A4 B7 C8}这个集合中把 {A1 A2}删除掉。
这里要注意一个点 dc_shell中对
多个object
进行get操作一定要
使用 { } 将object括起来
,dc_shell才能找到,否则找不到。
注意的第二点两个[get_port] 之间一定要用
空格隔开
,否则也会报错。
这里多说一句:
dc_shell 一定要注意pin(内部)和port(端口)的区别,如果预期找的是端口,但是使用get_pin那一定是会报错找不到的。
如何找一个port所在的时钟域:
方法1:get_attribute [get_pin xxx_reg/CP] clocks
方法2:report_timing -from [get_port xxx_name ] //对输入信号检查
report_timing -to [get_port xxx_name ] //对输出信号检查
如何得到某一个时钟的属性:
首先我第一反应是时钟有什么属性?然后我们带着问题来看这个命令得到的结果。
get_attribute [get_ports xxx_clk ] clocks //可以得到该时钟的属性
属性是啥:即它的源时钟是谁,它和谁在sdc中被设置在同一个group中。这就是clock的属性。
eg:dc_shell>
get_attribute [get_port tdnr0_wrap_vpclk ] clocks
{VCLK_1000M_EXTEND vi_sys2_vclk vi_sys1_vclk}
如何得到某一个pin 的fanout是多少:
all_fanout -from [get_pin u_vi_func_middown/isp2_I_main/Genericpipe/u_7c15_reg_65_/CP] -flat -endpoints_only
这里要注意一点,要get的pin的hier要用 / 的形式,找该pin 连接的DFF的CP端,因为timing path一般都是从DFF的CP端到下一个DFF的D端。不要忘记
-endpoints_only