FPGA学习:状态机化简&Testbench
FPGA学习 :状态机及其化简&Testbench
这里练习状态机利用《Verilog HDL高级数字设计》中的一个示例设计,该设计将从低位到高位串行输入的BCD码转换为从低位到高位串行输出的余三码,利用状态机判断每位的状态然后根据输入的数据判断输出数据。具体题目如下: 说句实话,这个题目有点为了用状态机而用状态机的意思了,要将串行输入BCD以余三码串行输出的方式我能想到的其实很简单,完全不需要状态机,用四个DFF做缓冲串转并,然后把转完的数加上0011即可,最后再用四个DFF并转串输出。 但是人家既然要求了要用状态机,那就做呗,顺便研究下状态机化简😅
状态转移图及其化简
首先根据题目条件,将依次从低到高输入4个2进制数,那也就意味着从状态机的开始状态需要再接收四个数据才能回到开始状态,那么这将是一个有四层递进关系树的状态机,结合每一位的真值表,可以将这个状态机的状态转移图画出来: 看上图可以知道,这个状态机显然有很多可以化简的地方,那么具体什么条件下可以将状态机化简/什么情况下两个状态等价呢? 其实这本书上也明确讲了,即如果两个状态对于相同的输入序列,都有相同的输出序列和下一状态,那么这两个状态称为等价状态,在转移图中可以只保留其中一个。 那么接下里就可以开始状态化简,一般先从最底层分支开始,即绿色部分的状态,下面都是显而易见的相同状态:
- S6和S10都是输入将输入直接输出(输入0输出0,输入1输出1),且下一状态均为IDLE
- S7/S8/S2都是将输入直接输出(输入0输出0),且下一状态均为IDLE
- S9/S11/S13都是将输入取反输出,且下一状态均为IDLE
可见,上面列举的第一种和第二种状态其实也可以看作同种状态,因为在这个题里,输入BCD是不会超过9的,因此将绿色状态化简,只保留两个状态即可,即输入和输出相同以及输入和输出相反,如下图所示: 接下来再化简粉红色部分状态:
- S3/S4/S5都是输入0输出1转换到S6;输入1输出0转换到S9(这也太巧了)
那么就轻而易举地化简这一步状态了,最后最简的状态如下图: 化简完就可以进行下一步了
状态机结构设计
状态机分为一/二/三段式三种状态机,一段式状态机很少用,经常使用的是二段和三段式状态机 二段式状态机的第一段是当前状态转移,第二段是下一状态获取以及输出 三段式状态机的第一段是当前状态转移,第二段是下一状态获取,第三段是输出 这里演示一下二段式状态机的写法: 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
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
Testbench仿真激励的设计
设计这个题目的testbench的时候,我一开始是想在rst之后,直接手动这么写: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17initial 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) ;
end1
2
3
4
5parameter 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进制文件
endBCD.txt
里保存如下内容: 1
2
3
4
5
6
7
8
9
100000
0001
0010
0011
0100
0101
0110
0111
1000
10011
2
3
4
5
6
7always @(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没有++和--
endrepeat
,还有while
,for
,forever
等等 为了确认我们这样操作是否正确,还可以通过$write
和$display
在终端里先看一眼是不是我们要输入给B_in的数据,以及它们是不是从低到高输入的 注意,$write
和$display
的不同点在下面代码块的注释中提到了 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15initial 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;
end1
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
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
FPGA学习:状态机化简&Testbench