关于简化状态机的写法

目录

 

 

概述

功能

旧程序

改完之后的程序

仿真

思考

第二次修改

问题1

问题2

回应问题1:

回应sclk改写问题 (问题1):

3、(回应状态机还是需要简化的问题)

疑问1

疑问2

1、验证一下用assign %和always 里面给% 有什么区别

2、sclk直接用clk(思考一下为什么当初要用二分频的sclk呢?是有什么原因吗?用二分频sclk岂不是更复杂吗)


 

概述

    最近在写状态机的时候,被嫌弃写得太冗长了,每一个状态的操作太多了,整个状态机代码量很大,于是开始学习怎么简化状态机的代码。

功能

    首先看一下我要写的spi时序要求

先看写时序,有三条线,sen、sclk、sdata,时序操作可以分为几个部分,

(1) 第一个clk什么都不做。

(2)第二个clk送出数据位0,表示W时序。

(3)第三个clk到第八个clk写入6bits地址。

(4)第九个clk到第二十八个clk写入20bits数据。

(5)第二十九个clk拉低sen,结束状态。

旧程序

这个写时序,我的想法是用58个状态机去实现它,所以我写状态机是这么写的:

最后写完了之后,被嫌弃整个状态机太冗长了,必须要简化。

比如说sclk,实际上是系统时钟的二分频,那么就不用在状态机里面操作了,直接做成时钟就好了。

然后是txstate,实际上每一个状态机里面,txstate的操作都是一样的,所以并不需要每个状态机都写,可以单独拎到外面来。

另外像txdone、trien等寄存器,也都可以根据txstate来操作,可以不写到状态机里面。

我思考了一阵子,最后把状态机重新写了一下,如下:

改完之后的程序

我觉得简化最多的地方就是把txstate和sclk拎出来单独写了,减少了很多状态机的代码,现在状态机里面只剩下数据的操作:

我不把sdataout这条线也拎出来,原因是我认为它写在状态机里面,会让读者更容易读懂时序的操作,因为时序就是按顺序来的,我对sdataout的操作也是按顺序一步一步来的,两者一致。

如果要将sdataout拉出来,就比较做一些组合逻辑进去,这样反而更不直观了。

仿真

做完之后,我仿真过了,两者功能是一致的,如下:

第一种写法:

第二种写法:

思考

写完之后,我想探讨代码简化写法的思路。

对比一下以下两种写法在功能上、系统上有什么不一样。

第一种写法(图中negedge应改为posedge):

第二种写法:

 

上面两种语句,功能上在仿真波形上是一样的,那么到底具体有什么不同呢?

 

很明显的,第二种的写法比较简洁,那么,为什么不所有关于组合逻辑或者时序逻辑的写法的都用第二种呢

其一,是有些数据的传输是一定要用寄存器的

其二,是电路上有局限性

 

 

上述两种写法在电路上的区别如上图所示,可以看出,always块中,其实是在sen信号前面,组合逻辑后面,也就是电路的中间过程中,加入了1个寄存器。

加入了这个寄存器,主要的优势就是在于,分解之后的组合逻辑,在时间上肯定比整体组合锁机的时间短,所以它发生时序违例的工作频率,一定会比assign写法来的更高,也就是说它可以跑到更高的工作频率。

总而言之,always的写法能够满足的工作频率更高,但是要多占用寄存器的资源。

 

第二次修改

 

 

在改完之后,还是存在几个问题:

问题1

首先是这个句子,我使用的%号,我需要知道它在电路上是个什么形态,会综合成什么样子的电路。

像下面这种句子,我可以清楚地知道它是一个比较器,所以,我必须清楚语句会大概综合成什么才可以用它。

 

 

问题2

 这个状态机还是太大了,有没有更方便的方法。

 

回应问题1:

除法器的电路实现

除法的运算过程

与乘法相比,除法的实现较为复杂,运算过程如下:

 

过程:

被除数和余数:将余数和被除数视为一个,共享一个寄存器,初始值为被除数

除数:可视为不断右移,并和被除数相减

商:每个bit依次生成,可视为不断左移

 

除法器的工作流程

要注意的是,与手算相比,电路实现总是将余数减除数,所以如果出现差小于0,要执行回退操作。

怎么会退呢?其实没有真正回退地方法,由于前面执行的是减法,回退只需加回来就可以了。

 

工作过程:

初始化:将8-bit被除数放到“余数寄存器”,4-bit除数放到“除数寄存器”的高4位,将4-bit商寄存器置零

执行减法运算:余数-除数,将结果放到“余数寄存器”

检查余数寄存器的最高位(判断正负)。如果值小于0,回退,商左移,新的最右位设为0;如果值大于或等于0,商左移,新的最右位设为1.

除数寄存器右移一位

检查是否为最后一轮(本例为第5轮)

 

 

回应sclk改写问题 (问题1):

sclk写成二分频有两种方法:

1、直接在本模块写

2、写出一个下级模块,然后例化它

 

3、(回应状态机还是需要简化的问题)

下面是我将

上图这段程序改完之后的状态机程序。

我是用了一个count_data计数器,去代替了数字,通过多用了这么一个寄存器,去简化我的代码,这个状态机综合出来肯定会多占用一些资源,但是可以让我的代码看起来更简短。并且还有一个好处就是,可以读写的数据位宽度可以根据实际情况变化,我只要改DATA_BIT就可以做到。

 

 

疑问1

 

在同一模块里面,先不讨论复位操作,就单纯讨论always触发块

 

always@  (posedge clk)

begin

       sclk<=~sclk;

end

 

always@  (posedge clk)

begin

       txstate<=txstate+1;

end

 

always@(negedge sclk)

