首先回顾了一下状态机的概念。我个人理解:状态机和人的思维非常类似,同时软件的执行也可以看作一个状态机。将一个任务划分为不同的状态,定义状态之间的跳转逻辑,不同的状态实现怎样的功能等,可以实现大多数的典型算法。但是状态机实现并不一定是个好的实现,基于状态机实现实际上跟软件思维已经差不多了,但实际上硬件之间是并行的,这和人的逻辑思维是不一样的。所以采用状态机实现一些逻辑功能的话,很大概率没有很好的利用并行运算这一特点,导致并行度不够高,吞吐量不够大等。当然,对于初学者,掌握状态机是非常重要的,状态机在实现控制逻辑上具有非常重要的作用。 然后回顾了一下状态机的电路图,状态机电路有三部分。 用于计算下一状态的状态计算逻辑,通常有当前状态和当前输入,决定了下一个状态 用于寄存状态逻辑的D触发器。状态机核心”存储+反馈“。因此D触发器就起到了存储的作用 输出逻辑。根据当前的状态计算此刻的输出 上述所说的是摩尔型状态机,也就是输出与输入无关,只取决于当前状态。希望大家在设计的时候都是用该状态机,不要使用mealy型状态机。原因非常简单,state信号变量由寄存器进行输出,因此计算输出逻辑的时候有很好的时序,如果采用输入加状态共同计算,如果输入是触发器直接输出倒还好。部分情况可能已经经过了很长的组合逻辑,时序会变得很差,并且还会产生毛刺。所以直接按照上图那样设计即可。 然后介绍了一个典型的状态机代码实现。给出的是二段式状态机。其中的enum是枚举变量。下面的代码将计算下一逻辑状态和寄存逻辑状态放到了同一个always块当中,也就是状态机三部分的前两部分。最后采用assign语句,实现了状态机的输出逻辑。我个人并不喜欢这种风格,在我自己做设计的时候,都是采用三段式状态机,也就是采用两个always块,显式的区分当前状态和下一状态,这么做的好处是代码可控,不然state指的是触发器的D端还是Q端呢(这段代码是Q端)?这其实会对初学者产生一些困难。下图是《数字设计.系统方法》的附录,大家可以仔细看一下,应该能有所体会。有关三段式的写法,请参考我的这篇文章:带带大师兄:HDLBits: 在线学习Verilog(Problem 120-126)。 然后是代码和电路图的对应,配套的文字告诉我们这段代码实际上是模板,目前的编译器能够很好的识别这段代码了(这段If/else可以很好的综合成带复位的寄存器了)。 然后说明了一下什么是下降沿异步复位,大家看图理解一下吧。 然后说明了,如果我们想全部写在一个always块应该怎么办。我们仍然希望在状态“s0”的时候输出1,这就需要我们在S2状态的时候,写下一个时钟上升沿的逻辑输出q为1。这样的话,输出和S0状态是一起到来的,也就达到了我们的目的。(其实这里蕴含着next_state和current_state作为不同的状态判断,进行逻辑输出是一个道理,大家理解清楚对应的电路图,应该很好判断,无非是差了一个时钟周期而已) 然后给了一段式状态机的代码,代码逻辑就是开始所说的,如何将所有逻辑写到一个always块。有一说一,这种代码大家不要写....... 这是代码对应的电路图,大家可以看到,会多消耗一个寄存器,用于对应q这个信号。代码和电路都比较简单,就不过多介绍了。 然后给了一个复杂点的例子。可以看到一共有三个状态,然后定义了一个function,实际上就是可以调用的一个组合逻辑而已。我们先看右边这个always_ff逻辑块。 初始状态为s0,然后s0会直接跳转到s1,s1会跳转到s2。不需要满足什么逻辑才会进行跳转,所以是3个时钟周期为一次循环。 再看c和count这两个逻辑变量。初始状态count和c都为0。s1状态count变成1,c变成a+b(a和b是输入信号,所以这是个mealy型状态机),s2状态count会经过myinc这个函数的计算,这个函数的逻辑将'b01变成了'b10,c变成a-b。 最后将c经过always_comb的逻辑运算,得到了f,这就是整段代码的含义(具体这段代码用来干嘛的,不太清楚。。。) 这是对应的电路图,我给大家理一下。 首先我们依旧看状态变化,在最下方,用一个mux选出送到state寄存器的输入信号,这里已经包含了状态机三部分的两部分了。“状态转移逻辑和状态寄存逻辑”。 然后状态会送到输出计算逻辑,我们先看count这个寄存器变量。由代码可以知道,它的值可能会是0,会是1,会是由myinc计算得到的值。因此依然会是一个mux进行选择(由state作为选择信号select),然后送到count寄存器作为输出。 然后是f变量,其实也是一样的道理。可能是0,可能是a+b,可能是a-b。由状态来选择进行输出到c寄存器。输出以后的变量还要经过always_comb逻辑,进行逻辑运算得到f。 经过了上面的代码,相信大家对状态机,以及状态机对应的代码和电路有了一定的了解。接下来让我们看一个有趣的例子,斐波那契数列 答主本人当时上verilog课的时候,期末上机第一道编程题就是斐波那契数列(当时不学无术的我做了好久才做了出来,越想越觉得自己菜),所以大家好好阅读这篇文章,除了帮助就业科研,还能帮你做考试题(bushi 回到slide本身,斐波那契数列的通向表达式如图所示,相信大家都知道了吧?用通俗的语言来表达,前两个数字为1,后面的数字为前两个数字的和。 这种极具规律,通项公式都给你列出来了的算法,当然可以用状态机来实现。我们定义一个状态机,输入"input_s"表示计算多少项n,"fibo_out"表示“F(n)"。在begin_fibo使能之前,一直保持IDLE状态。运算完成以后,会把done信号拉高。并且我们的testbench会进行一个比较。 然后给了设计思路,使用三个寄存器。分别表示计数,R0和R1的值。当counter计数达到input_s次数的时候完成运算,在此之前一直重复执行运算。 然后给出了代码实现。这种状态机的代码分析思路都是一致的,如下几步: 首先看状态怎么跳转,默认IDLE状态,当拉高begin_fibo的时候跳转到COMPUTE状态,当运算了input_s次数的时候,从COMPUTE状态跳转到DONE状态,DONE状态维持一个周期便会跳转到IDLE状态。 然后看逻辑输出。在IDLE状态,默认所有变量都是0,当begin_fibo拉高的时候,会给R0赋值1,R1赋值0(这里隐含了一个else逻辑,因为寄存器默认输出保持不变,所以不需要写这几个逻辑变量维持不变的逻辑。但是对于初学者,我的建议是写上,不然可能过段时间你就看不懂你代码了。)然后在COMPUTE状态下,如果没有运算指定的次数,则一直算,算完以后便可以跳转到DONE状态了。这一部分其实跟你写PYTHON区别不大了。因为状态机的思路本身就和软件很类似。 最后看一些always_ff块外面有没有一些别的跟state相关的逻辑,这里没有 最后留了一个作业作为project。让你只用两个状态(甚至一个)实现该功能。提示你可以使用assign语句赋值fibo_out和done信号。 两个状态实现其实很简单,大家应该能发现,那个DONE状态本身就是冗余的,我们运算完成以后,可以直接从COMPUTE跳转到IDLE状态即可。至于一个状态实现,那也不能称为状态机了,大家可以用一系列的if/else语句实现,也不难。 最后对于题目所要求的的,用testbench检测运算结果是否正确。首先我解释一下什么是testbench。我们完成设计以后,它只是一个功能模块,我们不知道它功能是否正确,因此我们需要写一个tb文件,里面例化了你的设计,然后有逻辑语句驱动你的设计的输入变量,并且对你的输出变量进行比对,判断对比结果是否正确。更多的概念可以自己搜一下什么是testbench。 此外,关于testbench例化模块,是个很麻烦的事情。大家可以搜一下TerosHDL,我一般使用这个直接生成tb。很方便。当然,这个生成的tb由于不知道你设计的逻辑功能,仅仅是一个简单的模块例化加输入信号赋0/1而已。如何进行比对呢?我的建议是可以加入python进行辅助验证。以这个斐波那契数列为例,大家用Python产生一个txt文件,为绝对正确的斐波那契数列,一般称为golden model或reference model。然后在testbench中大家将输出的结果与之前产生的txt进行比对即可(readmemh函数)。比对成功则PASS,否则FAIL。用脚本辅助IC设计的思想是非常重要的,当然大家也可以不用Python,用MATLAB或者C语言也是一样的。 |