0%

vendmachine

实验三、自动贩售机的设计和实现

一. 实验目的

  1. 掌握有限状态机的设计方法。;

  2. 能够使用 SystemVerilog 进行三段式状态机的建模。

二. 实验环境

  1. 操作系统:Windows 10 或 Ubuntu 16.04

  2. 开发环境:Xilinx Vivado 2018.2

  3. 硬件平台:远程 FPGA 云平台

三. 实验原理

有限状态机分为 Moore 型状态机和 Mealy 型状态机。前者,状态机的输出仅由当前状态决定,如图 4-1 所示,在状态转换图的绘制中,输出信息标在状态(圆圈)中。

image-20240520144102043

采用硬件描述语言进行状态机建模时,建议使用 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
//第一段,同步时序 always 模块,描述状态的转换
always_ff (posedge clk) begin //同步复位
if(!rst_n)
current_state <= S0;
else
current_state <= next_state; //注意,使用的是非阻塞赋值
end
//第二段,组合逻辑 always 模块,描述状态转移条件判断
always_comb begin
case(current_state)
S1: if(...)
next_state = S2; //阻塞赋值
...
endcase
end
//第三段,同步时序 always 模块(组合逻辑也可以),描述状态机的输出
always @ (posedge clk) begin
...//初始化
case(next_state)
S1: out1 <= 1'b1; //注意是非阻塞逻辑
S2: out2 <= 1'b1;
default:... //default 的作用是免除综合工具综合出锁存器
endcase
end

四. 实验内容

采用有限状态机,基于 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

image-20240520144232012

报纸自动贩售机由 4 部分构成。

⚫ 第一部分是计时器模块,该模块又由 3 个子模块构成,分别是计数器电路、使能时钟生成电路和边沿检测电路。

⚫ 第二部分是整个自动贩售机电路的核心——贩售机状态机。状态机根据投币情况产生“已付款”和“找零”输出。此外,如果已付款超过 15 分,则将 LED 灯点亮,表示出售成功。

⚫ 第三部分是两个 8 位二进制转 BCD 模块,分别将二进制的“已付款”和“找零”值转化为 BCD 编码,即 10 进制数。本实验中,该模块不需要实现,由教师直接提供 IP 使用

⚫ 第四部分是 7 段数码管动态扫描显示模块,它实现“已付款”和“找零”值的最终显示。

image-20240520144412271

完成上述分秒数字钟的设计,需要有以下几点需要注意:

  1. 7 段数码管动态扫描必须采用使能时钟实现,扫描频率为 1KHz(1ms)。

  2. 必须通过边沿检测电路识别“5 分”和“1 角”按键按下产生的上升沿,以用于后续处理。

  3. 用于计时的时钟频率为 25MHz(40ns)。

  4. 由于 7 段数码管扫描周期是 1ms,购买成功后需要等待 10s,从而造成仿真时间过长。为了加快仿真速度,可以在仿真的时候使用较大的计时单位和扫描速度

五. 实验步骤

第一部分、计时器模块

1.使能时钟生成器

首先系统的时钟主频为25MHz,所以时钟周期为1/25MHz = 40ns

我们要产生1ms的使能信号,只需经过1ms/40ns = 25000个时钟周期

因此可以定义一个计时器,每当counter == 24999时,就产生一个使能信号,并把counter重置为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module enable_clock(
input logic sys_clk,
input logic sys_rst_n,
output logic enable_1ms
);

logic [14:0] counter;
//为了加快仿真速度,从25000调整到24
parameter integer ONE_MS = 25000; //输入时钟频率为25MHz

always_ff @(posedge sys_clk)
if(~sys_rst_n)
counter <= 15'd0;
else if(counter == ONE_MS -1)
counter <= 15'd0;
else
counter <= counter + 1;

assign enable_1ms = (counter == ONE_MS -1);
endmodule

2.边沿检测电路

在头歌中已经联系过了边沿检测的书写,主要的原理就是保存上一时刻的信号值,如果上一时刻的信号为0,下一时刻的信号为1,那么就可以检测到一个上升沿。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module edge_detection(
input logic sys_clk,
input logic sys_rst_n,
input logic signal_in,
output logic edge_detection
);

logic signal_in_d; //上一时刻的signal_in

always_ff @(posedge sys_clk)
if(~sys_rst_n)
signal_in_d <= 0;
else
signal_in_d <= signal_in;

assign edge_detection = ~signal_in_d & signal_in; //上升沿
endmodule

通过边沿检测电路,我们可以检测到硬币被投入的时机,并且防止投币被多次计算