begin

       if(txstate==1)

              begin

……

end

end

 

 

我想问的是,negedge sclk 的这个always块,会在sclk=~sclk; 也就是sclk从1→0之后运行吗,是在sclk已经=0的时候判断if,还是在sclk==1的时候判断if。

 

我的理解是,当sclk下降沿出现时,sclk已经是0 了,那么txstate同样和sclk都是非阻塞赋值,txstate一定也已经完成了赋值的操作,所以,txstate是完成赋值后的值,在这个时刻判断,要判断赋值之后的值是哪个,让txstate==赋值之后的值,这个if才会是成立的。在modelsim仿真的结果也说明,确实是txstate赋值三之后的值这个if(txstate==?)才会判断成功,执行begin后面的语句。

如果语句是这么写的,那么就会是另一种情况

always@  (posedge clk)

begin

       sclk<=~sclk;

end

 

always@  (posedge clk)

begin

       txstate<=txstate+1;

end

 

always@(posedge clk)

begin

       if(txstate==1)

              begin

……

end

end

三个块是共用一个时钟clk,那么if判断语句里面,判断的是posedge上升沿时刻,txstate将会是当前值,也就是赋值前的值。因为always块是并行的,非阻塞赋值先计算右边的算式,再赋给左边,而if与右边算式计算同时进行,算式进行是在赋值之前,所以,txstate在判断语句里面是赋值之前的值。

 

 

 

可以,状态机简化了很多。

 

疑问2

1、验证一下用assign %和always 里面给% 有什么区别

 

always块里

上图对应代码如下:

 

 

assign块

 

上图对应代码如下:

 

 

这里的话,发现非阻塞赋值和阻塞赋值的区别就是非阻塞赋值在线的上面多加了一个触发器。

 

 

这里的话,在线路上加入一个寄存器,从建立时间保持时间的角度去分析,这样的电路,建立时间余量将会更长,因为它线路上的组合逻辑被分割了,相应的,组合逻辑少了的话,花在这上面的时间就少了,那么在时钟周期不变的情况下,(建立时间余量=周期-组合逻辑的时间)就会更长,从而,可以让系统跑到更高的工作频率。

 

%在后面的数字不同的时候,综合出的电路是不同的,如上图,是%2的结果,那么%3

是什么样子的电路呢?

明显的,当%后面跟随的数字不同,%综合的结果会变化,总之,有一些复杂。

 

2、sclk直接用clk(思考一下为什么当初要用二分频的sclk呢?是有什么原因吗?用二分频sclk岂不是更复杂吗)

我改写成直接用clk的时钟,发现确实可以做到,我认为,一开始写8bit状态机的时候,看网上别人写的都是sclk是二分频的状态,所以我已经形成了思维定势,觉得sclk就是要二分频来用,而忘了思考它为什么要二分频了,这样很不可取的行为。

做一个事情,虽然说一开始可以参考别人的设计,但是也要记住一个事情,那就是别人的东西,一定一定也不是最好的,所以在参考完别人的设计之后,一定要思考,别人做的这个东西,有什么优点?有什么缺点?我是不是可以做到更优?

 

譬如这次的sclk这个信号,我在以前的设计就可以直接将它接到系统clk上面,

比如这个spi时序

状态0:SCK为0,MOSI为要发送的数据的最高位,即I_data_in[7]

    状态1:SCK为1,MOSI保持不变

    状态2:SCK为0,MOSI为要发送的数据的次高位,即I_data_in[6]

    状态3:SCK为1,MOSI保持不变

    状态4:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[5]

    状态5:SCK为1,MOSI保持不变

    状态6:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[4]

    状态7:SCK为1,MOSI保持不变

    状态8:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[3]

    状态9:SCK为1,MOSI保持不变

    状态10:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[2]

    状态11:SCK为1,MOSI保持不变

    状态12:SCK为0,MOSI为要发送的数据的下一位,即I_data_in[1]

    状态13:SCK为1,MOSI保持不变

    状态14:SCK为0,MOSI为要发送的数据的最低位,即I_data_in[0]

    状态15:SCK为1,MOSI保持不变

别人用16个状态机去实现,那么现在,我觉得太麻烦了,我想用8个状态机去实现,是不是可行?我sclk这里直接接到clk上,会有什么问题。

思考一下他这样做的优势、劣势。

思考一下我把他这个改成8个状态机的优势、劣势。

 

在理论层面,我认为他这个设计完全可以不用将sclk二分频,直接把clk调过来使用,negedge clk实现mosi<=I_data_in。就可以了,我想想也没有什么不可以。

 

思维定势确实会限制人的思想,如果不是老师问我说为什么要二分频,我可能一直都不会去考虑这个sclk的问题。

这里就存在两个东西,

第一个是做事的原则,比如我的老师,他的原则就是能简单的事情就用简单的方式处理就好了,这里能用clk,为什么还要多用一个sclk出来,这就是将事情复杂化了,当然不可否认地,有些事情也要靠经验来指导。

第二个是思维定势,思维定势是人类一定会存在的一种东西,而且,它在某些时候会非常限制我们的思维,所以,为了避免思维被限制,我觉得有几种办法去避免思维定势。(1)向他人详细说明自己做事情的来龙去脉,前因后果,每一步是怎么做的,为什么这么做,这样做的优势和缺点,然后积极听取他人的意见,因为每个人的思维都是不一样的,这样非常有利于开拓思维,避免思维定势。(2)发散思维解决问题,大胆假设,小心求证,并且,做每一件事情都是有理由的,写的每一句代码也都是有理由的,并且有足够说服自己说服他人的理由。

      

猜你喜欢

转载自blog.csdn.net/qq_41034231/article/details/107813833