第8章Verilog设计深入 教学目的:在掌握Verilog基础语法的基础上,深入学习阻塞与非阻塞赋值 的本质区别,使学生真正建立硬件思维:理解Verilog中的高阻态描述,并学会 利用高阻态实现三态及双向数据总线。 教学基本要求:在掌握Verilog基础语法的基础上,进一步提高对部分关键 语法特质的理解,尤其是2种赋值方式的区别和双向数据总线的实现、过程中的 两类赋值语句。 教学内容:过程中的两类赋值语句、过程语句应用总结、不完整条件语句与 时序电路的关系、f语句归纳、三态与双向端口设计。 教学提示:非阻塞赋值在实际运行中仍然是串行执行的,但由于它的表达式 计算与操作数更新分开进行,导致了最终运算结果等价于并行执行,这是其与阻 塞赋值的本质差异。 学法指导:借用信号与系统中的冲激函数概念,可以有效的帮助学生理解非 阻塞赋值的实质。 作业:如何利用阻塞赋值达到非阻塞赋值的效果? 小结:这一章内容并不多,但对切实提高学生的编程能力十分关键,尤其是 阻塞与非阻塞赋值,这是Verilog语言中的著名难点,也是其特有的语法现象, 甚至许多具有多年经验的FPGA工程师也未能完全理清该问题。初学者更是对并 发执行的特性难以接受。事实上,借用冲激函数概念,可以更好地帮助学生理解 非阻塞赋值的实质。 详细大纲 8.1两种赋值语句 Verilog的著名难点:阻塞与非阻塞赋值,阻塞赋值英文是un-blocking assignment,它可以出现在两种场合,一种是Assign语句,一种是always块内部, 其中assign语句只能使用非阻塞语句,这是语法决定的。但always块内,则是 即可以是阻塞赋值,也可以是非阻塞赋值。 阻塞赋值的执行分成三步:首先是计算右边表达式的值,然后是赋值给左边 操作数,最后完成赋值,只要赋值语句中没有延时语句,这三个步骤不能分开 旦开始赋值就要完成操作,目标变量的值即刻更新。阻塞赋值的这种执行过程, 1
17 第 8 章 Verilog 设计深入 教学目的:在掌握 Verilog 基础语法的基础上,深入学习阻塞与非阻塞赋值 的本质区别,使学生真正建立硬件思维;理解 Verilog 中的高阻态描述,并学会 利用高阻态实现三态及双向数据总线。 教学基本要求:在掌握 Verilog 基础语法的基础上,进一步提高对部分关键 语法特质的理解,尤其是 2 种赋值方式的区别和双向数据总线的实现、过程中的 两类赋值语句。 教学内容:过程中的两类赋值语句、过程语句应用总结、不完整条件语句与 时序电路的关系、if 语句归纳、三态与双向端口设计。 教学提示:非阻塞赋值在实际运行中仍然是串行执行的,但由于它的表达式 计算与操作数更新分开进行,导致了最终运算结果等价于并行执行,这是其与阻 塞赋值的本质差异。 学法指导:借用信号与系统中的冲激函数概念,可以有效的帮助学生理解非 阻塞赋值的实质。 作业:如何利用阻塞赋值达到非阻塞赋值的效果? 小结:这一章内容并不多,但对切实提高学生的编程能力十分关键,尤其是 阻塞与非阻塞赋值,这是 Verilog 语言中的著名难点,也是其特有的语法现象, 甚至许多具有多年经验的 FPGA 工程师也未能完全理清该问题。初学者更是对并 发执行的特性难以接受。事实上,借用冲激函数概念,可以更好地帮助学生理解 非阻塞赋值的实质。 详细大纲 8.1 两种赋值语句 Verilog 的著名难点:阻塞与非阻塞赋值,阻塞赋值英文是 un-blocking assignment,它可以出现在两种场合,一种是 Assign 语句,一种是 always 块内部, 其中 assign 语句只能使用非阻塞语句,这是语法决定的。但 always 块内,则是 即可以是阻塞赋值,也可以是非阻塞赋值。 阻塞赋值的执行分成三步:首先是计算右边表达式的值,然后是赋值给左边 操作数,最后完成赋值,只要赋值语句中没有延时语句,这三个步骤不能分开, 一旦开始赋值就要完成操作,目标变量的值即刻更新。阻塞赋值的这种执行过程
主要是继承了软件编程语言的顺序执行语句。 而非阻塞赋值是Verilog特有的一种赋值方式,它是为了适应硬件电路的运 行特点而产生的。它的执行也可以分成三个步骤:运算、赋值、赋值完成。但是 它在运算完成后,就不再继续执行,而是执行另外的同类型非阻塞赋值语句的运 算操作,在所有非阻塞赋值语句的运算操作都完成后,然后再统一进行赋值操作 (即所谓并行执行理念)。最后全部赋值完成。 以教材上例5-1和例5-2为例,分析二者的区别: 首先假定A,B初值都是零,然后在同一时刻变成1,对例5一1,顺序执行 阻塞赋值,因此M1M2Q都是1,对例5-2,它采用的是非阻塞赋值,在A、B 没有变化前,按照代码书写内容,M1、M2、Q都为零,而在变化的时刻,这三 条语句会同时开始计算表达式的值,可以看到,对M1的赋值,计算结果为1、 M2的,注意它开始计算时M1还没有更新,因此这时M1仍为原值零,M2赋值 为零,同样,Q赋值M1和M2的或,但此时M1和M2都没有更新,因此最终 Q也为零。 如果是在时序电路中,阻塞赋值与非阻塞赋值的差异更大,它会直接导致综 合后电路的明显差异。 以下分析时序电路中采用两种赋值的对比: 首先分析非阻塞赋值,它定义了三个寄存型变量,a,b,q,按照非阻塞赋值的 执行过程来分析,在一个时钟沿到来时,过程被激活运行,三条语句同时计算表 达式的值,然后一同更新。首先在上升沿时刻,D的值被a寄存,这对应硬件电 路上的什么结构?1个D寄存器,然后呢,下一条语句是b<=a,注意这时候ā 的值还没有更新,因此,相当于把a的老值寄存给b,这又对应一个寄存器,同 样地,Q<b也对应一个寄存器。 因此,我们从整体的角度看,这样的非阻塞赋值代码最终综合后是三个寄存 器级联。 而如果是阻塞赋值,a=D,一个寄存器,接下来b=a,这时候a已经更新 成D的值了,因此相当于b=D,同样地Q=D,因此这种写法最终导致a,b,Q三个 变量的值始终都是一样的,它们都是D变量的寄存器输出,综合后只有一个寄 存器。 18
18 主要是继承了软件编程语言的顺序执行语句。 而非阻塞赋值是 Verilog 特有的一种赋值方式,它是为了适应硬件电路的运 行特点而产生的。它的执行也可以分成三个步骤:运算、赋值、赋值完成。但是 它在运算完成后,就不再继续执行,而是执行另外的同类型非阻塞赋值语句的运 算操作,在所有非阻塞赋值语句的运算操作都完成后,然后再统一进行赋值操作 (即所谓并行执行理念)。最后全部赋值完成。 以教材上例 5-1 和例 5-2 为例,分析二者的区别: 首先假定 A,B 初值都是零,然后在同一时刻变成 1,对例 5-1,顺序执行 阻塞赋值,因此 M1/M2/Q 都是 1,对例 5-2,它采用的是非阻塞赋值,在 A、B 没有变化前,按照代码书写内容,M1、M2、Q 都为零,而在变化的时刻,这三 条语句会同时开始计算表达式的值,可以看到,对 M1 的赋值,计算结果为 1、 M2 的,注意它开始计算时 M1 还没有更新,因此这时 M1 仍为原值零,M2 赋值 为零,同样,Q 赋值 M1 和 M2 的或,但此时 M1 和 M2 都没有更新,因此最终 Q 也为零。 如果是在时序电路中,阻塞赋值与非阻塞赋值的差异更大,它会直接导致综 合后电路的明显差异。 以下分析时序电路中采用两种赋值的对比: 首先分析非阻塞赋值,它定义了三个寄存型变量,a,b,q,按照非阻塞赋值的 执行过程来分析,在一个时钟沿到来时,过程被激活运行,三条语句同时计算表 达式的值,然后一同更新。首先在上升沿时刻,D 的值被 a 寄存,这对应硬件电 路上的什么结构?1 个 D 寄存器,然后呢,下一条语句是 b<=a,注意这时候 a 的值还没有更新,因此,相当于把 a 的老值寄存给 b,这又对应一个寄存器,同 样地,Q<=b 也对应一个寄存器。 因此,我们从整体的角度看,这样的非阻塞赋值代码最终综合后是三个寄存 器级联。 而如果是阻塞赋值,a = D,一个寄存器,接下来 b = a,这时候 a 已经更新 成 D 的值了,因此相当于 b=D,同样地 Q=D,因此这种写法最终导致 a, b,Q 三个 变量的值始终都是一样的,它们都是 D 变量的寄存器输出,综合后只有一个寄 存器
这里可以看出明显差异,所以,一般在时序电路设计中,建议使用非阻塞赋 值。 为加深理解,可介绍二者混用的例子 例5-7中,虽然l写在前面,但它采用的是非阻塞赋值,因此它的值不会立 刻更新,而bl采用的是阻塞赋值,一旦启动就立刻更新,因此,b1反而al更 早完成更新,这就是二者的区别。当然,像这种混用的写法,我们实际中不会这 样做,可读性太差,一般我们不建议阻塞和非阻塞赋值同时在一个块中出现。 注意:特定顺序的阻塞赋值也可以实现多级移位寄存器电路呢,但是不推荐 使用,可读性差,这也更进一步印证了时序电路中最好采用非阻塞赋值的结论。 总结非阻塞与阻塞赋值 关于阻塞与非阻塞,教材上给出了许多混用例子,但这些例子都只是为了说 明二者的区别而构建的,现实中不建议使用。对于组合电路,为了保证逻辑正确, 最好用阻塞赋值,用非阻塞会导致赋值更新不及时影响后面的判断。而对时序电 路,为了保证寄存器不被优化掉,就最好用非阻塞赋值,以免后面赋值直接用到 新值。 82过程结构总结 1:过程与软件的函数有相似又有差异,过程具有无限循环特性,只要敏感 信号量发生变化,就会自动运行,运行完毕后进入等待状态,等待再次运行。 2、过程中语句具有并行性,从仿真角度看,always块中无论包含了多少语 句,只要过程启动,执行时间无限小,而与是不是阻塞赋值无关(与软件有很大 差异,软件代码越多,执行时间越长,)Verilog要描述硬件电路的行为,所以, Verilog仿真不像软件可以单步调试的,它只能是一个时间轴上的仿真,通过查 看不同时刻变量的变化情况来确定代码功能是否正常。 3、不同过程之间是并行的:多个always块是完全并行的,谁写前面是没有 任何区别。当然不一定大家都在执行,具体每个过程是否在执行状态是看它的敏 感信号量是否满足,这是软件语言无论如何实现不了的。 4、时序过程中只能存在单一时钟沿逻辑:主要是从代码对应的硬件电路角 度来考虑,最终时序电路是要映射到寄存器,而寄存器只有一个触发时钟,如果 既想在上升沿作操作,也想在下降沿作处理,需要分成多个ways块,而且阻 19
19 这里可以看出明显差异,所以,一般在时序电路设计中,建议使用非阻塞赋 值。 为加深理解,可介绍二者混用的例子 例 5-7 中,虽然 a1 写在前面,但它采用的是非阻塞赋值,因此它的值不会立 刻更新,而 b1 采用的是阻塞赋值,一旦启动就立刻更新,因此,b1 反而 a1 更 早完成更新,这就是二者的区别。当然,像这种混用的写法,我们实际中不会这 样做,可读性太差,一般我们不建议阻塞和非阻塞赋值同时在一个块中出现。 注意:特定顺序的阻塞赋值也可以实现多级移位寄存器电路呢,但是不推荐 使用,可读性差,这也更进一步印证了时序电路中最好采用非阻塞赋值的结论。 总结非阻塞与阻塞赋值 关于阻塞与非阻塞,教材上给出了许多混用例子,但这些例子都只是为了说 明二者的区别而构建的,现实中不建议使用。对于组合电路,为了保证逻辑正确, 最好用阻塞赋值,用非阻塞会导致赋值更新不及时影响后面的判断。而对时序电 路,为了保证寄存器不被优化掉,就最好用非阻塞赋值,以免后面赋值直接用到 新值。 8.2 过程结构总结 1:过程与软件的函数有相似又有差异,过程具有无限循环特性,只要敏感 信号量发生变化,就会自动运行,运行完毕后进入等待状态,等待再次运行。 2、过程中语句具有并行性,从仿真角度看,always 块中无论包含了多少语 句,只要过程启动,执行时间无限小,而与是不是阻塞赋值无关(与软件有很大 差异,软件代码越多,执行时间越长,)Verilog 要描述硬件电路的行为,所以, Verilog 仿真不像软件可以单步调试的,它只能是一个时间轴上的仿真,通过查 看不同时刻变量的变化情况来确定代码功能是否正常。 3、不同过程之间是并行的:多个 always 块是完全并行的,谁写前面是没有 任何区别。当然不一定大家都在执行,具体每个过程是否在执行状态是看它的敏 感信号量是否满足,这是软件语言无论如何实现不了的。 4、时序过程中只能存在单一时钟沿逻辑:主要是从代码对应的硬件电路角 度来考虑,最终时序电路是要映射到寄存器,而寄存器只有一个触发时钟,如果 既想在上升沿作操作,也想在下降沿作处理,需要分成多个 always 块,而且阻
塞与非阻塞不要混用。 5、关于分支的完整性:在时序电路中,分支可以不完整,但在组合电路中, 如果分支不完整,就很可能有问题(解释为什么会生成锁存器)。 8.3移位寄存器设计 回忆数字电路中的移位寄存器知识,介绍其用处(串并转换)。 分析例5-14,在LOD为高时读入预置数,然后每个时钟周期数据右移 次,QB相当于把DIN的数据转化成串行数据,从最低位开始,一位一位输出(分 析时序图)。 其它功能更完整的移位寄存器由学生自学,另外,Verilog中也有专门的移位 运算符>,<(95标准,它移出腾空的位都是用0来填充),在Verilog2001标准中 又新增对有符号数的移位,<>,更方便。 8.4硬件乘法器设计 这一节内容其中真正的重点并不是要求大家学会如何设计硬件乘法器,而是 让大家学习一些新的Verilog语法,关于硬件乘法器的设计,不要求大家掌握, 事实上,现在FPGA内部都是有现成的1P核,大家直接拿来调用即可。 I、parameter::非常实用的功能,与C语言的宏定义非常相似,使模块具有 可重构性(介绍parameter的用法,并板书实例,如一个参数可变加法器,需要 注意的是parameter只对当前模块范围有效)。 2、整数类型:类似于C语言的int型,integer,固定32位位宽,主要用在 循环体变量中。 3、for循环:for循环在C语言中应用的极其普遍,Verilog中虽然也有for 循环,但却很少应用,这也是由于硬件电路的特性决定的,or循环经过综合之 后相当于把所有循环体展开,这意味着大量的硬件复制,所以一般可综合代码中 很少用for循环,格式与C语言完全相同,只不过Verilog中没有C语言的+ -运算符,因此这里循环条件不能简写,循环变量一般定义成integer类型(for 循环示例)。 4、repeat循环:直接规定循环次数,使用起来更为简洁 5、while:借用了C语言的语法,只要条件为真就执行(循环示例) 6、还有一种forever,不可综合,主要用于仿真,类似于while(I),也就是 20
20 塞与非阻塞不要混用。 5、关于分支的完整性:在时序电路中,分支可以不完整,但在组合电路中, 如果分支不完整,就很可能有问题(解释为什么会生成锁存器)。 8.3 移位寄存器设计 回忆数字电路中的移位寄存器知识,介绍其用处(串并转换)。 分析例 5-14,在 LOAD 为高时读入预置数,然后每个时钟周期数据右移一 次,QB 相当于把 DIN 的数据转化成串行数据,从最低位开始,一位一位输出(分 析时序图)。 其它功能更完整的移位寄存器由学生自学,另外,Verilog 中也有专门的移位 运算符>>,<<(95 标准,它移出腾空的位都是用 0 来填充),在 Verilog2001 标准中 又新增对有符号数的移位,<<</>>>,更方便。 8.4 硬件乘法器设计 这一节内容其中真正的重点并不是要求大家学会如何设计硬件乘法器,而是 让大家学习一些新的 Verilog 语法,关于硬件乘法器的设计,不要求大家掌握, 事实上,现在 FPGA 内部都是有现成的 IP 核,大家直接拿来调用即可。 1、 parameter:非常实用的功能,与 C 语言的宏定义非常相似,使模块具有 可重构性(介绍 parameter 的用法,并板书实例,如一个参数可变加法器,需要 注意的是 parameter 只对当前模块范围有效)。 2、 整数类型:类似于 C 语言的 int 型,integer,固定 32 位位宽,主要用在 循环体变量中。 3、 for 循环:for 循环在 C 语言中应用的极其普遍,Verilog 中虽然也有 for 循环,但却很少应用,这也是由于硬件电路的特性决定的,for 循环经过综合之 后相当于把所有循环体展开,这意味着大量的硬件复制,所以一般可综合代码中 很少用 for 循环,格式与 C 语言完全相同,只不过 Verilog 中没有 C 语言的++、 --运算符,因此这里循环条件不能简写,循环变量一般定义成 integer 类型(for 循环示例)。 4、 repeat 循环:直接规定循环次数,使用起来更为简洁 5、 while:借用了 C 语言的语法,只要条件为真就执行(循环示例) 6、 还有一种 forever,不可综合,主要用于仿真,类似于 while(1),也就是
我们常说的死循环,后面我们介绍仿真时再详细介绍其用法。 (循环语句总结) 总共4种循环语句,其中for和While都和C语言基本一致,repeat和forever 则是Verilog特有,但总体上,这些循环语句主要是在仿真中使用,RTL级设计 中用处不大,写T代码的目的是高效的硬件电路实现,而不是追求精致的代 码,教材上虽然给出了多个使用循环语句构建硬件乘法器的例子,但没有实用价 值,如果需要使用硬件乘法器,请直接使用FPGA厂商提供的IP核。 8.5IF语句一般用法 1、不完整的分支语句,意味着有记忆,是时序电路,所以一般在寄存器设 计中使用 2、2个分支,即可以是组合也可以是时序,具体情况具体分析(还可以板书, 写完整分支的D触发器) 3、多分支的flse语句,也是完整分支,同样地,可以是组合也可以是时 序 2种条件语句,一种是if else,还有一种是case语句,多重if else语句是有 优先级的,而case则是平级, 通过优先编码器介绍优先级概念PPT17(优先编码器实现) 教材上给这种优先编码器给出了2种实现方式,右边是多分支flse语句, 这个比较容易理解。左边是一种case语句的变种,casez,casez的含义是如果比 较的某一位是高阻态,那么这一位就不再比较,所以左边这种写法也可以达到优 先编码的目的,但是需要说明的是csz语句不在标准的可综合语句之列,也就 是说并非所有的综合器都能对例5-2完成综合,因此,我们在实际设计中,不要 采用casez的方式(与之相类似,还有一个casex,它只要比较对象中某一位是 不定态,也不作比较) 8.6三态与双向端口设计 nout类型端口在对外接口中十分常用,也是Verilog的另外一个难点。 在学习iout之前,复习数字电路中的知识,介绍高阻态(解释高阻态的含 义),们之所以能实现双向端口,就是因为有高阻态在里面。 板书:画三态门符号,并解释其特征。 21
21 我们常说的死循环,后面我们介绍仿真时再详细介绍其用法。 (循环语句总结) 总共 4 种循环语句,其中 for 和 While 都和 C 语言基本一致,repeat 和 forever 则是 Verilog 特有,但总体上,这些循环语句主要是在仿真中使用,RTL 级设计 中用处不大,写 RTL 代码的目的是高效的硬件电路实现,而不是追求精致的代 码,教材上虽然给出了多个使用循环语句构建硬件乘法器的例子,但没有实用价 值,如果需要使用硬件乘法器,请直接使用 FPGA 厂商提供的 IP 核。 8.5 IF 语句一般用法 1、不完整的分支语句,意味着有记忆,是时序电路,所以一般在寄存器设 计中使用 2、2 个分支,即可以是组合也可以是时序,具体情况具体分析(还可以板书, 写完整分支的 D 触发器) 3、多分支的 if else 语句,也是完整分支,同样地,可以是组合也可以是时 序 2 种条件语句,一种是 if else,还有一种是 case 语句,多重 if else 语句是有 优先级的,而 case 则是平级, 通过优先编码器介绍优先级概念 PPT17(优先编码器实现) 教材上给这种优先编码器给出了 2 种实现方式,右边是多分支 if else 语句, 这个比较容易理解。左边是一种 case 语句的变种,casez,casez 的含义是如果比 较的某一位是高阻态,那么这一位就不再比较,所以左边这种写法也可以达到优 先编码的目的,但是需要说明的是 casez 语句不在标准的可综合语句之列,也就 是说并非所有的综合器都能对例 5-2 完成综合,因此,我们在实际设计中,不要 采用 casez 的方式(与之相类似,还有一个 casex,它只要比较对象中某一位是 不定态,也不作比较) 8.6 三态与双向端口设计 inout 类型端口在对外接口中十分常用,也是 Verilog 的另外一个难点。 在学习 inout 之前,复习数字电路中的知识,介绍高阻态(解释高阻态的含 义),们之所以能实现双向端口,就是因为有高阻态在里面。 板书:画三态门符号,并解释其特征