FPGA学习:状态机化简&Testbench

FPGA学习 :状态机及其化简&Testbench

这里练习状态机利用《Verilog HDL高级数字设计》中的一个示例设计,该设计将从低位到高位串行输入的BCD码转换为从低位到高位串行输出的余三码,利用状态机判断每位的状态然后根据输入的数据判断输出数据。具体题目如下: 题目要求 说句实话,这个题目有点为了用状态机而用状态机的意思了,要将串行输入BCD以余三码串行输出的方式我能想到的其实很简单,完全不需要状态机,用四个DFF做缓冲串转并,然后把转完的数加上0011即可,最后再用四个DFF并转串输出。 但是人家既然要求了要用状态机,那就做呗,顺便研究下状态机化简😅

状态转移图及其化简

首先根据题目条件,将依次从低到高输入4个2进制数,那也就意味着从状态机的开始状态需要再接收四个数据才能回到开始状态,那么这将是一个有四层递进关系树的状态机,结合每一位的真值表,可以将这个状态机的状态转移图画出来: 状态转移图1 看上图可以知道,这个状态机显然有很多可以化简的地方,那么具体什么条件下可以将状态机化简/什么情况下两个状态等价呢? 其实这本书上也明确讲了,即如果两个状态对于相同的输入序列,都有相同的输出序列和下一状态,那么这两个状态称为等价状态,在转移图中可以只保留其中一个。 那么接下里就可以开始状态化简,一般先从最底层分支开始,即绿色部分的状态,下面都是显而易见的相同状态:

  • S6和S10都是输入将输入直接输出(输入0输出0,输入1输出1),且下一状态均为IDLE
  • S7/S8/S2都是将输入直接输出(输入0输出0),且下一状态均为IDLE
  • S9/S11/S13都是将输入取反输出,且下一状态均为IDLE

可见,上面列举的第一种和第二种状态其实也可以看作同种状态,因为在这个题里,输入BCD是不会超过9的,因此将绿色状态化简,只保留两个状态即可,即输入和输出相同以及输入和输出相反,如下图所示: 状态转移图2 接下来再化简粉红色部分状态:

  • S3/S4/S5都是输入0输出1转换到S6;输入1输出0转换到S9(这也太巧了)

那么就轻而易举地化简这一步状态了,最后最简的状态如下图: 状态转移图3 化简完就可以进行下一步了

状态机结构设计

