实验四 自动贩售机的设计与实现
实验资料 实验源码

实验目的

  1. 掌握有限状态机的设计方法;
  2. 能够使用 SystemVerilog 进行三段式状态机的建模。

实验原理

有限状态机分为 Moore 型状态机和 Mealy 型状态机。前者,状态机的输出 仅由当前状态决定,如图 4-1 所示,在状态转换图的绘制中,输出信息标在状态 (圆圈)中。后者,状态机的输出由当前时刻状态和输入共同决定,如图 4-2 所 示,在状态转换图的绘制中,输出信息标在状态转换箭头之上。

采用硬件描述语言进行状态机建模时,建议使用 3 段式。第一段描述状态的 转换(即对状态机中的寄存器进行建模),采用时序逻辑实现;第二段描述状态 转换条件和规律(即对状态机中的次态逻辑进行建模),采用组合逻辑实现;第 三段描述输出逻辑,根据实际设计需要可采用组合逻辑或时序逻辑实现。

实验内容

详情见实验资料中实验指导书pdf或简略版实验指导书

采用有限状态机,基于 SystemVerilog HDL 设计并实现一个报纸自动贩售机。 整个工程的顶层模块如图 4-3 所示,输入/输出端口如表 4-1 所示。使用 4 个七 段数码管实时显示已付款和找零情况。其中,两个数码管对应“已付款”,另两个 数码管对应“找零”,单位为分。通过 1 个拨动开关对数字钟进行复位控制。使用 两个按键模拟投币,其中一个按键对应 5 分,另一个按键对应 1 角。使用 1 个 LED 灯标识出售是否成功,灯亮表示出售成功,否则表示已付款不够,出售失败。 假设报纸价格为 15 分,合法的投币组合包括:

⚫ 1 个 5 分的硬币和一个 1 角的硬币,不找零

⚫ 3 个五分的硬币,不找零

⚫ 1 个 1 角的硬币和一个 5 分的硬币,不找零

⚫ 两个 1 角的硬币是合法的,找零 5 分。

当投入硬币的组合为上面 4 种之一时,则购买成功,LED 灯亮。购买成功后,LED 灯持续亮 10 秒,然后自动熄灭,同时 4 个数码管也恢复为 0。

实验步骤

12

  1. 首先完成使能时钟模块实现分频的功能。时钟沿周期为40ns,而扫描周期为1ms。说明没经过25000个时钟沿才产生一个有效的时钟沿
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
`timescale 1ns / 1ps

module en_clock #(parameter N = 25000) (
input sys_clk,
input sys_rst_n,
output logic cout
);

//时钟周期为40ns
//每经过25000个时钟上升沿为1ms
//每1ms输出一个有效上升沿
//分频
logic [31 : 0] temp;

always_ff@(posedge sys_clk) begin
if(!sys_rst_n) temp <= 0;
else if(temp == N-1) temp <= 0;
else temp <= temp + 1;
end

always_ff@(posedge sys_clk) begin
if(!sys_rst_n) cout <= 1'b0;
else if(temp == N-1) cout <= 1'b1;
else cout <= 1'b0;
end

// always_ff@(posedge sys_clk) begin
// if(sys_rst_n==1) begin
// if(temp==N-1) begin
// temp <= 0;
// cout <= 1'b1;
// end
// else begin
// temp <= temp + 1;
// end
// end
// else begin
// cout <= 1'b0;
// end
// end

endmodule
  1. 然后完成边沿检测模块。由于已经分频,所以理论上应该是每1ms检测到一个有效时钟上升沿。用移位寄存器完成设计。
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
`timescale 1ns / 1ps

module chk_asc(
input sys_clk,
input sys_rst_n,
input i_start,
output logic en
);

//现态和次态
logic Q0, Q1;

always_ff@(posedge sys_clk) begin
if(sys_rst_n) begin
//i_start为高电平时,代表有效上升沿
//即每1ms一次
Q0 <= i_start;
Q1 <= Q0;
end
else begin
Q0 <= 0;
Q1 <= 0;
end
end

assign en = (~Q1)&Q0;

endmodule
  1. 接着完成计时器模块。售出一份报纸后灯会亮10s,这里的计时功能由计时器来完成。与使能时钟类似,需要分频。时钟沿的周期为40ns,所以10s需要经过250000000个时钟沿。
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
`timescale 1ns / 1ps

module next_state #(parameter M = 250000000)(
input sys_clk,
input sys_rst_n,
input en,
output logic state
);

logic [31 : 0] temp;
//灯要持续亮10s

always_ff@(posedge sys_clk) begin
if(!sys_rst_n) begin
temp <= 0;
state <= 0;
end
//en=1,灯亮10s后灭
else if(en) begin
if(temp < M - 1) temp <= temp + 1;
else begin
temp <= 0;
state <= 1;
end
end
else if(!en) begin
temp <= 0;
state <= 0;
end
end

endmodule
  1. 之后完成有限状态机模块。售卖机共有5个状态。可以用case语句完成
    • 已投入¥0
    • 已投入¥0.05
    • 已投入¥0.10
    • 已投入¥0.15
    • 已投入¥0.20
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
`timescale 1ns / 1ps

module State_Mache (
input sys_clk,
input sys_rst_n,
input coin5_asc,
input coin10_asc,
input reset_flg,
output logic [7 : 0] price,
output logic [7 : 0] change,
output logic open
);

logic [3 : 0] current_state, next_state;
always_ff@(posedge sys_clk) begin
if(!sys_rst_n) current_state <= 4'd0;
else current_state <= next_state;
end

