跟着我从零开始入门FPGA(一周入门XXOO系列)-2、组合逻辑设计
发布者:jackzhang
时间:2015-03-11 08:57:28
(本连载共七部分,这是第二部分)
作者:McuPlayer2013 (EETOP FPGA版块版主) 原帖地址:http://bbs.eetop.cn/thread-385362-1-1.html)
以下是正文:
2、组合逻辑设计
组合逻辑是神马?
所谓组合逻辑就是,一堆输入注定了一个(或多个)输出,明天你再送同样的这一堆输入,可以得到跟今天完全相同的结果。
或者说,输出的值跟先前任何状态没有一毛钱的关系,只跟当前的输入有关系。
来个最简单的:
assign out = in1 & in2;
这是个与门,out的值只跟in1和in2有关。
这时候?语句很有作用了,比如
assign out = sel ? in1 : in2;
这是一个二选一的选择器。
你肯定觉得二选一太简单了,来个4选一看看
assign out (sel==2'b00) ? in0 : (sel==2'b01) ? in1 : (sel==2'b10) ? in2 : (sel==2'b11) ? in3;
不知道你感觉如何,反正我从第二个问号开始花眼,咋办?
首先一个办法,?表达式的复合,我们可以用括号来区分层次,但仍然感觉很不直观。
想到了什么,C语言的switch/case,OK,我们就用Verilog的case语句写一下
always @(sel or in0 or in1 or in2)
case(sel)
2'b00: out = in0;
2'b01: out = in0;
2'b10: out = in0;
2'b11: out = in0;
endcase
OK,我们看看这个case语句是什么?没错,他就是那个真值表的美丽化身。
怎么,你还想到了卡诺图辅助逻辑表达式化简,当年读书时候,整天对着田字格横看竖看的,很神奇的。
现在我们有Verilog语言了,化简的事情交给综合器好了。
啥,你不知道综合器是啥?C语言的C编译器,你知道吧,他俩基本是一个地位的。
always的小老鼠后面的括号里不是有很多“变量”吗,那叫敏感信号。
只要敏感信号任何一个有变动,下面的语句就执行一次,其实这是个形象的说法,几乎是专门给C语言工程师定制的一个解释。
说到逻辑电路,我们找个非典型的用途吧-----地址译码
CPU就说是8051吧,其实其他的也一样
我们有个外设,内部有8个寄存器,我们打算把它安排到地址0xF080~0xF0087,设计它的片选信号
比较笨的方法:
assign sel = (addr==16'hF080) | (addr==16'hF081) | ...............
我就不敲完了,8个啊,复制完了,还要一个个修改,还要校对,够苦B的了
always @(addr)
case(addr)
16'hF080, 16'hF081, 16'hF082, 16'hF083, 16'hF084, 16'hF085, 16'hF086, 16'hF087:
sel = 1;
default:
sel = 0;
endcase
这个case语句简单多了吧,16'hF080的含义就是此数据有16个bit,h表示后面是十六进制表示的。
F080你要不知道什么意思,估计你们学校是体育老师讲计算机基础课,当然也可能是政治老师。
其实,case里面连续写8个项,然后一个冒号,感觉也很是苦B
那我们手工分析下0xF080~0xF0087的特征,高位的3个Nibble是F08,低位Nibble是0~7
我们再用二进制的方式看看:
1111 0000 1000 0000
1111 0000 1000 0001
1111 0000 1000 0010
1111 0000 1000 0011
1111 0000 1000 0100
1111 0000 1000 0101
1111 0000 1000 0110
1111 0000 1000 0111
---------------------------------------
1111 0000 1000 0xxx
这3个小x的含义我就不说了,这点归纳的逻辑都没有的话,您真不适合做工程师,适合做公务员。
always @(addr)
casex(addr)
16'b1111_0000_1000_0xxx:
sel = 1;
default:
sel = 0;
endcase
是不是帅呆了,如果你不是太粗心的话,应该看到了这个是casex而不是case
其实帅不是目的,泡到妞才是根本。看一个assign语句就可以搞定的事,何必罗嗦这么多呢。
assign sel = (addr==16'b1111_0000_1000_0xxx);
估计你还看到了一个小东西,那下划线。
没错,他就是个摆设,增加可读性的摆设,去掉也可以,不过我舍不得去掉。
C语言里如果要判断这个sel信号,一般用bit mask的方法
sel = ((addr&0xFFF8)==0xF0808);
看来照猫画虎,这招也可以用在这里,虽然代码稍微差异,思路近似。
关于case语句的一个注意事项,就是所谓的full-case,这个很重要
always @(addr)
casex(addr)
16'b1111_0000_1000_0xxx:
sel = 1;
endcase
那么,当地址不落在我们指定范围内的时候,没有语句来处理。
没来命令,就是要原地驻军。
其实相当于,
always @(addr)
casex(addr)
16'b1111_0000_1000_0xxx:
sel = 1;
default:
sel = sel;
endcase
天啊,sel=sel,这就是传说中的意外的latch,这跟我们的意图不一致
即使逻辑上可以完成指定功能,他也额外用掉了一个寄存器。
记住,用case语句的时候一定要full-case处理,除非你设计的就是寄存器
寄存器的latch是时序逻辑里面的内容
欲知后市如何,请听下回分解。