前言
本系列为FPGA设计实例,基于Verilog HDL,题目一般是我在网上看到的一些FPGA相关的实验题目,基本会是一个实际场景的系统实现,而不是简单单元的设计,这是为了能更全面的练习,这些实例一般是可以基于FPGA进行实现的,因为正好手里有一块zynq板子,所以想把这个东西用起来,之前做一个卷积核,但是把ip集成到zynq上和arm核协同验证时一直不成功,所以希望也可以学习一下zynq的软硬件协同使用。
以上是本系列的目的,OK,废话不多说,让我们直接开始第一个开发实例:自动售货机系统的设计。来源:哈工大MOOC。
用状态机设计一个自动售货机
它的投币口每次只能投入一枚五角或一元的硬币。投入一元五角钱硬币后机器自动给出一杯饮料,投入2元钱(2枚一元硬币)后,在给出硬币的同时找零一枚五角的硬币,投币时仅支持一枚一枚投。
信号定义
clk # 时钟信号输入
rst # 系统复位信号
half_yuan # 投入五角硬币信号,信号为“1”代表投入五角
one_yuan # 投入一元硬币信号,信号为“1”代表投入一元
half_out # 找零信号,"1"代表找零五角
dispense # 售出信号,“1“代表售出一杯饮料
collect # 取走饮料信号,“1”代表购买者取走饮料
状态机
规定三种状态:IDLE——未投币,half——投币五角,one——投币一元。
状态转移图:
状态转移信号为——one_yuan half_yuan/dispense half_out,示例:01/10
自动售货机的Verilog描述:
售货机模块的Verilog代码如下:
`timescale 1ns / 1ps
module vending_machine(
clk,
one_yuan,
half_yuan,
collect,
half_out,
dispense,
rst
);
//定义端口
parameter idle=2'b00,half=2'b01,one=2'b10;
input one_yuan,half_yuan,rst,clk;
output reg collect,half_out,dispense;
reg[1:0] ST; //当前状态
//控制逻辑实现
always @(posedge clk) begin
if(rst)
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= idle;
end
else
case(ST)
idle:
if(half_yuan)
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= half;
end
else if(one_yuan)
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= one;
end
else
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= idle;
end
half:
if(half_yuan)
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= one;
end
else if(one_yuan)
begin
dispense <= 1;
collect <= 1;
half_out <= 0;
ST <= idle;
end
else
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= half;
end
one:
if(half_yuan)
begin
dispense <= 1;
collect <= 1;
half_out <= 0;
ST <= idle;
end
else if(one_yuan)
begin
dispense <= 1;
collect <= 1;
half_out <= 1;
ST <= idle;
end
else
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= one;
end
default:
begin
dispense <= 0;
collect <= 0;
half_out <= 0;
ST <= idle;
end
endcase
end
//
endmodule
同时编写testbench激励:
`timescale 1ns / 1ps
module VM_tb();
reg clk;
reg rst;
reg half_yuan;
reg one_yuan;
wire collect;
wire half_out;
wire dispense;
vending_machine vm1(
.one_yuan(one_yuan),
.half_yuan(half_yuan),
.collect(collect),
.half_out(half_out),
.dispense(dispense),
.clk(clk),
.rst(rst)
);
always #10 clk = ~clk;
always@(posedge clk)
begin
one_yuan = {$random}%2;
half_yuan = ~one_yuan;//连续投币,每次投入一枚硬币
end
initial begin
clk = 0;
rst = 0;
one_yuan = 0;
half_yuan = 0;
#20
rst = 1;
#40
rst = 0;
#1000
$stop;
end
endmodule
testbench激励中假设我们是连续投币,每次只能投一枚一元或五角硬币,硬币足够时就给出饮料,需要找零时即找零。
测试波形
在Vivado中跑仿真,波形截图如下:
从图中可以看到,黄色线前的时钟周期,先投了一元,然后黄色线处又接着投了一元,下一时钟周期给出饮料并且找零五角;给饮料的时钟周期也投了一元,下一周期再投了一元,然后再下一周期给出饮料并且找零,符合我们对售货机的行为描述。
改进之处
对于售货机来说,如果检测到投币后隔了较久才投下一个币,当前代码检测后one_yuan/half_yuan信号不归零,信号在下一个时钟沿可能会重复捕获,所以需要设置一次投币信号检测,检测完成后复原,避免投币信号未发生变化时被多次捕获。
当然也有别的方法来避免重复捕获,比如及时复原投币信号。
还可以为售货机添加其他功能,比如选择货物,补货提醒等更加实用的功能,模拟更多的需求场景。
总结
本实例是一个经典的开发实例,作为练手,可以用来熟悉状态机的设计与实现,对基本hdl语法练习以及基本的激励编写和调试能力练习也有很大的帮助。
版权声明:本文为博主作者:__Retr0原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/platinumrong/article/details/132803821