always_ff@(posedge sys_clk) begin
if(!sys_clk) begin
price <= 8'd0;
change <= 8'd0;
open <= 1'b0;
end
else begin
//通过现态和输入得到次态
case(current_state)
4'd0: begin
//¥0
if(coin5_asc) next_state <= 4'd1;
else if(coin10_asc) next_state <= 4'd2;
end
4'd1: begin
//¥0.05
if(coin5_asc) next_state <= 4'd2;
else if(coin10_asc) next_state <= 4'd3;
end
4'd2: begin
//¥0.10
if(coin5_asc) next_state <= 4'd3;
else if(coin10_asc) next_state <= 4'd4;
end
4'd3: begin
//¥0.15
if(reset_flg) next_state <= 4'd0;
end
4'd4: begin
//¥0.20
if(reset_flg) next_state <= 4'd0;
end
endcase
end
end

//通过次态得到输出结果
always@(*) begin
if(!sys_rst_n) begin
price = 8'd0;
change = 8'd0;
open = 1'b0;
end
else begin
case(next_state)
4'd0: begin
//¥0
price = 8'd0;
change = 8'd0;
open = 1'b0;
end

4'd1: begin
//¥0.05
price = 8'd5;
change = 8'd0;
open = 1'b0;
end

4'd2: begin
//¥0.10
price = 8'd10;
change = 8'd0;
open = 1'b0;
end

4'd3: begin
//¥0.15
price = 8'd15;
change = 8'b0;
open = 1'b1;

end

4'd4: begin
//¥0.20
price = 8'd20;
change = 8'd5;
open = 1'b1;
end
endcase
end
end

endmodule
  1. 接着完成BCD码转七段数码管信号模块。同样使用case语句完成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
`timescale 1ns / 1ps

module BCD_2_7Dig(
input [3 : 0] BCD,
output logic [7 : 0] a_to_g
);
always@(*) begin
case(BCD)
4'b0000:a_to_g=8'b11000000;
4'b0001:a_to_g=8'b11111001;
4'b0010:a_to_g=8'b10100100;
4'b0011:a_to_g=8'b10110000;
4'b0100:a_to_g=8'b10011001;
4'b0101:a_to_g=8'b10010010;
4'b0110:a_to_g=8'b10000010;
4'b0111:a_to_g=8'b11011000;
4'b1000:a_to_g=8'b10000000;
4'b1001:a_to_g=8'b10010000;
default: a_to_g=8'b11111111;
endcase
end

endmodule
  1. 完成七段数码管扫描模块
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
`timescale 1ns / 1ps

module _7Dig_Scan(
input sys_clk,
input sys_rst_n,
input cout,
input [7 : 0] price,
input [7 : 0] change,
output logic [3 : 0] BCD,
output logic [3 : 0] an
);

//七段数码管每1ms扫描一次
always_ff@(posedge sys_clk) begin
if(!sys_rst_n) an <= 4'd1;
//与实验三相同,四个七段数码管并不是同时亮,同时灭
//an为4个七段数码管的使能端
else if(cout) an <= {an[2 : 0], an[3]};
end

always@(*) begin
if(an == 1) BCD <= price[3 : 0];
else if(an == 2) BCD <= price[7 : 4];
else if(an == 4) BCD <= change[3 : 0];
else if(an == 8) BCD <= change[7 : 4];
end
endmodule
  1. 最后在vend.sv中进行结构建模
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
77
78
`timescale 1ns / 1ps

module vend(
input sys_clk, sys_rst_n,
input coin5, coin10,
output [3 : 0] an,
output [7 : 0] a_to_g,
output open
);

//使能时钟的输出
//每1ms一次高电平
logic cout;
//是否投入一个5分硬币或一个一角硬币
logic coin5_asc,coin10_asc;
//投入硬币的总额以及找零
logic [7:0] price,change;
//转换为BCD码
logic [7:0] price_BCD,change_BCD;
logic [3:0] BCD;
//复位标志
logic reset_flg;

en_clock en_clock(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cout(cout)
);
chk_asc coin5_chk_asc(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.i_start(coin5),
.en(coin5_asc)
);
chk_asc coin10_chk_asc(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.i_start(coin10),
.en(coin10_asc)
);
next_state next_state(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.en(open),
.state(reset_flg)
);
State_Mache State_Mache(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.coin5_asc(coin5_asc),
.coin10_asc(coin10_asc),
.reset_flg(reset_flg),
.price(price),
.change(change),
.open(open)
);
bin2bcd_0 price_bin2bcd_0(
.bin(price),
.bcd(price_BCD)
);
bin2bcd_0 change_bin2bcd_0(
.bin(change),
.bcd(change_BCD)
);
_7Dig_Scan _7Dig_Scan(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.cout(cout),
.price(price_BCD),
.change(change_BCD),
.BCD(BCD),
.an(an)
);
BCD_2_7Dig BCD_2_7Dig(
.BCD(BCD),
.a_to_g(a_to_g)
);
endmodule

实验结果

波形图

5

FPGA截图

  • 投入硬币之前

1

  • 投入一个1角硬币

2

  • 再投入一个1角硬币

3

  • 10s后复位

4

FPGA视频

各模块电路图

  • 使能时钟生成器

6

  • 边沿检测电路

7

  • 贩售状态机

8

  • 七段数码管扫描显示

9

  • BCD码转七段数码管模块

10

  • 七段数码管使能信号生成模块

13

状态转换图

11