这两天终于学到有限状态机的部分了,可以说这道题算是我进入FSM之后很难的一道题。这个题自己第一遍基本是写不出来代码,然后结合CSDN上面一些博主写的代码分析了一下,找到思路重新写了一遍。最后得到的代码也不简洁,基本达到100行,而且用的算法比答案给得更加复杂(请允许我在Verilog中用“算法”这个跟硬件语言关系不大的词语)。
先回顾一下自己现在知道的做FSM的Methodology:
1,将具体问题抽象为逻辑问题:确定输入、输出变量,状态变量(现态与次态);确定电路状态个数,并明确电路各个状态的含义;
2,状态分配(编码):为电路各个状态编码(也就是给出对应的二进制数组,而这个二进制数组往往对应到各个触发器的状态);确定触发器位数,从而确定状态变量的位宽;
3,画电路转换图,要包含输入、输出、次态;
4,根据电路转换图写代码。一般来说,作为初学者,我觉得写三段式代码逻辑比较清晰,如果按照HDLBits的说法,就是:
(1)Transition logic,也就是状态转移。考虑在不同的现态和输入的情况下,次态的情况。这里我们一般先考虑现态,用case语句涵盖现态的各个情况,再在对应模块中用if-else条件句讨论输入的不同取值对应的次态。
(2) Sequential logic, 也就是时序逻辑部分。这里的时序逻辑部分从微观角度来说,其实就是受时钟脉冲控制的触发器的状态变化(因为电路的其他部分都不受时钟脉冲控制)。也就是说考虑在clk的控制下,电路从现态到次态的转变情况;注意由于复位信号的存在,这个部分往往需要讨论同步、异步复位信号。一般都是用if-else语句解决。
(3)Output logic, 也就是输出的部分。这部分一般也只是组合逻辑部分,按照现态和输入的不同情况写对应的输出。今天做的水库问题还只是Moore型FSM,不用考虑输入的影响。
回到这个题目,我认为这个题很难的一点就是审题。审题的困难体现在逻辑抽象,这道题我的第一感受就是很难找到现态、次态。甚至从题目描述来说,我以为我们需要引入三个状态变量,过去的状态用来比较水位高低,现在的状态用来确定FR1等流速阀门,还有个次态留给状态转移。这让我觉得无从下手。
后来反复阅读分析了其他博主的代码,我才大致明白,其实只需要两个状态变量。在这个题里面,状态是用来存储上一次水位检测时的情况的,而我们确定DFR时就是将当前水位(新测出的水位情况,也即输入的S,或次态)跟之前的水位测量情况(现态)作比较。换而言之,输入的水位测量情况既是输入变量,又是电路的次态。而按照这种分析思路,这个题代码的特殊点就在于后面写Output logic时,条件语句讨论的是“次态”的值,这跟我们传统认识中FSM的输出应该讨论的是现态的值不符,其实只是这个题目理解的特殊性而已。实际上按照题意,这里面的“次态”其实就是当前水位,也即“现态”;只不过我们需要引入过去的水位情况作为更早的状态。如果改个说法,我们也可以说过去水位是“早态”,现在水位是“现态”,那么我们的状态转移就发生在“早态”和“现态”之间了,只是不符合我们时序逻辑的用语习惯而已。
一旦明确了这个FSM的现态和次态,我们就可以开始确定电路状态个数并编码了。我们当然可以采用one-hot,但是按照习惯我还是使用了常规编码方式。从题目上面可以看出,水位高度一共有四种,低于S1,在S1和S2之间,在S2和S3之间,高于S3。我们可以简单地按照水位高度,将这个四个状态从小到大对应到0、1、2、3进行编码(这样编码在后面比较水位高度时也有好处,可以将水位高度的比较直接转换为码值的比较)。由于电路只有四个状态,我们的状态变量也只需要两位的位宽就可以了。
reg [1:0] state, next;
//The variable next is the new state, which is equivalent to the outcome of s.
parameter L = 2'b00, S12 = 2'b01, S23 = 2'b10, H = 2'b11;
//Consider 4 states, they are below s1,s1 to s2, s2 to s3, above s3.
值得一提的是在编码过程中,parameter用得比较多。它其实跟C语言里面的define很相似,相当于给一个常数赋个名字。用parameter可以极大方便我们调用电路的状态,同时让代码更易理解。这里我直接用L、H分别代表最低水位和最高水位,用S12和S23代表水位位于S1和S2、S2和S3之间的状态,非常易懂。
下一步就是写状态转移部分。存疑的是,我看到不同博主写的此题的四个状态的代码内容,似乎都默认了状态转换只发生在相邻水位之间,而不考虑跨位转换(辟如从最低位转换成最高位),因此再写状态转换的时候最低位和最高位的次态都分别只有两种情况。我现目前也不知道这是不是题目的什么默认条件。
always @(*)
//Transistion logic, but the question default the transition of levels only happens between two adjacent states and itself.
begin
case (state)
L: begin
if (s == 3'b000)
next = L;
else if (s == 3'b001)
next = S12;
end
S12: begin
if (s == 3'b000)
next = L;
else if (s == 3'b001)
next = S12;
else if (s == 3'b011)
next = S23;
end
S23: begin
if (s == 3'b001)
next = S12;
else if (s == 3'b011)
next = S23;
else if (s == 3'b111)
next = H;
end
H: begin
if (s == 3'b011)
next = S23;
else if (s == 3'b111)
next = H;
end
default next = L;//When the s check nothing, it is equal to the lowest level.
endcase
end
这部分代码在分析出来以后基本是照着转换图写就行了。
下面就是时序逻辑部分,这个部分一般都比较简单。我们还是分别讨论有无重置信号的情况。
always @(posedge clk)//Sequential part(Flip flops part).
begin
if (reset)
state <= L;
else
state <= next;
end
这里还要注意一点,在三段式FSM中,组合逻辑的always块中都用阻塞赋值,而时序逻辑的always块中都用非阻塞赋值。
然后下面就是输出部分。按照题意和我们先前分析的,一旦当前水位(次态)确定,fr1,fr2,fr3就确定了,无非是在确定次态下再讨论两个状态水位高低而已。因此我们这里还是使用case语句先讨论次态的不同情况,再在对应情况下比较次态和现态,也即当前水位和上一次测量时的水位的高低。由于编码的关系,这里我们直接通过对应码值大小来代替水位高低的比较就可以了。
always @(posedge clk)//Output logic.
begin
if (reset||next == L)
{fr1,fr2,fr3,dfr} = 4'b1111;
else begin
case (next)
S12: begin
if (next<state)
{fr1,fr2,fr3,dfr} = 4'b1101;
else if (next>state)
{fr1,fr2,fr3,dfr} = 4'b1100;
else
begin
{fr1,fr2,fr3} = 3'b110;
dfr = dfr;
//When the level of water dosen't change, the value of dfr also keeps, which is important.
end
end
S23: begin
if (next<state)
{fr1,fr2,fr3,dfr} = 4'b1001;
else if (next>state)
{fr1,fr2,fr3,dfr} = 4'b1000;
else
begin
{fr1,fr2,fr3} = 3'b100;
dfr = dfr;
end
end
H: begin
if (next>state)
{fr1,fr2,fr3,dfr} = 4'b0000;
else if (next == state)
begin
{fr1,fr2,fr3} = 3'b000;
dfr = dfr;
end
else
begin
{fr1,fr2,fr3} = 3'b000;
dfr = 1'b1;
end
end
endcase
end
end
这部分我个人的感受就是写起来非常地繁琐(虽然其实逻辑比较清晰)。光这部分代码就占了五十多行。另外值得注意的是当水位没有变化时,dfr也不变,这里可以选择不写dfr=dfr,但是千万不要把水位没有变化时dfr的情况归到0、1这种定态中去。
至此,这道题的三个部分就结束了。我在文末会附上完整代码。另外根据参考答案的比对,很多代码是将这道题的电路状态划分为6个,甚至8个,以上情况代码的量会少一些,大概五十行左右就可以解决。等自己有空的时候再分析一下。
module top_module (
input clk,
input reset,
input [3:1] s,
output fr3,
output fr2,
output fr1,
output dfr
);
reg [1:0] state, next;//The variable next is the new state, which is equivalent to the outcome of s.
parameter L = 2'b00, S12 = 2'b01, S23 = 2'b10, H = 2'b11;//Consider 4 states, they are below s1,s1 to s2, s2 to s3, above s3.
always @(*)//Transistion logic, but the question default the transition of levels only happens between two adjacent states and itself.
begin
case (state)
L: begin
if (s == 3'b000)
next = L;
else if (s == 3'b001)
next = S12;
end
S12: begin
if (s == 3'b000)
next = L;
else if (s == 3'b001)
next = S12;
else if (s == 3'b011)
next = S23;
end
S23: begin
if (s == 3'b001)
next = S12;
else if (s == 3'b011)
next = S23;
else if (s == 3'b111)
next = H;
end
H: begin
if (s == 3'b011)
next = S23;
else if (s == 3'b111)
next = H;
end
default next = L;//When the s check nothing, it is equal to the lowest level.
endcase
end
always @(posedge clk)//Sequential part(Flip flops part).
begin
if (reset)
state <= L;
else
state <= next;
end
always @(posedge clk)//Output logic.
begin
if (reset||next == L)
{fr1,fr2,fr3,dfr} = 4'b1111;
else begin
case (next)
S12: begin
if (next<state)
{fr1,fr2,fr3,dfr} = 4'b1101;
else if (next>state)
{fr1,fr2,fr3,dfr} = 4'b1100;
else
begin
{fr1,fr2,fr3} = 3'b110;
dfr = dfr;//When the level of water dosen't change, the value of dfr also keeps, which is important.
end
end
S23: begin
if (next<state)
{fr1,fr2,fr3,dfr} = 4'b1001;
else if (next>state)
{fr1,fr2,fr3,dfr} = 4'b1000;
else
begin
{fr1,fr2,fr3} = 3'b100;
dfr = dfr;
end
end
H: begin
if (next>state)
{fr1,fr2,fr3,dfr} = 4'b0000;
else if (next == state)
begin
{fr1,fr2,fr3} = 3'b000;
dfr = dfr;
end
else
begin
{fr1,fr2,fr3} = 3'b000;
dfr = 1'b1;
end
end
endcase
end
end
endmodule