受造恒久永罚永夜。入此地者,弃绝一切希望。
——《神曲》
有句电影台词说得好:“没吃三天素,就想上西天,天下没那么容易的事!”
汝欲学Verilog否?
然!——由值域与表达式始。
非也,非也,吾不欲也!——“你是老道雇来踢场子的?来人啊,拖出去埋了!最讨厌土豪了,不来学习 Verilog 语言,买这本书干嘛啊?点火烤肉?老衲把你当全羊烤了!”我说嘛,前天才养了只猫,昨天晚上房梁上就有耗子叫。还以为是招财(猫的昵称)不努力呢,还饿了它一顿。现在看起来,倒是此子捣鬼。反而是在下冤枉了招财,免不得“妙鲜包”赔罪。老板,这个账找此人报销啊!
出家人慈悲为怀,善念为本,帮大家搞定了这个孽障,世界清静了,方好随老僧入Verilog之门,修行数字逻辑设计。话说在前面,练功什么的都没有捷径可走,最少老衲没有那个道行。到了鄙人手下,几乎都是老老实实的从挑水练脚力和劈柴练臂力开始,慢慢过渡到修习易筋经的。没有段誉的无量山山洞可以钻,当然更无虚竹的冰窖。别多想啦,“台上一分钟,台下十年功”,还不去担水?
讨论一个问题,必须首先明确讨论的范围,给大家一个约束。要不很多讨论就会沦为鸡同鸭讲,变成很无聊的打嘴仗。对于数学或工程问题,有关问题的值域是一个非常重要的信息。例如,有关问题到底是伽罗华域的事情,还是实数域的概念,就会得到完全不同的结论。见过太多贵人,在争论得面红耳赤之后,突然发现原来不是讨论信号处理而是编解码的尴尬了。当然,遇到这种情况,也不是没有全身而退的办法,但是这属于耍流氓的领域了,本书不表。
回到正题,前文书说道:Verilog 语言是描述数字硬件电路的;而《数字电路》/《数字逻辑设计》教材里,又说数字电路的理论基础是布尔代数;布尔这个就偏门了,帮大家查了一下,都说这个是“二值”逻辑的。但是,为什么连维基百科里都会有如下说法呢?
逻辑值及其解释 信号强度(从强到弱)及其属性
0:逻辑低电平,条件为假 supply:驱动
1:逻辑高电平,条件为真 strong:驱动
z:高阻态,浮动 pull:驱动
x:未知逻辑电平 large:存储
weak:驱动
medium:存储
small:存储
highz:高阻态
上面列出了Verilog采用的具有八种信号强度的四值逻辑(four-valued logic),数字电路中的信号可以用逻辑值、信号强度加以描述。当系统遇到信号之间的竞争时,需要考虑各组信号的状态和强度。如果驱动统一线网的信号强度不同,则输出结果是信号强度高的值;如果两个强度相同的信号之间连接到同一个线网,将会发生竞争,结果为不确定值x。”
Verilog还是用强度值来解决数字电路中不同强度的驱动源之间的赋值冲突。如果两个具有不同强度的信号驱动同一个线网,则竞争结果值为高强度信号的值。如果两个强度相同的信号之间发生竞争,则结果为不确定值。这个情况本身就是设计里的禁忌,在四值电路里容易造成短路等不可预知的危险,是不建议出现的,因此这里也不详细论述。
以上说法属于本分的范畴,也就是说,到这里也就算说清楚了;下面的介绍是交情的领域,讲讲清楚有利于大家理解。
值“0”和“1”不难理解,这个在数电里大家接触得多了,无须详述。如图2.4所示为一个5 V的TTL(Transistor-Transistor Logic,晶体管-晶体管逻辑)的典型上升沿。这个也是数电的知识,简单说吧,就是:当输出高于V OH 时,认为是输出为逻辑值“1”;当输出低于 V OL 时,认为是输出为逻辑值“0”;当输入高于 V IH 时,认为是输入为逻辑值“1”;当输入低于V IL 时,认为是输入为逻辑值“0”。
图2.4 TTL电平与逻辑值判断
电气标准里定义:V OH_min =2.4V,V OL_max =0.4V,这是对器件输出的要求;V IH_min =2.0V,V IL_max =0.8V,这是对输入信号的要求。这些具体指标不是关键,大家看看热闹就好。
还有其他标准,情况大体类似,不再赘述。
有些细心的读者要问了,那么这些“V”的高低之间的区域算什么呢?这个问题问得好,老衲正等着呢。以输入为例,如果输入电压在V IH 和V IL 之间,则在真实电路里既可能被认为是“0”,也可能被判决为“1”,通俗说就是看人品了。
在Verilog语言的仿真中间,不存在这种模棱两可的道理,于是“x”这个第三者出现了,标记输入出现问题。未知逻辑电平“x”的主要出现可能有两个。第一个:上面介绍的情况多出现于触发器等不满足采样要求(就是前面时序分析里介绍的保持时间和建立时间),俗称为“采到边沿”。另一个:触发器等具有存储功能的器件未被赋初值(包括FPGA赋初值和复位赋初值),俗称为“无初值”。大多数时序电路的当前状态(也就是各个触发器的当前值的集合)是和电路本真以前的状态有关的,这个后面会多次讲到。没有初始值,仿真软件会郁闷的,后面的值是什么完全没谱了。仿真总归比实际系统仁慈一些,于是输出的值是“x”而不是随机选择的,这样容易观察到。在大多数仿真软件输出的波形里,“x”会被标为醒目的红色,于是这种现象也就有了一个通俗的说法“全国山河一片红”。
说完了“x”再扯扯“z”。高阻态“z”与逻辑未知电平“x”是一个“语言造”的不同概念,它是在电路系统中真实存在的。
在总线连接的结构上,总线上挂有多个设备,设备与总线以高阻的形式连接,这样在设备不占用总线时自动释放总线,以方便其他设备获得总线的使用权。大部分单片机I/O使用时都可以设置为高阻输入。高阻输入可以认为输入电阻是无穷大的,认为I/O对前级的影响极小,不产生电流(不衰减),而且在一定程度上也增加了芯片的抗电压冲击能力。
高阻态是一个数字电路里常见的术语,指的是电路的一种输出状态,既不是高电平,也不是低电平,如果高阻态再输入下一级电路,则对下级电路无任何影响,和没接一样,如果用万用表测既有可能是高电平也有可能是低电平,由它后面接的东西而定。
进行电路分析时,高阻态可当作开路理解。你可以把它看作输出(输入)电阻非常大。它的极限状态可以认为悬空(开路)。也就是说,理论上高阻态不是悬空,它是对地或电源电阻极大的状态。而在实际应用中,它与管脚的悬空几乎是一样的。
当门电路的输出上拉管导通而下拉管截止时,输出为高电平,反之,就是低电平;当上拉管和下拉管都截止时,输出端就相当于浮空(没有电流流动),其电平随外部电平高低而定,即该门电路放弃对输出端电路的控制。
输出高阻可以利用名字叫作“三态门”的器件实现,其符号和真值表如图2.5所示。鉴于本书涉及的都是逻辑层面的内容,其内部具体电路在这里就不画出来了,读者可以自行参考有关资料。
图2.5 三态门的电气符号与真值表
高阻一般会在输入/输出管脚处使用,这时需要图2.6中所示的电路来实现。
图2.6 输入/输出管脚电路
世界那么大,仅仅靠两个数值“0”和“1”能表示得完全吗?这个问题在几十年前问还情有可原,现在,看看电子数字计算机的应用范围,多少活生生的例子都说明:这种怀疑是完全没有必要的。具体到技术层面,答案就在两个字里:编码。“万世万世,无所不可编码”,这个话大了点,肯定有反例。但是换一种说法:“大多数工程问题,都可通过数字化的编码解决,”这就是非常谦虚的说法了。
理论上,每位施主都可以搞一套自己私有的编码体系,只要到时不发生混淆即可。但是,沙门的建议是:如无必需,请勿创新。对于二进制无符号数、有符号数等的编码方法,前人已做了很多研究,有了许多结论,放着不用纯属浪费。
“二进制无符号数、有符号数的二进制编码”应该属于数电教习提纲里的“应教、应知、应会”内容。且容鄙人偷个懒,抄一点教科书的内容。
“二进制是计算技术中广泛采用的一种数制。二进制的数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由17世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0。
一般来讲,若b是基底,则在b进制系统中的数表示为a 1 b k +a 2 b k-1 +a 3 b k-2 +…+a k+1 b 0 的形式,并按次序写下数字a 1 a 2 a 3 …a k+1 。这些数字是0到b-1的自然数。
若一段文字(譬如这段文字)讨论多个基数,当有歧义时,基数(本身用十进制表示)用下标方式写在数的右边。除非有上下文说明,没有下标的数字视为十进制。通过使用一点(小数点)来将数字分成两组,这样就可以用位置系统来表示小数了。例如,在基数为2的系统中,10.11表示1×2 1 +0×2 0 +1×2 -1 +1×2 -2 =2.75。
一般来讲,b进制系统中的数有如下形式:
对于常用的二级制系统,b=2。”
“原码是一种最‘自然而然’的编码思想,就是沿袭数学上的表示。正数符号位为‘0',负数符号位为‘1',后面添加绝对值的无符号整数表示。以8比特位宽带符号整数格式为例,‘+1’就是(0000 0001) 原 ,‘-1’就是(1000 0001) 原 。其中,下标标记编码方法在没有歧义时,可以略去不写。这个表示的最大好处就是,工程师用眼睛就可以看出数值,不必经过换算。
反码看起来就有些别扭了:正数符号位为‘0',后面添加绝对值的无符号整数表示;负数符号位为‘1',后面添加绝对值的无符号整数的各比特位变反表示。还是以8比特位宽带符号整数格式为例,‘+1’就是(0000 0001) 反 ,“-1”就是(1111 1110)反。反码里的“反”字指的就是这个各比特位变反操作。这种表达的坏处很明显,任谁只看不计算,都不知道负数的值是几何。
补码瞧上去就更加‘没天理’了:正数符号位为‘0',后面添加绝对值的无符号整数表示;负数符号位为‘1',后面添加绝对值的无符号整数的各位变反,然后还要加上1(数值,不是每比特位加1)表示。还是以8比特位宽带符号整数格式为例,‘+1’就是(0000 0001) 补 ,‘-1’就是(1111 1111) 补 (=(1111 1110) 反 +0000 0001)。”
虽然说二进制是数字电路的基础,但是用一串1、0来书写数字实在是有些长,容易使人看得眼花。现在软件技术先进了,完成一个进制转化可以说是易如反掌。因此,为了方便书写及符合工程师的一般习惯,Verilog 语言里提供了关于数字的多种进制的支持,表达的一般格式如图2.7所示。除了数值部分前面已经介绍以外,其他部分的值域和含义见表2.5。
图2.7 Verilog内常量的一般格式
表2.5 Verilog内常量表达中各个部分的含义
例2.3给出了一些例子,供大家参考。提醒大家,常量里的那个“'”是必需的,千万别忘掉了。
【例2.3】各种常量表达
对于上面的各种例子中间,非法的常量无须赘述,类似于位宽过大,尤其是“-4 'sd15 ”这种还带上代码设计者自己复杂计算的常量表达,以后我不希望见到。写这种东西,除了炫耀自己语法掌握得有多好之外,似乎没有什么工程上面的实际好处。
有时,数值表示会很长,此时可以使用下画线“_”分割,参考例2.4。
【例2.4】常量的分割
除了简单的驱动/传输门之外,几乎所有系统的输入信号都必须产生某些相互关系。从简单的比较器、译码器,到复杂的交换机、路由器,无一例外。很明显,透传性质的驱动/传输门不是本书的设计重点,因此了解常量或后面讲到的变量如何产生关系就是不可缺少的了。
表2.6中间给出了运算符——也就是信号关联的操作的一个列表,至于它们的具体含义与细节,将会在下一章里详细介绍。换句话说,先看热闹,下文书里讲门道。另外,还有有关实数的操作问题,因为不是电路的部分,这里略去。
表2.6 Verilog语言内的操作符
运算符之间不是并列的,这和四则运算的乘除优先的情况类似,运算符之间的优先级顺序见表2.7。括号对“()”具有最高的优先权,表里没有体现,老衲接下来说明。
表2.7 运算符的优先级顺序
表2.6虽然规定了一个顺序,但不是用来强制大家死记硬背的。实际代码里,根本就不建议采用平铺的方式书写代码。看看例2.5,不难体会有括号和没有括号的两种写法容易阅读。不难理解,为什么很多编程规范里强行规定代码里要使用括号了。
【例2.5】建议采用括号对提高代码的可读性
无括号对:1'b1 ? 8'h2 * 8'h3+8'h4 : 8'h2 * 8'h3-8'h4
有括号对:(1'b1)?(8'h2 * 8'h3+8'h4):(8'h2 * 8'h3-8'h4)
这正是:
“布尔高低变化多,数目胜过孙头陀。仿真接口都要做,四值方可全部落。多位比特任腾挪,几个进制靠君说。运算关联要记妥,括号带入更容错。”