Verilog HDL 数字系统设计及实践 第5章 时序逻辑建模
学习指南 【知识目标】 (1) 掌握时序电路的基本概念和含义; (2) 掌握如何用Verilog行为描述语句表示寄存器和锁存器; (4) 理解同步时序逻辑设计的概念; (5) 掌握多种典型时序电路的Verilog描述。 【技能目标】 (1) 熟练使用Verilog构建各种寄存器、锁存器和基本的存储单元; (2) 熟练使用Verilog描述各种同步有限状态机电路; (3) 理解同步实现逻辑设计的重要性。 【重点难点】 (1) 如何避免在设计中产生意外的产生锁存器; (2) 如何设计可综合的时序逻辑; (3) 充分理解使用同步时序逻辑进行设计的意义和优缺点。
5.1 时序逻辑建模概述 时序逻辑电路,是指在Verilog HDL所描述的电路中,包含一个或多个存储单元。 5.1 时序逻辑建模概述 时序逻辑电路,是指在Verilog HDL所描述的电路中,包含一个或多个存储单元。 这些存储单元可以是边沿触发的寄存器,或者是电平触发的锁存器。由于引入了 存储单元,时序逻辑电路具有“记忆”功能,可以记录当前时刻之前的输入激励情况 以及电路状态。因此,与组合逻辑不同的是,时序逻辑的输出同时取决于当前时刻 和以前时刻的输入。 图5.1 组合逻辑和时序逻辑共同构建数字系统
5.2 寄存器和锁存器的设计 寄存器和锁存器是时序逻辑电路中最基本的存储单元。本书中描述的锁存器和寄存器, 5.2 寄存器和锁存器的设计 寄存器和锁存器是时序逻辑电路中最基本的存储单元。本书中描述的锁存器和寄存器, 分别特指数字电路课程中介绍过的D锁存器和边沿触发的D触发器。 5.2.1寄存器设计实例 【例5.1】利用Verilog HDL设计一个简单寄存器。该寄存器在时钟信号i_clk上升沿触 发,其数据输入信号为i_din。 // example_5_1: A simple register module dff ( input i_clk, input i_din, output reg o_dout ); // 在always语句的敏感列表@()中加入边沿敏感的时钟信号i_clk always @ (posedge i_clk) o_dout <= i_din; endmodule
5.2 寄存器和锁存器的设计 寄存器和锁存器是时序逻辑电路中最基本的存储单元。本书中描述的锁存器和寄存器, 5.2 寄存器和锁存器的设计 寄存器和锁存器是时序逻辑电路中最基本的存储单元。本书中描述的锁存器和寄存器, 分别特指数字电路课程中介绍过的D锁存器和边沿触发的D触发器。 5.2.1寄存器设计实例 【例5.1】利用Verilog HDL设计一个简单寄存器。该寄存器在时钟信号i_clk上升沿触 发,其数据输入信号为i_din。 // example_5_1: A simple register module dff ( input i_clk, input i_din, output reg o_dout ); // 在always语句的敏感列表@( )中加入边沿敏感的时钟信号i_clk always @ (posedge i_clk) o_dout <= i_din; endmodule
5.2 寄存器和锁存器的设计 图5.2 example5_1代码所示电路 5.2.2锁存器设计实例 5.2 寄存器和锁存器的设计 图5.2 example5_1代码所示电路 5.2.2锁存器设计实例 【例5.3】用Verilog HDL描述一个简单的锁存器。该锁存器在控制信号i_en为高电平时开 启,为低电平时锁存当前值。 // example_5_3: A simple latch module latch ( input i_en, input i_din, output reg o_dout ); // 敏感列表中没有边沿触发的信号
5.2 寄存器和锁存器的设计 always @ (i_din or i_en) if (i_en) o_dout <= i_din; 5.2 寄存器和锁存器的设计 always @ (i_din or i_en) if (i_en) o_dout <= i_din; // 没有与'if'语句对应的'else'语句,生成锁存器,否则生成组合逻辑。 endmodule 图5.4 example_5_3代码所示电路
5.3 寄存器和锁存器的推断 5.3.1寄存器的推断 不带复位端口的简单寄存器可以由一个在always语句中被赋值的变量描述,并且该 5.3 寄存器和锁存器的推断 5.3.1寄存器的推断 不带复位端口的简单寄存器可以由一个在always语句中被赋值的变量描述,并且该 always语句的敏感列表中只包含一个边沿敏感的信号。在描述寄存器的always语句中, 应该使用非阻塞赋值(Non-blocking assignment)来给变量 赋值,以保证生成正确的寄存器 电路,并且在仿真中避免产生竞争(Simulation race)。 带异步复位或置位端口的寄存器可以由一个在always语句中被赋值的变量描述,并且 该always语句的敏感列表中包含至少两个边沿敏感的信号,但不包含任何电平敏感的信号。 此外,该always语句必须包含一个if条件语句,来指定寄存器的第一个异步赋值行为(如异 步复位、置位等等),以及可选的else if嵌套条件语句来指定额外的异步赋值行为。最后一 个else语句用于指定同步的寄存器赋值行为。异步赋值语句的输入信号通常连接到寄存器的 复位或置位端口,而同步赋值语句的输入信号则连接在寄存器的数据输入端口,即通常所说 的D端口。 5.3.2锁存器的推断 锁存器可以由一个在always语句中被赋值的变量描述,并且在该always语句的敏感列表 中应只包含电平敏感的触发信号。同时,该变量在always语句中存在有没有赋值的情况。
5.3 寄存器和锁存器的推断 在描述锁存器时,always语句中所有赋值表达式等号右边参与赋值的信号必须全部在敏 5.3 寄存器和锁存器的推断 在描述锁存器时,always语句中所有赋值表达式等号右边参与赋值的信号必须全部在敏 感列表中列出,并且应该使用非阻塞赋值(Non-blocking assignment)来给变量赋值。 // 变量q描述一个锁存器 reg q; ... always @ (enable or d) // 当enable为低电平时,q锁存当前值。 if (enable) q <= d; // 变量q不会生成锁存器,而是生产组合逻辑,因为在always语句中 // q未存在不被赋值的情况,即条件判别的所以分支都给q指定了赋值语句。 else q <= 1'b0;
5.4 存储器的设计与建模 存储器一般分为只读存储器(ROM)和随机访问存储器(RAM)。ROM存储的数据信息在电路初始化的时候获得固定值,并且在之后不能被修改,只能进行读取。ROM一般用于存储电路的配置信息。与ROM不同的是,RAM存储的信息在运行时可以被修改,可以用来存储电路工作时产生的数据信息。 5.4.1ROM建模 【例5.4】代码example_5_4描述了一个简单的ROM模型。 // example_5_4: A simple ROM model // 变量z描述了一个ROM,信号sel是ROM的地址选择信号。 module rom_case ( input [2:0] i_sel, // ROM地址信号。ROM深度为8 output reg [3:0] o_dat ); always @ (i_sel) case (i_sel) 3'b000: o_dat = 4'b1001; 3'b001: o_dat = 4'b1011;
5.4 存储器的设计与建模 3'b010: o_dat = 4'b0010; 3'b011: o_dat = 4'b0011; 5.4 存储器的设计与建模 3'b010: o_dat = 4'b0010; 3'b011: o_dat = 4'b0011; 3'b100: o_dat = 4'b1110; default: o_dat = 4'b0000; // 其它地址均输出全0 endcase endmodule 由上述电路所示的ROM模型可知,由于ROM不需要在运行时被修改,因此ROM的建 模可以通过组合逻辑完成。 5.4.2 RAM建模 RAM的建模需要用到Verilog HDL的数组变量。RAM既可以通过信号的电平变化来触发,也可以通过边沿变化来触发。 【例5.6】用Verilog HDL描述了一个电平变化触发的16位宽RAM模型。当控制信号i_we为 高电平时,RAM将输入数据存储到当前地址i_addr所指向的存储空间。
5.4 存储器的设计与建模 // example_5_6: A simple level-sensitive RAM model 5.4 存储器的设计与建模 // example_5_6: A simple level-sensitive RAM model // 变量mem描述了一个电平触发的RAM模型,即一个锁存器组。 module ram_latch ( input i_we, input [6:0] i_addr, input [15:0] i_dat, output [15:0] o_dat ); // 用数组定义一个16x128的存储器。 reg [15:0] mem [127:0]; always @ (i_we or i_dat) if (i_we) mem[i_addr] <= i_dat; assign o_dat = mem[i_addr]; endmodule
5.5 在设计中使用同步时序逻辑 对数据在各个组合逻辑间的流动进行有序控制,是时序逻辑的主要功能。 5.5 在设计中使用同步时序逻辑 对数据在各个组合逻辑间的流动进行有序控制,是时序逻辑的主要功能。 同步时序逻辑中的所有存储单元都利用一个全局分布的时钟进行周期性的同步,因此所有组合逻辑路径都在同一时刻开始进行计算,并且在经过一段相同的延迟之后,其结果值被写入到存储单元中。 图5.5 同步互连方法 5.5.1利用同步时序逻辑消除冒险 对于实际的组合逻辑,输入数据发生变化时,输入数据总要等待一段延迟才会发生变化。组合逻辑实现的功能越复杂,综合后产生的门单元就越多,则该路径的电路延迟就越大(如乘法器的延迟往往比同位宽的加法器大许多)。
5.5 在设计中使用同步时序逻辑 电路中的组合逻辑往往包含大量的分支,这些分支路径的延迟通常各不相同,这就造 5.5 在设计中使用同步时序逻辑 电路中的组合逻辑往往包含大量的分支,这些分支路径的延迟通常各不相同,这就造 成了组合逻辑冒险(Hazard)的产生。组合逻辑的冒险可以分为静态冒险(Static hazard) 和动态冒险(Dynamic hazard)。 图5.6 电路系统组合逻辑路径的不同延迟时间 图5.6所示为典型的同步实现电路结构图。组合逻辑C1、C2和C3都可能产生冒险, 但是它们的输出值分别在5ns,12ns和7n后保持稳定,并且在之后的时间里,寄存器都 可以对它们进行正确的读取。
5.5 在设计中使用同步时序逻辑 5.5.2利用流水线提高同步时序逻辑性能 5.5 在设计中使用同步时序逻辑 5.5.2利用流水线提高同步时序逻辑性能 使用同步设计,虽然可以减小设计的复杂度,但往往电路的性能比异步逻辑差。 在长延迟组合逻辑中插入寄存器来加速电路整体工作频率的方法,我们称之为流水线技术。 利用流行线技术改进图5.6所示的同步时序电路,改进后的电路结构图如图5.7所示。 图5.7 利用流行线改进同步时序逻辑电路
5.6 同步有限状态机 同步有限状态机分为两种。第一种被称为Mealy机,即状态机的输出不仅决定于状态机 的当前状态,还决定于当前时刻的输入 5.6 同步有限状态机 同步有限状态机分为两种。第一种被称为Mealy机,即状态机的输出不仅决定于状态机 的当前状态,还决定于当前时刻的输入 图5.8 Mealy状态机结构示意图 Mealy状态机的逻辑表达式可以写为: 下一状态 = F(当前状态, 输入) 输出 = G(当前状态, 输入)
5.6 同步有限状态机 与Mealy状态机不同的是,Moore机的输出只决定于状态机的当前状态,与当前输入没 有关系。 5.6 同步有限状态机 与Mealy状态机不同的是,Moore机的输出只决定于状态机的当前状态,与当前输入没 有关系。 图5.9 Moore状态机结构示意图 Moore状态机的逻辑表达式可以写为: 下一状态 = F(当前状态, 输入) 输出 = G(当前状态)
5.6 同步有限状态机 【例5.9】根据图5.10所示的状态转换图,用Verilog HDL设计有限状态机。 5.6 同步有限状态机 【例5.9】根据图5.10所示的状态转换图,用Verilog HDL设计有限状态机。 图5.10 典型的总线操作控制器状态转移图
5.6 同步有限状态机 // example_5_9: A sample bus controller FSM 5.6 同步有限状态机 // example_5_9: A sample bus controller FSM module fsm_bus_control ( input i_clk, input i_rst_n, input i_write, input i_sel, input i_ok, // 直接输出状态机的当前状态和下一个状态 output [2:0] o_stat_current, output [2:0] o_stat_next ); parameter Reset = 3'b000; parameter Idle = 3'b001; parameter Read = 3'b010; parameter Write = 3'b011; parameter Delay = 3'b100; reg [2:0] cur_stat; reg [2:0] nxt_stat;
5.6 同步有限状态机 // 状态存储器总是在时钟上升沿采集下一个状态的值。 5.6 同步有限状态机 // 状态存储器总是在时钟上升沿采集下一个状态的值。 always @ (posedge i_clk or negedge i_rst_n) if (! i_rst_n) cur_stat <= Reset; else cur_stat <= nxt_stat; // 计算下一个状态的组合逻辑F always @ (cur_stat or i_write or i_sel or i_ok or i_rst_n) begin case (cur_stat) Reset: nxt_stat = Reset; nxt_stat = Idle;
5.6 同步有限状态机 Idle: if (i_write && i_sel) nxt_stat = Write; 5.6 同步有限状态机 Idle: if (i_write && i_sel) nxt_stat = Write; else if (!i_write && i_sel) nxt_stat = Read; else nxt_stat = Idle; Write: nxt_stat = Delay; Read: Delay: if (i_ok) default: nxt_stat = Reset; endcase end // 输出组合逻辑G。该设计直接输出当前和下一个状态值,因此只需要一条线将 // 内部的状态存储器连接到模块端口。 assign o_stat_current = cur_stat; assign o_stat_next = nxt_stat; endmodule
5.7 时序逻辑建模实例 5.7.1计数器 【例5.10】设计一个4位宽的计数器,计数的初值为4’b0000。当计数值达到4’b1111时, 5.7 时序逻辑建模实例 5.7.1计数器 【例5.10】设计一个4位宽的计数器,计数的初值为4’b0000。当计数值达到4’b1111时, 计数器给出一个计数已满的触发信号o_full,并且在下一个时钟周期重新从4’b0000开始 计数。计数器还包含一个使能信号i_en。当使能信号无效时,计数器暂停计数。 // example_5_10: 4-bit counter module counter_4 ( input i_clk, input i_rst, input i_en, output reg [3:0] o_cnt, output o_full ); always @ (posedge i_clk or posedge i_rst) begin if (i_rst) o_cnt <= 4'b0000; else if (i_en) o_cnt <= o_cnt + 4'b0001;
5.7 时序逻辑建模实例 end assign o_full = (o_cnt == 4'b1111); endmodule 5.7 时序逻辑建模实例 end assign o_full = (o_cnt == 4'b1111); endmodule 图5.11 4位计数器仿真波形图 5.7.2 串并/并串转换器 要使用串行通信,则必须设计并串转换器和相应的控制器,将源模块内部的并行数据转换为连续的串行数据。而在接收方,目标模块也必须包含对应的串并转换器,将串行数据恢复为并行数据。串行通信的控制模块用来控制什么时候启动串并/并串转换器,以及如何同步传输的数据。
5.7 时序逻辑建模实例 例5.11】分别设计8位的串/并和并/串转换器。当源模块使用并/串转换器,目标模块使用 5.7 时序逻辑建模实例 例5.11】分别设计8位的串/并和并/串转换器。当源模块使用并/串转换器,目标模块使用 串/并转换器时,源模块的8位并行数据可以通过串行通道传输到目标模块,然后在目标 模块中恢复为8位的并行数据。并行数据的最低位将作为串行数据的第1位进行传输。 // example5_11_a: 8-bit parallel to serial converter // 该代码用于源模块进行数据发送 module paral_to_serial ( input i_clk, input i_rst_n, input [7:0] i_pdata, input i_en, output o_sync, // 同步信号,有效时表示现在传输的是数据的最后1位 output o_sdata ); reg [2:0] ps_cnt;
5.7 时序逻辑建模实例 always @ (posedge i_clk or negedge i_rst_n) 5.7 时序逻辑建模实例 always @ (posedge i_clk or negedge i_rst_n) if (! i_rst_n) ps_cnt <= 3'b000; else if (ps_cnt == 3'b111) else if (i_en) ps_cnt <= ps_cnt + 3'b001; assign o_sdata = (ps_cnt == 3'b000) ? i_pdata[0] : (ps_cnt == 3'b001) ? i_pdata[1] : (ps_cnt == 3'b010) ? i_pdata[2] : (ps_cnt == 3'b011) ? i_pdata[3] : (ps_cnt == 3'b100) ? i_pdata[4] : (ps_cnt == 3'b101) ? i_pdata[5] : (ps_cnt == 3'b110) ? i_pdata[6] : (ps_cnt == 3'b111) ? i_pdata[7] : 1'b0; assign o_sync = (ps_cnt == 3'b111); endmodule
图5.13 代码example5_11_a仿真结果波形图 5.7 时序逻辑建模实例 图5.13 代码example5_11_a仿真结果波形图
5.7 时序逻辑建模实例 【例5.12】设计一个偶数倍时钟分频电路。该电路将输入时钟进行分频,产生一个相位 5.7 时序逻辑建模实例 5.7.3时钟分频电路 【例5.12】设计一个偶数倍时钟分频电路。该电路将输入时钟进行分频,产生一个相位 与源时钟相同,周期是源时钟8倍的新时钟。 // example_5_12: Even frequency divider to divide clock by 8. // 该电路实现输入时钟的8分频,输出时钟占空比为50%。 module even_freq_div ( input i_clk, input i_rst_n, output reg o_clk ); reg [1:0] cnt; always @ (posedge i_clk or negedge i_rst_n) if (! i_rst_n) cnt <= 2'b00; else
5.7 时序逻辑建模实例 cnt <= cnt + 2'b01; 5.7 时序逻辑建模实例 cnt <= cnt + 2'b01; always @ (posedge i_clk or negedge i_rst_n) if (! i_rst_n) o_clk <= 1'b0; else if (cnt == 2'b11) o_clk <= ~o_clk; endmodule
思考与练习 1.简述组合逻辑电路和时序逻辑电路的区别,以及各自在电路中所起的作用。 2.时序逻辑中的寄存器也可以用门级描述来构建,实例化预定义门电路时可以给门单元指定延迟,如: and #10 ad1(out, opa, opb); 表示实例化一个带延迟的与门。与门的任何输入端口到输出端口的延迟都是10个仿真时间。利用带反馈的组合逻辑,可以构建出带时序功能的电路。请用门级描述设计一个1位宽的锁存器,并通过仿真来观察用门级描述和用行为描述构建的锁存器有何异同。如图X5-1所示为用与非门设计D锁存器的电路结构图。 图X5-1 D锁存器门级电路图
思考与练习 发器(即本书中提到的寄存器)。并通过仿真比较用门级描述和用行为级描述的寄存器有 何异同。可参考图X5-2。 3.请用2个上题描述的D锁存器,利用Verilog的层次化描述方法,构建一个边沿触发的D触 发器(即本书中提到的寄存器)。并通过仿真比较用门级描述和用行为级描述的寄存器有 何异同。可参考图X5-2。 图X5-2 边沿D触发器门级电路图 4.利用Verilog行为描述,设计一个8位宽的、带异步复位端口的寄存器。复位信号高有效。寄存器为时钟下降沿触发。 5.请问下面的代码片段是否能综合成寄存器?为什么?
思考与练习 ... wire out; always @ (posedge clk or posedge rst or negedge set_n) if (rst) out <= 1'b0; else if (set_n) out <= 1'b1; else out <= in; 请用Verilog描述一个容量为32KB的RAM,该RAM的数据宽度为32位。RAM为时钟上升沿触发,且读/写数据用不同的端口。 7.请将习题6中的RAM改写成读/写数据复用同一端口的RAM。 8.简述同步时序逻辑设计的概念及其优缺点。 9.简述问什么利用同步设计可以有效消除组合逻辑的冒险。同步时序逻辑电路中全局时钟的最大频率主要由什么因素决定?
图X5-3 带猝发传输模式(Burst)的总线控制状态机 思考与练习 10.简述利用流水线技术提供同步设计电路速度的原理。 11.根据图X5-3的状态转移图,用Verilog HDL设计一个同步有限状态机电路。 图X5-3 带猝发传输模式(Burst)的总线控制状态机
思考与练习 12.该状态机除了输出当前状态和下一状态值以外,还输出32位的haddr和32位的hwdata 信号。复位后这两个信号均为全0。当状态机进入Addr状态时,haddr被赋值为0x00000100。 当状态机进入Write或BurstWrite状态时,hwdata的被赋值为0x5555aaaa。当状态机处于 BurstRead或者BurstWrite状态时,haddr的值在每个时钟上升沿自加1。进入Resp状态, haddr和hwdata保持当前值。返回Idle状态时,haddr和hwdata的值被清零。 13.利用有限状态机设计一个前导码检测电路。该电路有一个1位宽的数据输入端口i_serial。 该端口的值在时钟i_clk的上升沿变化。检查电路在时钟的每个上升沿采集i_serial的值。 当i_serial的值出现逻辑序列10011时,检查电路在输出端口o_detect输出一个时钟周期 的高电平。 14.设计一个8位宽的降序计数器。计数器复位后从8’hff开始往下计数。当计数到8’h00时, 计数器在输出端口o_full输出一个时钟周期的高电平。该计数器还包含一个重新载入计数 器值的输入信号i_load。当i_load信号有效时,计数器的当前值恢复为8’hff。 15.改进习题12中的计数器,添加一个8位宽的数据输入端口i_dat。当i_load信号有效时, 计数器从i_dat端口加载新的计数值到内部寄存器。 16.设计一个占空比为50%的16倍分频电路,电路输出时钟的周期是输入时钟周期的16倍。 17.设计一个占空比为50%的11倍分频电路。并使用综合工具进行综合。