3.计数器

这一部分主要是为了在售出成功后保持10s的LED灯亮起,所以要设计一个10s的计数器。我们可以借鉴上面的1ms使能信号的产生,这里无非是250000000个时钟周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module timer_10s(
input logic sys_clk,
input logic sys_rst_n,
input logic start,
output logic done
);

logic[31:0] count;
parameter integer TEN_SECONDS = 250000000;

always_ff @(posedge sys_clk)
if(~sys_rst_n)
count <= 0;
else if(start)
count <= (count == TEN_SECONDS-1) ? 0 : count + 1;
else
count <= 0;

assign done = (count == TEN_SECONDS-1);
endmodule

第二部分、贩售状态机

我们首先定义如下几个状态

1
2
3
4
5
6
7
8
typedef enum logic [2:0] {
IDLE = 3'b000, //空闲
PAID5 = 3'b001,
PAID10 = 3'b010,
PAID15 = 3'b011,
PAID20 = 3'b100,
WAIT_RESET = 3'b101
} state_t;

首先我们根据题目的描述,给出贩售状态机的状态转换图

image-20240520145948080

状态转移比较简单,每一次都是从次态变为现态,代码如下

1
2
3
4
5
6
7
// State transition
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
state <= IDLE;
else
state <= next_state; //次态变为现态
end

次态逻辑里面定义了每种状态下的下一时刻的状态,代码如下

其中timer_done表示10s已经到了,那么就把PAID15和PAID20下一时刻的状态设置为WAIT_RESET

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
// Next state logic
always_comb begin
case (state)
IDLE: begin
if (edge_detected_coin5)
next_state = PAID5;
else if (edge_detected_coin10)
next_state = PAID10;
else
next_state = state;
end
PAID5: begin
if (edge_detected_coin5)
next_state = PAID10;
else if (edge_detected_coin10)
next_state = PAID15;
else
next_state = state;
end
PAID10: begin
if (edge_detected_coin5)
next_state = PAID15;
else if (edge_detected_coin10)
next_state = PAID20;
else
next_state = state;
end
PAID15: begin
// 保持住现在的状态
if(timer_done)
next_state = WAIT_RESET;
else
next_state = state;
end
PAID20: begin
// 保持住现在的状态
if(timer_done)
next_state = WAIT_RESET;
else
next_state = state;
end
WAIT_RESET: next_state = IDLE;
default: next_state = IDLE;
endcase
end

输出逻辑分别定义了price,change,open(表示LED灯是否亮起),这里我多定义了一个LED状态,用led表示

1
2
3
4
5
// Output logic
assign price = (state == WAIT_RESET) ? 8'd0 : total_paid; // Price represents the total paid amount
assign change = (state == WAIT_RESET) ? 8'd0 : change_amount;
assign open = (state == PAID15 || state == PAID20);
assign led = (state == PAID15 || state == PAID20); // LED on for 10 seconds after successful purchase

然后再分别定义total_paid和change_amount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Total paid logic
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
total_paid <= 8'd0;
else if(state == WAIT_RESET) //这句话保证了在出售成功后10s能够自动清零
total_paid <= 8'd0;
else if (edge_detected_coin5)
total_paid <= total_paid + 8'd5;
else if (edge_detected_coin10)
total_paid <= total_paid + 8'd10;
end

// Change calculation logic
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
change_amount <= 8'd0;
else if (state == PAID20) //因为只有PAID20状态为合法状态,所以只需要定义固定的找零值为5
change_amount <= 8'd5;
else
change_amount <= 8'd0;
end

vending_machine_fsm模块代码如下

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
module vending_machine_fsm(
input logic sys_clk,
input logic sys_rst_n,
input logic coin5,
input logic coin10,
output logic [7:0] change,
output logic [7:0] price,
output logic open,
output logic led
);

// 定义枚举类——状态
typedef enum logic [2:0] {
IDLE = 3'b000, //空闲
PAID5 = 3'b001,
PAID10 = 3'b010,
PAID15 = 3'b011,
PAID20 = 3'b100,
WAIT_RESET = 3'b101
} state_t;

state_t state, next_state;
logic [7:0] total_paid;
logic [7:0] change_amount;
logic timer_done;
logic enable_1ms;
logic edge_detected_coin5, edge_detected_coin10;

timer_10s timer_10s_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.start(state == PAID15 || state == PAID20),
.done(timer_done)
);

// Instantiate timer module for edge detection and 1ms enable signal
timer_module timer_module_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.coin5(coin5),
.coin10(coin10),
.enable_1ms(enable_1ms),
.edge_detected_coin5(edge_detected_coin5),
.edge_detected_coin10(edge_detected_coin10)
);

// Output logic
assign price = (state == WAIT_RESET) ? 8'd0 : total_paid; // Price represents the total paid amount
assign change = (state == WAIT_RESET) ? 8'd0 : change_amount;
assign open = (state == PAID15 || state == PAID20);
assign led = (state == PAID15 || state == PAID20); // LED on for 10 seconds after successful purchase

// State transition
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
state <= IDLE;
else
state <= next_state; //次态变为现态
end

// Next state logic
always_comb begin
case (state)
IDLE: begin
if (edge_detected_coin5)
next_state = PAID5;
else if (edge_detected_coin10)
next_state = PAID10;
else
next_state = state;
end
PAID5: begin
if (edge_detected_coin5)
next_state = PAID10;
else if (edge_detected_coin10)
next_state = PAID15;
else
next_state = state;
end
PAID10: begin
if (edge_detected_coin5)
next_state = PAID15;
else if (edge_detected_coin10)
next_state = PAID20;
else
next_state = state;
end
PAID15: begin
// 保持住现在的状态
if(timer_done)
next_state = WAIT_RESET;
else
next_state = state;
end
PAID20: begin
// 保持住现在的状态
if(timer_done)
next_state = WAIT_RESET;
else
next_state = state;
end
WAIT_RESET: next_state = IDLE;
default: next_state = IDLE;
endcase
end

// Total paid logic
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
total_paid <= 8'd0;
else if(state == WAIT_RESET)
total_paid <= 8'd0;
else if (edge_detected_coin5)
total_paid <= total_paid + 8'd5;
else if (edge_detected_coin10)
total_paid <= total_paid + 8'd10;
end

// Change calculation logic
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
change_amount <= 8'd0;
else if (state == PAID20)
change_amount <= 8'd5;
else
change_amount <= 8'd0;
end

endmodule

第三部分、8为二进制转BCD模块

本实验中,该模块不需要实现,由教师直接提供 IP 使用

IP核实例化后结构如下

image-20240520152501255

第四部分、7段数码管动态扫描显示模块

为了实现动态扫描,还是要利用到之前定义的使能时钟生成器

此外,还需要实现七段数码管的译码器,即把十进制0~9翻译成对应的8位数码管电路(本实验是8位,最高位始终无效),注意是共阳极低电平有效,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module seven_segment_decoder(
input logic [3:0] digit,
output logic [7:0] seg
);

always_comb begin
case (digit)
4'd0: seg = 8'b11000000; // 0
4'd1: seg = 8'b11111001; // 1
4'd2: seg = 8'b10100100; // 2
4'd3: seg = 8'b10110000; // 3
4'd4: seg = 8'b10011001; // 4
4'd5: seg = 8'b10010010; // 5
4'd6: seg = 8'b10000010; // 6
4'd7: seg = 8'b11111000; // 7
4'd8: seg = 8'b10000000; // 8
4'd9: seg = 8'b10010000; // 9
default: seg = 8'b11111111; // Blank
endcase
end

endmodule

在实现4位七段数码管显示模块时,应当实例化上面的两个模块。由于是4位需要来回扫描,因此可以定义一个scan_state来表示扫描到的位置,每次扫描到对应的位置,都使能相应的数码管,从而显示不同位置上的数字。这些数字从左到右分别是change_bcd[7:4], change_bcd[3:0], paid_bcd[7:4], paid_bcd[3:0]。

模块代码如下:

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
module seven_segment_display(
input logic sys_clk,
input logic sys_rst_n,
input logic [7:0] paid_bcd,
input logic [7:0] change_bcd,
output logic [7:0] a_to_g,
output logic [3:0] an
);

// Internal signals
logic [1:0] scan_state;
logic [3:0] digit;
logic [7:0] seg;
logic enable_1ms;

// Instantiate the enable clock module to generate 1ms enable signal
enable_clock enable_clock_inst (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.enable_1ms(enable_1ms)
);

// Instantiate the segment decoder
seven_segment_decoder decoder (
.digit(digit),
.seg(seg)
);