状态机分为一/二/三段式三种状态机,一段式状态机很少用,经常使用的是二段和三段式状态机 二段式状态机的第一段是当前状态转移,第二段是下一状态获取以及输出 三段式状态机的第一段是当前状态转移,第二段是下一状态获取,第三段是输出 这里演示一下二段式状态机的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
`timescale 1ns/100ps
module BCD_to_Yusan #(
parameter STATE_NUM = 3
)(
input wire B_in,
input wire clk,
input wire rstn,
output wire B_out
);

parameter IDLE = 3'b000;
parameter STATE_0 = 3'b001;
parameter STATE_1 = 3'b010;
parameter STATE_2 = 3'b011;
parameter STATE_3 = 3'b100;
parameter STATE_4 = 3'b101;
parameter STATE_5 = 3'b110;

reg [STATE_NUM - 1 : 0 ] STATE_NOW = 0;
reg [STATE_NUM - 1 : 0 ] STATE_NEXT = 0;

//!fsm_extract
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
STATE_NOW = IDLE;
end
else begin
STATE_NOW = STATE_NEXT;
end
end

reg B_out_r;
//!fsm_extract
always @(posedge clk) begin
case (STATE_NOW)
IDLE:
case (B_in)
1'b0: begin STATE_NEXT = STATE_0; B_out_r = !B_in; end
1'b1: begin STATE_NEXT = STATE_1; B_out_r = !B_in; end
default: STATE_NEXT = IDLE;
endcase

STATE_0:
case (B_in)
1'b0: begin STATE_NEXT = STATE_2; B_out_r = !B_in; end
1'b1: begin STATE_NEXT = STATE_3; B_out_r = !B_in; end
default: STATE_NEXT = IDLE;
endcase

STATE_1:
begin STATE_NEXT = STATE_3; B_out_r = B_in; end

STATE_2:
begin STATE_NEXT = STATE_4; B_out_r = B_in; end

STATE_3:
case (B_in)
1'b0: begin STATE_NEXT = STATE_4; B_out_r = !B_in; end
1'b1: begin STATE_NEXT = STATE_5; B_out_r = !B_in; end
default: STATE_NEXT = IDLE;
endcase

STATE_4:
begin STATE_NEXT = IDLE; B_out_r = B_in; end

STATE_5:
begin STATE_NEXT = IDLE; B_out_r = !B_in; end

default: STATE_NEXT = IDLE;
endcase

end

assign B_out = B_out_r;

endmodule
如果需要改成三段式状态机,只需要将上面代码第二段的输出全部提出来,另写一个always块即可

Testbench仿真激励的设计

设计这个题目的testbench的时候,我一开始是想在rst之后,直接手动这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
initial begin
rstn = 1'b1;
#5 rstn = 1'b0;
#5 rstn = 1'b1;
//B_in从低到高输入0011
B_in = 1'b1
#5 B_in = 1'b1
#5 B_in = 1'b0
#5 B_in = 1'b0
end

//clock generating
real CYCLE_200MHz = 5 ; //200Mhz ~ 5ns
always begin
clk = 0 ; #(CYCLE_200MHz/2) ;
clk = 1 ; #(CYCLE_200MHz/2) ;
end
但是显而易见这种手动输入的方式有个很大的缺点,我写testbench肯定是想把每种情况都test一遍,而且还得测试连续输入连续输出的情况,如果每种情况都这样一个个改B_in的话显得太笨拙太没效率了,那该咋办呢? 那其实很简单,我们在testbench里生成一块memory,把10种BCD码保存在外部文件,然后在testbench里把文件加载到memory就行了:
1
2
3
4
5
parameter ADDR_SIZE = 10 , WORD_SIZE = 4;
reg [WORD_SIZE - 1:0] Ram[0:ADDR_SIZE - 1]; //存储器:生成10个4位寄存器,在verilog2005中,内存存储从左侧数字开始分配,因此左侧值为0
initial begin
$readmemb("BCD.txt",Ram); //readmemb读二进制文件,readmemh读16进制文件
end
其中,BCD.txt里保存如下内容:
1
2
3
4
5
6
7
8
9
10
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
然后再利用循环语句将这些数据赋值给B_in即可,注意,要从低位开始赋值!:
1
2
3
4
5
6
7
always @(posedge clk ) begin
B_in = Ram[addr_count][word_count];
if (word_count == 1'b0) begin
word_count = 3'b011;
addr_count = addr_count + 1'b1;
end else word_count = word_count - 1'b1; //verilog没有++和--
end
当然,verilog里的循环语句不止有repeat,还有while,for,forever等等 为了确认我们这样操作是否正确,还可以通过$write$display在终端里先看一眼是不是我们要输入给B_in的数据,以及它们是不是从低到高输入的 注意,$write$display的不同点在下面代码块的注释中提到了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
initial begin
addr_count = 1'b0;
word_count = 3'b011;

//以下代码作用是四个bit为1组,打印10组数,从低位开始输出
repeat(40) begin
$write("%b",Ram[addr_count][word_count]); //display自动换行,write需要加换行符
if (word_count == 1'b0) begin
$write("\n");
word_count = 3'b011;
addr_count = addr_count + 1'b1;
end else word_count = word_count - 1'b1; //verilog没有++和--
end
#250 $finish;
end
最后我把整个testbench和仿真出来的波形附在下面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
`timescale 1ns/100ps
module BCD_to_Yusan_tb;

parameter ADDR_SIZE = 10 , WORD_SIZE = 4;
// Ports
reg B_in;
reg clk = 0;
reg rstn = 1;
reg [3:0] count;
reg [WORD_SIZE - 1:0] word_count;
reg [ADDR_SIZE - 1:0] addr_count;
wire B_out;
reg [WORD_SIZE - 1:0] Ram[0:ADDR_SIZE - 1]; //存储器:生成10个4位寄存器,在verilog2005中,内存存储从左侧数字开始分配,因此左侧值为0

BCD_to_Yusan
tb (
.B_in (B_in ),
.clk (clk ),
.rstn (rstn ),
.B_out ( B_out)
);

/*iverilog */
initial begin
$dumpfile("BCD_to_Yusan_tb.vcd"); //生成的vcd文件名称
$dumpvars(0, BCD_to_Yusan_tb); //tb模块名称
end
/*iverilog */

initial begin
$readmemb("BCD.txt",Ram); //readmemb读二进制文件,readmemh读16进制文件
end

initial begin
rstn = 1'b0;
#5rstn = 1'b1;
addr_count = 1'b0;
word_count = 3'b011;

//以下代码作用是四个bit为1组,打印10组数,从低位开始输出
repeat(40) begin
$write("%b",Ram[addr_count][word_count]); //display自动换行,write需要加换行符
if (word_count == 1'b0) begin
$write("\n");
word_count = 3'b011;
addr_count = addr_count + 1'b1;
end else word_count = word_count - 1'b1; //verilog没有++和--
end

addr_count = 1'b0;
word_count = 3'b011;
#250 $finish;
end

always @(posedge clk ) begin
B_in = Ram[addr_count][word_count];
if (word_count == 1'b0) begin
word_count = 3'b011;
addr_count = addr_count + 1'b1;
end else word_count = word_count - 1'b1; //verilog没有++和--
end

//clock generating
real CYCLE_200MHz = 5 ; //200Mhz ~ 5ns
always begin
clk = 0 ; #(CYCLE_200MHz/2) ;
clk = 1 ; #(CYCLE_200MHz/2) ;
end

endmodule
仿真波形

作者

Hank.Gan

发布于

2021-08-11

更新于

2021-08-13

许可协议

评论