EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
分频电路' \) L) v/ P ]. I" j
3 X$ f, c. L. a( ?/ s1 w
第一次写技术性的blog,就先选择一个看似简单的话题吧。 无论从算法上还是逻辑上,这个题目都非常简单,但是对于ASIC工程师,恐怕却是一个不小的挑战。 首先,看看我们的目标:
![]()
很简单吧,只要在响应的输入时钟沿上产生输出的翻转就可以了。但是对于ASIC工程师,却有很多东西是值得讨论的。 1.行为级的实现是非常简单的,只要你会写C,就可以简单的用verilog写出来,甚至连语法都基本一致: always@( negedge resetn or posedge clk or negedge clk) begin if (resetn==1'b0) begin counter[2:0]<=3'd0; out_clk<=1'b0; end else begin if (counter[2:0]==3'd5) begin counter[2:0]<=3'd0; end else beign counter[2:0]<= counter[2:0]+1; end //////////////////////////////////////////////////// if (counter[2:0]==3'd5 || counter[2:0]==3'd2) begin out_clk<=~out_clk; end end end 不要不相信这种写法,现在甚至还有一些书仍然有类似的范例。其仿真结果也确实是正确的
![]()
但是如果你只是想做一个测试仿真用还可以,要是想做真正的芯片那会被人嘲笑的。因为这个逻辑在现有的条件下无法综合。没有支持2个时钟沿的寄存器。所以这个写不对不是因为逻辑问题,而纯粹是现在的半导体工艺问题,也许以后会有支持双沿的寄存器。 2.下面给出一个逻辑图,很值得回味的。
这个逻辑是用器件画出来的,所以肯定不存在物理上的实现问题。但是其工作过程在逻辑上却有些让人头痛。如果你的数字电路学的很扎实,可以写一下方程来验证一下。在实际的实验中我相信它也80%以上是工作的。 仿真图如下:
![]() 也许有人要问,既然仿真都是对的,那么为什么要说实际中是80%工作呢? 看到波形图上的clk1(黄色)上的那些毛刺了么,毛刺并不可怕,但是这个电路工作的基础却是那些毛刺,准确地说,那些毛刺是必须有的,是工作过程的比不可少的部份。这样的电路是否能正常工作就很让人匪夷所思了。 我们能不能让电路的正常功能不依赖于毛刺呢? 小结一下: 以上的思路都是试图在输入的clk上做改造,试图在恰当的地方取正沿,恰当的地方取反沿。但是要知道,这一定会导致竞争和冒险。虽然逻辑上是可性的,但是实践中却没有那么简单。这个时候,我们需要调整一下思路了: 能不能营造一个安全的时机来切换时钟沿的选择?显然,这需要在切换时钟沿时,强制时钟输出固定电平,当切换完成后,在取消这个强制条件。对么?我们来试试看。 3. 看看以下的代码,也是一段有趣的东西。 always@(negedge resetn or posedge clk)$ g x" N$ R4 K* o& \ C- N
begin) e3 c0 P5 M/ Y( X
if (resetn==1'b0): ^# Z$ d* g! Y+ E# j
begin# s# N' e X- ]* J
cnt1[1:0]<=2'd0;
. a2 `* I6 }- v9 `- b9 S end # G! ?6 k) B# t
else+ }2 m2 m' e' \( e
begin
# H: [4 j) } R if(cnt1[1:0]==2'd2)
, B- ?* @0 A/ l* [ begin9 L, M7 `: \0 a0 M& f+ [, E% V( h1 O
cnt1[1:0]<=2'd0; " Y. Z; G1 B3 H6 ?, T+ b; R# a1 A
end
- v" J# p3 H" f else
6 Z! _3 ]& G$ j* R begin- ~+ `" D3 ]- W' a' c C
cnt1[1:0]<=cnt1[1:0]+1; ! s1 a" ]4 Y9 p$ p
end
0 f0 d% c2 \! r2 a% f% F. ?# G9 F1 v end, q5 n% y4 O) E$ P
end always@(negedge resetn or negedge clk)
2 N' g+ k' f" S8 Pbegin2 }0 D a+ m7 o$ n
if (resetn==1'b0)
t7 a- n& |, `! g begin
$ M- O$ j, E% v# a7 B1 ^ cnt2[1:0]<=2'd0;
( M/ O7 I+ g) O+ w2 M9 N end
7 @" O7 y' B; n else+ L! b W& p7 f
begin8 ?; @0 v# `( F! t- Y5 i5 }* c' R
if(cnt2[1:0]==2'd2)$ `9 M! R1 J9 z
begin
s+ O. V5 g: r0 m) f. }$ n cnt2[1:0]<=2'd0;
% h- t! j6 [* X# Q7 r& p end
) C, ` n4 k b1 O6 m" t else
, ]* L& \% V0 h* b+ W begin
- H+ A3 H- H, G7 n2 M cnt2[1:0]<=cnt2[1:0]+1;
( @! R1 \' r0 g. B: h, ]; l end
+ `; }3 m$ n; [ L4 T* W end! C+ V4 E' v1 s; A' C
end always@*
* f. O. V5 e5 T7 n9 Kbegin
& i+ r9 X ~3 X: J if (cnt1[1:0]==2'd2 || cnt2[1:0]==2'd0)
- L. \+ R+ V# {- |1 U4 P0 K( V begin# N" w1 V& D+ x8 X. h% z
clk1 = 1'b0;4 ~! R. P' X/ {/ U/ }1 J! S( w
end5 f* y; {( T; a& }
else if (cnt1[1:0]==1'b1)
- T i; U+ x# h' Q) v! {/ ] begin
+ C" r7 H+ l) n* A, f9 g clk1 = ~clk;5 U7 _0 c/ M; Q6 }8 ?1 c0 N+ K9 X
end& s2 c2 ~- ~9 n1 X# i( w
else
2 X, x6 Q! H( h* L! S! b begin( A1 s. J8 r: W, K
clk1 = clk;6 t# v1 Q+ Q& S0 [2 o) k u# g
end
' b' Z8 z" b2 i5 `. R B7 r8 z% ]end always@(negedge resetn or posedge clk1)
5 G1 x2 }: O4 t- [begin9 m& f, f4 Q6 P5 u O
if (resetn==1'b0)! D1 Z5 Z e2 m) ?1 ~% E6 u+ N
begin
, H% I* Z" R; D/ @- w: |' S" p clk_out<=1'b0;
, p- |2 g5 F2 Q5 ^9 f end# y# j4 Y5 }8 _: V5 d
else- d6 f5 y1 P1 x' B
begin( G# M) {! O1 A# S# ^
clk_out<=~clk_out;
4 Z2 A" [3 m( N1 k, ]9 C7 r end
) s4 A/ e0 E [end 最后用的时钟clk1是clk和clk的反,但是在切换之间加上的强制为0的逻辑。 ![]()
这段代码肯定是可综合的,而且简单的约束一下时序就可以生产。 但重要的是看了这段逻辑后我们突然明白了一件事情:要想长生质量好的3分频时钟,我们似乎必须要用到clk的下降沿来做点控制逻辑,这是被反复求证后逼出来的(不知道其它人是否有同感)。那么我们为什么不愿意用下降沿触发的的寄存器呢?因为有些库里可能没有这样的器件,而必须在时钟树上上加反向。如果有下降沿的寄存器,那么今后的扫瞄链又要多一些麻烦,虽然这些麻烦都可以客服,但是作为一个成熟的工程师要明白:尽量不要给自己找麻烦。在工程上,最平常的东西最可靠。 4. 到这里我们的命题似乎解决了,但是我们的思考还不应该停止。既然我们要用clk的下降沿,呢就完全可以打破以前的思路,来看看这个新的手段能给我们带来什么。 always@(negedge resetn or posedge clk)
# n! ?& W* e4 Z4 Wbegin! V* [8 |2 N6 V
if (resetn==1'b0)
) L: H/ S2 |! | R5 O( a; D- a" @ begin
4 P7 r0 _4 ~ x$ M7 G1 `# c' l& s cnt1[1:0]<=2'd0;
' R$ d2 a% ~/ L/ {. X, \ clk1<=1'b0;
+ A2 z2 q0 }" J end
4 m2 J% a+ F8 x3 K9 L# `* `( W else2 d) g! K7 o& F. i
begin- p8 A' o' `- W& c
if(cnt1[1:0]==2'd2)/ z4 t6 w$ ^0 r$ A* D
begin
3 P" w2 c' ?! f4 {0 t. Z1 X) @- U0 \ cnt1[1:0]<=2'd0;
% {0 l7 F: \: f5 B% f clk1<=1'b1;$ [+ R3 S+ r. D; e e6 b
end
( V5 i" P3 U9 V" p else
( q5 G! L% H8 {% z9 ^* B" E3 P begin* h" g0 R) R% s: g. w Y
cnt1[1:0]<=cnt1[1:0]+1; 2 A7 }+ }# B) p9 C' C" M- d
clk1<=1'b0;; n& d( A4 x2 I. V' J
end; g- r# ]. ~ J! O
end' u* p2 K3 F( i6 m; f+ \9 }
end always@(negedge resetn or negedge clk)# C" j: r. Z1 A- ~" u2 T# _0 Q5 j
begin
& [( |) q4 }) a8 `( r. n- Q if (resetn==1'b0)
' ]/ p/ c' c8 E; U) k% Y0 {9 h* l+ p begin( v& H& ^/ J; S
clk2<=2'd0; 6 J7 F; C: j2 M1 S1 A( a; B a
end 3 A4 g) M- p. j9 \
else* J# P2 @; @! g9 p s8 |( p
begin
: A4 i! F: L+ R' Q7 T clk2<=clk1;: _0 F8 @ Q2 |2 J2 @
end4 U- J8 k. j" L4 q8 X* J2 n Q
end assign clk_out = clk1 | clk2; 代码似乎更简单了,思路也完全变了。这回的思路是:先做一个简单的3分频电路,然后再调整占空比。以这个思路,所有的1/N分频都可以解决。 很高兴么?其实是有点失落。我们费了很大的周折,最后的结果竟然是这么传统的分频方法(后加一些调整),如果早知道是这样恐怕都没有人愿意浪费时间来就此问题专门的讨论。 但是我们对结果满意么?似乎有点疑虑。因为我们更喜欢寄存器做最后的输出,而对组合逻辑输出总是觉得不那么妥当。至少我的内心一致有这样的情节。 5. 最后的总结: 写这个东西并不是真的要讨论3分频电路,至少对于我来说不是,我只想说明一个ASIC工程师在面对一个问题时,从理想到现实到现实的挣扎过程。只有每天都经历这个过程的人才明白ASIC工程师的含义和价值。 有时会有人问我做ASIC前端好好事后端好,我不能答。我比较不出好坏,但是我知道要是你想做前端工程师就要时常面对上述的思考过程,这是纯粹脑力的耗费,而结果往往让别人觉得很简单,所以有时你还要承受这样的不平衡。因为对于一个没有真正经历过ASIC工程的人,你耗费的脑力和解释都是苍白的,而这个人很可能就是你的老板。 / D% I C* w' ?. j% a. H
, i! T( y6 n5 m# h |