施主们辛苦了,学习语言一贯是一个苦差事。学 Verilog 还算好的,到底还是人工语言,有规律,还有《数字电子技术》这门课垫底(没学好这门功课的同学 ---- 学好与否,与您老的考试成绩无关 ---- 赶快回去找本教材好好看看)。如果是学习自然语言,那就更麻烦了。除了记忆还是记忆,这就是学习的规律,没有捷径的。
前面讲解了组合逻辑的事情,从今天起老衲来说说时序逻辑的端倪。
在数字电路理论中,时序逻辑电路是指电路任何时刻的稳态输出不仅取决于当前的输入,还与前一时刻输入形成的状态有关。 这跟组合逻辑电路相反,组合逻辑的输出只会跟目前的输入成一种函数关系。 换句话说,时序逻辑拥有储存器件(内存)来存储信息,而组合逻辑则没有。换句话说,是否存在存储器件是判断一个电路是否是时序逻辑电路的关键。
时序逻辑按照其中信号变化的时机,又分为同步时序和异步时序两种。
同步电路是一种由定时器产生的时钟信号驱动的电路。在一个同步电路中,它的各个期间中,逻辑信号的每一个变化都是同时的。所有信号变化都服从一个特别的同步信号,称为“时钟”。不管逻辑的规模有多大,都假设对于时钟信号而言,其他逻辑部分的变化的时延是微小的。所以,整个电路的行为 -- 无论任何一个信号—都可以在任何速度上精确地被预见。在实际电路中,时钟源到达各个触发器的网线长度不同,因此需要使用时钟缓存单元来尽量降低时钟偏移的影响。
异步电路逻辑更加体现逻辑的本质,但是由于它的弹性关系,其也是设计上困难度最高的。 最基本的储存组件是锁存器。 锁存器可以在任何时间改变它的状态,依照其他的锁存器信号的变动,他们新的状态就会被产生出来。 异步电路的复杂度随着逻辑级数的增加,而复杂性也快速的增加,因此他们大部分仅仅使用在小的应用。
在一般的设计中,会采用同步电路进行设计。这是方便分析和设计的要求,一般设计人员的大脑只能把握这样的范围了。在自己可以控制的范围内设计,这是一个行之有效的策略。
很多情况下,目前大规模的数字电路系统里面,对应不同功能部分,可能存在多个时钟。但是,每一个部分一般由一个时钟驱动,这个范围内可以视为同步电路。但是,因为不同时钟的频率与相位的差别,不同部分之间的信号有无法满足“逻辑信号的每一个变化都是同时”的要求。这是一种特殊的,但是常用的异步电路逻辑。对于这种电路后文书将会详细介绍,但是这种电路的分析过程倒是更类似于同步逻辑。
1. 时钟启动,节拍控制
在时序电路里面,再强调时钟的作用也不为过的。一般而言,在一个系统里面只会采用一种类型的器件。这样设计起来简单,而且不会存在不能实现的情况。最常用的器件呢,就算是 D 触发器了。这个器件使用起来,类似于对于信号的采样,最简单不过。
D 触发器的电气符号里面,一般常用的有三组输入信号:数据输入 D、复位 / 初值输入 S 和 R 以及时钟信号 CLK。忽略掉复位 / 初值输入 S 和 R 的影响,D 触发器的真值表如表 1。其中,Qnext 为下一个有效时钟沿的 Q 的值。从信号处理的角度,可以将 D 触发器理解为在时钟有效沿对于输入采样,并且延迟一个时钟节拍输出的单元。D 触发器的时序图如图 1 所示。
表 1 D 触发器的真值表
D |
CLK |
Q |
Qnext |
0 |
↑ |
x |
0 |
1 |
↑ |
x |
1 |
x |
0/1 |
0 |
0 |
x |
0/1 |
1 |
1 |
图 1 D 触发器的时序图
正因为 D 触发器有延时的作用,有时候为了一些相关的数据到达时间对齐,会使用 D 触发器对一些先到达的数据的延时操作。注意,D 触发器能够完成的时延一定是时钟周期的整数倍。这个时候,工程上经常说的术语是“打 X 拍子”,X 是一个整数。这个严格来说就是延迟 X 个时钟周期的意思。打一拍子就用一个 D 触发器,打两拍子就用两个 D 触发器串联,以此类推。打多个拍子的代码实现方法,会在下一讲中介绍,这里仅仅给大伙儿建立一个概念。
作为一个简单的例子,老衲推荐计数器。计数器,很多人见过:大家坐飞机的时候,进门前,空姐手里拿的,见一个人“咯嗒”按一下,上面的数字加一。这个计数器,用来统计最终进入飞机的人数的,以备后用(某家就不再多说了,不能乌鸦嘴。方丈告诫我:嘴下留德,胜造七级浮屠)。这里的计数器,功能和空姐手里的差不多,只不过她那是机械的,这里的是电子的。她那里的按键,在这里是使能信号“enable”。计数器的结构如图 2 所示,其中加入了溢出保护机制。其代码如例 1,其中给出了带溢出保护和不带溢出保护的两套代码。
图 2 计数器的结构图
上图中没有给出时钟 CLK 和复位 RST 信号的处理,这是一般时序逻辑结构图的惯例。这两个信号的处理太一般了,所以被偷懒地省略了。
【例 1】计数器代码
无溢出保护 |
具有溢出保护 |
module counter ( input CLK, input RST, input enable, output reg[7:0] counter, ); //Load other module(s) //Definition for Variables in the module //Logical always @(posedge CLK, negedge RST) begin if (!RST) //Reset begin counter <= 8'h00; end else if(enable) //Counter enabled begin counter <= counter + 8'h001; end else begin end end endmodule |
module counter ( input CLK, input RST, input enable, output reg[7:0] counter, output reg overflow_flag ); //Load other module(s) //Definition for Variables in the module //Logical always @(posedge CLK, negedge RST) begin if (!RST) //Reset begin {overflow_flag, counter} <= 9'h000; end else if (overflow_flag) //Over flew and reset begin {overflow_flag, counter} <= 9'h000; end else if(enable) //Counter enabled begin {overflow_flag, counter} <= {overflow_flag, counter} + 9'h001; end else begin end end endmodule |
不具有溢出保护的功能的计数器,在实际工程中是有问题的。就按例子中 8 比特宽度的计数宽度为例,其最大计数值是 255。如果来了 256 个乘客,没有溢出保护的代码会显示计数值为 0,这显然会带来误解。
还有代码中利用了组合操作“{ }”来简化的书写,也请读者注意这个技巧。
2. 循环串接,链状传递
最简单的 D 触发器组成的电路是什么?那一定是只有一个 D 触发器的、延迟一个时钟节拍的电路了。这个老和尚上一讲已经介绍了,现在贫道在深入一步。“稍稍难一点的电路是什么呢?”参考图 3.1 里面的一般化的数字逻辑系统架构,去掉各种组合逻辑之后,就是一个非常单调的、也是很简单的 D 触发器链了。聪明!这就是这一节给大家介绍的第一种千层面:只有面。
这样的一个 D 触发器链,在信号处理上也叫作延时链,结构如图 3 所示。
图 3 延时链的结构
大家会发现只是 4 个 D 触发器的例子,代码已经很长了。那么如果是很多 D 触发器呢,例如 16 个 D 触发器或者 32 个 D 触发器,那不就成了累傻孩子的活计了吗?这里就要介绍唯一可以被综合成电路的循环了:常数循环次数的 for 循环。
关键词 for 的一般形式是:
for (variable = start_value; end_condition; circle_express)
begin
operations
end
其中,variable 是一个变量名;start_vlue 是变量的初始值;end_condition 是循环的条件;circle_expree 是每个循环的步进操作;operations 是每次循环的操作。要想这个循环能被综合,for 的循环次数必须确定,。捎带说明一下,最常见的是第一行的形式。
例 2 里面给出了 4 级触发器链用 for 实现的代码。只要改变里面的常数,就是 1024 级 D 触发器链也不难实现。
【例 2】4 级 D 触发器链的 for 实现
module DFF_link_4_for
(
input CLK, input RST,
input input_data,
output output_data
);
//Load other module(s)
//Definition for Variables in the module
reg dff[3:0];
integer loop;
//Logical
assign output_data = dff[3];
always @(posedge CLK, negedge RST)
begin
if (!RST)
//Reset
begin
for (loop = 0; loop <= 3; loop = loop + 1)
begin
dff[loop] <= 1'b0;
end
end
else
begin
dff[0]<= input_data;
for (loop = 1; loop <= 3; loop = loop + 1)
begin
dff[loop] <= dff[loop - 1];
end
end
end
endmodule
看到这里,很多人会把 Verilog 里面的 for 和其他程序语言里面的 for 混淆掉。这是一个十分严重的错误。循环变量在电路的哪里体现呢?任凭您老“眼睛瞪的像铜铃”,也找不到半分 loop 的影子吧?实话告诉您,这个循环变量是在综合里面起作用的,告诉综合软件要重复几遍 for 里面的操作。
如果大伙儿觉得一串 D 触发器只能做个时延玩玩,那就错了。这里贫道给诸位一个例子:不用 RAM 来存储一组数据。当然了,这个存储也是有代价的。数据不能像 RAM 一样随机读写,只能按照一定的时序读取。
3. 各取所长,分工合作
完成一个组合逻辑的操作,会有时延吗?这里的时延是指从输入信号稳定到获得正确结果的时间间隔。对于行为仿真,宗师和党魁说的没错,还真是没有时延的。但是到了时序仿真这里,就老母鸡变鸭了,结果里面明明白白地出现了时延。有时延倒是叫一般人感觉靠谱一点:天下没有免费的午餐。这个时延包含两个部分,也就是前文书说的可以优化的两个部分:降低布局布线造成的时延(DW)或者降低组合逻辑的时延(DC)。叫大伙儿建立一个概念:做任何事情都是要代价的,这才是天道。
回忆一下数电课程里面介绍过的 4 比特加法器链。假设:每个 1 比特的加法环节需要的处理时间是 5 ns(当然,实际电路如果这么慢,估计老夫敲一个字母计算机都要等上一分钟来处理了。这个虽然不符合实际,但是计算方便啊。大伙儿就别抬杠了。)。那么,4 个 1 比特加法器环节,一个 4 比特加法器的处理时间应该是 20 ns 了。换句话说,在 20 ns 里面如果输入输出有变化,那么结果肯定有问题。再换句话说,这个 4 比特加法器允许输入数据的变化周期大于 20 ns,对应最高的数据变化频率为 50 MHz。注意这里还都是组合逻辑,不存在时钟和时钟频率的概念。
如何能够加快数据变化的频率呢?
首先,“偷工减料”是不可取的。这位说:4 比特加法器是 50 MHz,那么 2 比特加法器就提高到 100 MHz 了。话是没错,但是这个比特位宽的减少,却不是您老说了算的。这个是一对算法什么的工程师计算、仿真的结果,减少了系统性能肯定受影响。
其次,“忍经”在这里唱不得。这位又说了:50 MHz 也不慢了,忍了吧。这话也没错,但是系统的数据输入速度,是依赖于外部要求的,也不能变。
最后,依赖于模块结构的变化以及时序控制的引入,可以解决这个问题。现在我们就开始这个过程。这里会很详细的介绍设计过程,啰嗦是啰嗦了一点,但是利于大伙儿把握和以后举一反三。忍了吧!
忽略全加器和半加器的区别,图里面一共有四个一样的 1 比特加法单元。一个简单、实用的想法是把这些加法器都隔离开,这样每次运算的时间就是 5 ns 了。那么自然而然的问题来了:用什么器件来隔离呢?又是一个简单、实用的想法:D 触发器。这个就是基本的思路了。这就是所谓流水线的思想了。
To be continued!
后面内容更加精彩,大伙儿明晚早来占位!
与非网原创内容,谢绝转载!
系列汇总: