题目
我们仍以可乐机为背景,一瓶可乐的价格还是 2.5 元。用按键控制投币(加入按键消抖功能),可以投 0.5 元硬币和 1 元硬币,投入 0.5 元后亮一个灯,投入 1 元后亮 2 个灯,投入 1.5 元后亮 3 个灯,投入 2 元后亮 4 个灯,如果投币后 10s 不再继续进行投币操作则可乐机回到初始状态。投入 2.5 元后出可乐不找零,此时 led 灯实现单向流水操作,流水 10s后自动停止;投入 3 元后出可乐找零,此时 led 灯实现双向流水操作,流水 10s 后自动停止。这里也有复位键,其功能是终止本次投币操作,使可乐机立刻回到初始状态。
流程图

代码
1. 顶层模块 cola_machine.v
module cola_machine
(
input wire sys_clk, // 50MHz 时钟
input wire sys_rst_n, // 全局复位,低电平有效
input wire key_half_raw, // 原始0.5元按键
input wire key_one_raw, // 原始1元按键
input wire key_rst_raw, // 原始复位按键
output reg [3:0] led, // 4个LED灯
output reg po_cola, // 出可乐信号
output reg po_money // 找零信号
);
// 消抖后的按键信号
wire key_half_sync;
wire key_one_sync;
wire key_rst_sync;
// 状态机输出信号
wire [2:0] state_code; // 状态编码,传递给LED模块
wire po_cola_w;
wire po_money_w;
// ================ 按键消抖模块实例化 ================
key_filter #(.CNT_MAX(20'd999_999)) u_filter_half (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in(key_half_raw),
.key_flag(key_half_sync)
);
key_filter #(.CNT_MAX(20'd999_999)) u_filter_one (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in(key_one_raw),
.key_flag(key_one_sync)
);
key_filter #(.CNT_MAX(20'd999_999)) u_filter_rst (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_in(key_rst_raw),
.key_flag(key_rst_sync)
);
// ================ 核心状态机模块实例化 ================
cola_fsm u_fsm (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key_half(key_half_sync),
.key_one(key_one_sync),
.key_rst(key_rst_sync),
.state_code(state_code),
.po_cola(po_cola_w),
.po_money(po_money_w)
);
// ================ LED控制模块实例化 ================
led_controller u_led_ctrl (
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.state_code(state_code),
.po_cola(po_cola_w),
.led(led)
);
// 输出赋值
assign po_cola = po_cola_w;
assign po_money = po_money_w;
endmodule
2. 核心状态机模块 mealy_cola_fsm.v
module mealy_cola_fsm
(
input wire sys_clk,
input wire sys_rst_n,
input wire key_half,
input wire key_one,
input wire key_rst,
output reg [2:0] current_state,
output wire po_cola,
output wire po_money
);
// ================ 参数定义 ================
parameter IDLE = 3'd0;
parameter HALF = 3'd1;
parameter ONE = 3'd2;
parameter ONE_HALF = 3'd3;
parameter TWO = 3'd4;
// ================ 内部信号 ================
reg [2:0] next_state;
reg [26:0] timeout_cnt;
reg timeout_10s;
// ================ 第一段状态机:时序逻辑,描述状态转移 ================
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
current_state <= IDLE;
else if (key_rst || timeout_10s)
current_state <= IDLE;
else
current_state <= next_state;
end
// ================ 超时计数器 ================
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
timeout_cnt <= 0;
else if (current_state != IDLE && !key_half && !key_one && !key_rst)
timeout_cnt <= timeout_cnt + 1'b1;
else
timeout_cnt <= 0;
end
assign timeout_10s = (timeout_cnt >= 27'd500_000_000);
// ================ 组合逻辑:计算下一状态 ================
always @(*) begin
case (current_state)
IDLE:
if (key_half)
next_state = HALF;
else if (key_one)
next_state = ONE;
else
next_state = IDLE;
HALF:
if (key_half)
next_state = ONE;
else if (key_one)
next_state = ONE_HALF;
else
next_state = HALF;
ONE:
if (key_half)
next_state = ONE_HALF;
else if (key_one)
next_state = TWO;
else
next_state = ONE;
ONE_HALF:
if (key_half)
next_state = TWO;
else if (key_one)
next_state = IDLE; // 1.5 + 1 = 2.5 → 出可乐
else
next_state = ONE_HALF;
TWO:
if (key_half)
next_state = IDLE; // 2 + 0.5 = 2.5 → 出可乐
else if (key_one)
next_state = IDLE; // 2 + 1 = 3 → 出可乐+找零
else
next_state = TWO;
default:
next_state = IDLE;
endcase
end
// ================ 第二段状态机:组合逻辑,描述 Mealy 输出 ================
// 注意:虽然是组合逻辑,但为了清晰和可维护性,仍使用 `always @(*)` 块
always @(*) begin
case (current_state)
ONE_HALF:
if (key_one) begin // 1.5 + 1 = 2.5
po_cola = 1'b1;
po_money = 1'b0;
end else begin
po_cola = 1'b0;
po_money = 1'b0;
end
TWO:
if (key_half) begin // 2 + 0.5 = 2.5
po_cola = 1'b1;
po_money = 1'b0;
end else if (key_one) begin // 2 + 1 = 3
po_cola = 1'b1;
po_money = 1'b1;
end else begin
po_cola = 1'b0;
po_money = 1'b0;
end
default:
po_cola = 1'b0;
po_money = 1'b0;
endcase
end
endmodule
3. LED控制模块 led_controller.v
module led_controller (
input wire sys_clk,
input wire sys_rst_n,
input wire [2:0] current_state,
input wire po_cola,
input wire po_money,
output reg [3:0] led
);
parameter IDLE = 3'd0;
parameter HALF = 3'd1;
parameter ONE = 3'd2;
parameter ONE_HALF = 3'd3;
parameter TWO = 3'd4;
reg [26:0] clk_div;
reg [3:0] led_reg;
reg dir;
reg in_single_flow; // 记录是否处于单向流水灯模式
reg in_double_flow; // 记录是否处于双向流水灯模式
wire led_tick = (clk_div == 27'd100_000_000 - 1);
// ================ 时钟分频计数器 ================
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_div <= 0;
else
clk_div <= clk_div + 1'b1;
end
// ================ LED 基础状态 ================
always @(*) begin
case (current_state)
HALF: led_reg = 4'b0001;
ONE: led_reg = 4'b0011;
ONE_HALF: led_reg = 4'b0111;
TWO: led_reg = 4'b1111;
default: led_reg = 4'b0000;
endcase
end
// ================ 流水灯效果 ================
always @(posedge sys_clk or negedge sys_rst_n) begin
reg last_po_cola;
reg last_po_money;
if (!sys_rst_n) begin
led <= 4'b0000;
dir <= 1'b0;
in_single_flow <= 0;
in_double_flow <= 0;
end else begin
// 检测模式切换边沿
if (po_cola && !po_money && (!in_single_flow)) begin
// 刚进入单向流水灯
led <= 4'b1000; // 强制从最左侧开始
in_single_flow <= 1;
in_double_flow <= 0;
end else if (po_cola && po_money && (!in_double_flow)) begin
// 刚进入双向流水灯
led <= 4'b1000; // 强制从最左侧开始
dir <= 1'b0; // 强制向右开始
in_single_flow <= 0;
in_double_flow <= 1;
end else if (!po_cola) begin
// 退出流水灯模式
led <= led_reg;
in_single_flow <= 0;
in_double_flow <= 0;
end else if (po_cola && !po_money) begin
// 单向流水灯
if (led_tick)
led <= {led[2:0], led};
end else if (po_cola && po_money) begin
// 双向流水灯
if (led_tick) begin
if (dir == 1'b0) begin // 向右
if (led == 4'b0001) begin
led <= 4'b0010;
dir <= 1'b1;
end else begin
led <= {led[2:0], led};
end
end else begin // 向左
if (led == 4'b1000) begin
led <= 4'b0100;
dir <= 1'b0;
end else begin
led <= {led[0], led[3:1]};
end
end
end
end
end
end
endmodule