// Scan state machine
always_ff @(posedge sys_clk) begin
if (~sys_rst_n)
scan_state <= 2'd0;
//为了加快仿真速度,这里调整为50个时钟周期(1/10 时间)
else if (enable_1ms) // 25MHz clock / 1KHz = 25000 如果计数器 counter 达到24999(即1ms),则将 scan_state 加1。
scan_state <= (scan_state == 2'd3) ? 2'd0 : scan_state + 2'd1;//该变量用于跟踪当前正在扫描的数码管,2'd0到2'd3分别表示4个数码管。
end

// Digit and anode control
always_comb begin
case (scan_state)
2'd0: begin
digit = paid_bcd[3:0];
an = 4'b0001; // Enable AN0
end
2'd1: begin
digit = paid_bcd[7:4];
an = 4'b0010; // Enable AN1
end
2'd2: begin
digit = change_bcd[3:0];
an = 4'b0100; // Enable AN2
end
2'd3: begin
digit = change_bcd[7:4];
an = 4'b1000; // Enable AN3
end
default: begin
digit = 4'd0;
an = 4'b0000;
end
endcase
end

// Assign segment output
assign a_to_g = seg;

endmodule

以上,便完成了所有子模块的实现,现在可以构造顶层模块vend

第五部分、顶层模块

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
module vend(
input sys_clk, sys_rst_n,
input coin5, coin10,
output [3 : 0] an,
output [7 : 0] a_to_g,
output open
);

logic led;
logic [7:0] paid_bin, change_bin;
logic [7:0] paid_bcd, change_bcd;

// Instantiate FSM
vending_machine_fsm fsm (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.coin5(coin5),
.coin10(coin10),
.change(change_bin),
.price(paid_bin),
.open(open),
.led(led)
);

// Instantiate bin2bcd for paid and change
bin2bcd bin2bcd_paid (
.bin(paid_bin),
.bcd(paid_bcd)
);

bin2bcd bin2bcd_change (
.bin(change_bin),
.bcd(change_bcd)
);

// Instantiate 7-segment display
seven_segment_display display (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.paid_bcd(paid_bcd),
.change_bcd(change_bcd),
.a_to_g(a_to_g),
.an(an)
);

endmodule

第六部分、仿真测试

由于本实验的模块过多,所以我采用了单元测试的方法,分别测试了timer_module, vending_machine_fsm, seven_segment_display, vend模块,以下便写出测试的代码和仿真结果

1.timer_module

为了加快仿真速度,我们把SIMULATION_CLK_PERIOD设置为4,仿真等待时间设置为20(总仿真时间仅有1000ns),把使能时钟中的25000改为24

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
`timescale 1ns / 1ps

module timer_module_tb;

// Parameters for simulation
localparam integer SIMULATION_CLK_PERIOD = 4; // 4ns for 250MHz clock to speed up simulation
localparam integer SIMULATION_WAIT_TIME = 20; // 20ns for each test case observation

// Inputs
logic sys_clk;
logic sys_rst_n;
logic coin5;
logic coin10;

// Outputs
logic enable_1ms;
logic edge_detected_coin5;
logic edge_detected_coin10;

// Instantiate the timer module
timer_module uut (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.coin5(coin5),
.coin10(coin10),
.enable_1ms(enable_1ms),
.edge_detected_coin5(edge_detected_coin5),
.edge_detected_coin10(edge_detected_coin10)
);

// Clock generation
initial begin
sys_clk = 0;
forever #(SIMULATION_CLK_PERIOD / 2) sys_clk = ~sys_clk;
end

// Stimulus process
initial begin
// Initialize Inputs
sys_rst_n = 0;
coin5 = 0;
coin10 = 0;

// Apply reset
#(SIMULATION_WAIT_TIME);
sys_rst_n = 1;

// Test edge detection for coin5
#(SIMULATION_WAIT_TIME);
coin5 = 1;
#(SIMULATION_WAIT_TIME);
coin5 = 0;
#(SIMULATION_WAIT_TIME * 5); // Wait to observe edge detection

// Test edge detection for coin10
#(SIMULATION_WAIT_TIME);
coin10 = 1;
#(SIMULATION_WAIT_TIME);
coin10 = 0;
#(SIMULATION_WAIT_TIME * 5); // Wait to observe edge detection

// Observe enable_1ms signal (accelerated to observe in short time)
#(SIMULATION_WAIT_TIME * 20); // Short wait to observe 1ms signal

// End of simulation
$stop;
end

endmodule

仿真结果

image-20240520154650111

可以看到,在检测到coin5和coin10上升沿的时候,边沿检测的值为1

2.vending_machine_fsm

这里可以分四种情况来测,我这里只列举出一种情况(5+5+10)

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
`timescale 1ns / 1ps

module vending_machine_fsm_tb;

// Parameters for simulation
localparam integer SIMULATION_CLK_PERIOD = 4; // 4ns for 250MHz clock to speed up simulation
localparam integer SIMULATION_WAIT_TIME = 20; // 20ns for each test case observation

// Inputs
logic sys_clk;
logic sys_rst_n;
logic coin5;
logic coin10;

// Outputs
logic [7:0] change;
logic [7:0] price;
logic open;
logic led;

// Instantiate the vending machine FSM module
vending_machine_fsm uut (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.coin5(coin5),
.coin10(coin10),
.change(change),
.price(price),
.open(open),
.led(led)
);

// Clock generation
initial begin
sys_clk = 0;
forever #(SIMULATION_CLK_PERIOD / 2) sys_clk = ~sys_clk;
end

// Stimulus process
initial begin
// Initialize Inputs
sys_rst_n = 0;
coin5 = 0;
coin10 = 0;

// Apply reset
#(SIMULATION_WAIT_TIME);
sys_rst_n = 1;

// Test case 4: Insert 5 cents, then another 5 cents, and then 10 cents to reach 20 cents
#(SIMULATION_WAIT_TIME);
coin5 = 1;
#(SIMULATION_WAIT_TIME);
coin5 = 0;
#(SIMULATION_WAIT_TIME * 2); // Short wait to observe state change

// Insert second 5 cents
#(SIMULATION_WAIT_TIME);
coin5 = 1;
#(SIMULATION_WAIT_TIME);
coin5 = 0;
#(SIMULATION_WAIT_TIME * 2); // Short wait to observe state change

// Insert 10 cents
#(SIMULATION_WAIT_TIME);
coin10 = 1;
#(SIMULATION_WAIT_TIME);
coin10 = 0;
#(SIMULATION_WAIT_TIME * 2); // Short wait to observe state change

// End of simulation
$stop;
end

endmodule

仿真结果

image-20240520155139873

3.seven_segment_display

我分别测试了paid_bcd = 15, change_bcd = 5 和 paid_bcd = 20, change_bcd = 10,两种情况,这两种情况真实中并不能出现,该测试仅仅是为了观察动态扫描的每一位是否正常显示

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
`timescale 1ns / 1ps

module seven_segment_display_tb;

// Parameters for simulation
localparam integer SIMULATION_CLK_PERIOD = 4; // 1ns for 1GHz clock to speed up simulation
localparam integer SIMULATION_WAIT_TIME = 20; // 10ns for each test case observation

// Inputs
logic sys_clk;
logic sys_rst_n;
logic [7:0] paid_bcd;
logic [7:0] change_bcd;

// Outputs
logic [7:0] a_to_g;
logic [3:0] an;

// Instantiate the seven segment display module
seven_segment_display uut (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.paid_bcd(paid_bcd),
.change_bcd(change_bcd),
.a_to_g(a_to_g),
.an(an)
);

// Clock generation
initial begin
sys_clk = 0;
forever #(SIMULATION_CLK_PERIOD / 2) sys_clk = ~sys_clk;
end

// Stimulus process
initial begin
// Initialize Inputs
sys_rst_n = 0;
paid_bcd = 8'h00;
change_bcd = 8'h00;

// Apply reset
#(SIMULATION_WAIT_TIME);
sys_rst_n = 1;

// Test case: Display paid amount = 15, change amount = 5
#(SIMULATION_WAIT_TIME);
paid_bcd = 8'h15; // 15 in BCD
change_bcd = 8'h05; // 5 in BCD
#(SIMULATION_WAIT_TIME * 10); // Observe for a while

// Test case: Display paid amount = 20, change amount = 10
#(SIMULATION_WAIT_TIME);
paid_bcd = 8'h20; // 20 in BCD
change_bcd = 8'h10; // 10 in BCD
#(SIMULATION_WAIT_TIME * 10); // Observe for a while

// End of simulation
$stop;
end

endmodule

仿真结果

image-20240520155531944

将对应的a_to_g还原,可以发现这里的实现逻辑是正确的

4.vend

这里我测试了两种情况,投入15分和投入20分,两种情况都能实现动态扫描(an[3:0]),且每位动态扫描的过程中都能够正确的显示,并且实现了投入15分后等待10s后(仿真时将时间加快了)会自动清零重新开始计数。

仿真结果

image-20240520160354874

至此,所有模块的仿真测试均通过。

六. 实验结果

image-20240520161341223

image-20240520161355079

image-20240520161402310

image-20240520161411064

image-20240520161420887

-------------本文结束感谢您的阅读-------------