• FPGA—可乐机拓展训练题(状态机)


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

       套用三要素法来分析:

    输入:不投币、投入 0.5 元硬币、投入 1 元硬币;

    输出:不出可乐/不找零、出可乐/不找零、出可乐/找零;

    状态:可乐机中有 0 元、可乐机中有 0.5 元、可乐机中有 1 元、可乐机中有 1.5 元、可乐机中有 2 元、可乐机中有 2.5 元、可乐机中有 3 元。

    1. 模块框图

    2. 状态转换图

    3. RTL代码

    3.1 按键消抖部分

           实现按键消抖,注意有两个输入故调用两次。效果为当检查到按键被按下后,输出的key_flag被拉高一个时钟,作为标志信号输入给下一个模块。

    1. `timescale 1ns/1ns
    2. module key_filter
    3. #(
    4. parameter CNT_MAX = 20'd999_999
    5. )
    6. (
    7. input wire sys_clk ,
    8. input wire sys_rst_n ,
    9. input wire key_in ,
    10. output reg key_flag
    11. );
    12. reg [19:0] cnt_20ms ; //计数器
    13. //cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
    14. always@(posedge sys_clk or negedge sys_rst_n)
    15. if(sys_rst_n == 1'b0)
    16. cnt_20ms <= 20'b0;
    17. else if(key_in == 1'b1)
    18. cnt_20ms <= 20'b0;
    19. else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
    20. cnt_20ms <= cnt_20ms;
    21. else
    22. cnt_20ms <= cnt_20ms + 1'b1;
    23. //key_flag:当计数满20ms后产生按键有效标志位
    24. //且key_flag在999_999时拉高,维持一个时钟的高电平
    25. always@(posedge sys_clk or negedge sys_rst_n)
    26. if(sys_rst_n == 1'b0)
    27. key_flag <= 1'b0;
    28. else if(cnt_20ms == CNT_MAX - 1'b1)
    29. key_flag <= 1'b1;
    30. else
    31. key_flag <= 1'b0;
    32. endmodule

    3.2 complex_fsm部分

            实现随着状态切换后小灯的各种显示效果。注意影响状态切换的条件除了投币还有10s有效时间。使用两类均采用时序逻辑的 always 块,第一类 always 块描述状态(state)的转移为第一段状态机,第二类 always 块描述数据的输出为第二段状态机,所以写状态机一般则采用两段式。

           总结一下套用的格式有哪些主要部分构成:第一部份端口列表部分; 第二部份状态编码部; 第三部份是定义的状态变量; 第四部份分为第一段状态机和第二段状态机。一共有四部分,写状态机代码的时候根据这种格式依次编写,非常容易的就可以实现。

    1. `timescale 1ns/1ns
    2. module complex_fsm
    3. #(
    4. parameter CNT_MAX = 25'd24_999_999, //0.5s定时器计时
    5. parameter CNT_KEY = 20'd999_999 //20ns按键稳定状态
    6. )
    7. (
    8. input wire sys_clk ,
    9. input wire sys_rst_n ,
    10. input wire pi_money_one , //投币1元
    11. input wire pi_money_half , //投币0.5元
    12. output reg [3:0] led
    13. );
    14. //只有七种状态,使用独热码
    15. parameter IDLE = 7'b0000001;
    16. parameter HALF = 7'b0000010;
    17. parameter ONE = 7'b0000100;
    18. parameter ONE_HALF = 7'b0001000;
    19. parameter TWO = 7'b0010000;
    20. parameter TWO_HALF = 7'b0100000;
    21. parameter THREE = 7'b1000000;
    22. reg [6:0] state ;
    23. reg [3:0] led_a ;
    24. reg [3:0] led_b ;
    25. reg [3:0] led_c ;
    26. reg [24:0] cnt ;
    27. reg [4:0] cnt_10s = 5'd21;
    28. reg move ;
    29. reg po_money ;
    30. reg po_cola ;
    31. wire [1:0] pi_money;
    32. wire po_money_half;
    33. wire po_money_one ;
    34. wire cola_flag;
    35. //pi_money:为了减少变量的个数,用 位拼接 把输入的两个1bit信号拼接成1个2bit信号
    36. //投币方式可以为:不投币(00)、投0.5元(01)、投1元(10),每次只投一个币
    37. assign pi_money = {po_money_one, po_money_half};
    38. //满足出可乐条件的所有情况
    39. assign cola_flag = (state == TWO && pi_money == 2'b01) || (state == TWO &&
    40. pi_money == 2'b10) || (state == ONE_HALF && pi_money == 2'b10);
    41. //第一段状态机,当前状态state如何根据输入跳转到下一状态
    42. always@(posedge sys_clk or negedge sys_rst_n)
    43. if(sys_rst_n == 1'b0)
    44. state <= IDLE;
    45. else case(state)
    46. IDLE : if(pi_money == 2'b01) //有几个输入就有几种跳转情况
    47. state <= HALF;
    48. else if(pi_money == 2'b10)
    49. state <= ONE;
    50. else
    51. state <= IDLE;
    52. HALF : if(pi_money == 2'b01)
    53. state <= ONE;
    54. else if(pi_money == 2'b10)
    55. state <= ONE_HALF;
    56. else if(cnt_10s == 5'd20)
    57. state <= IDLE;
    58. ONE : if(pi_money == 2'b01)
    59. state <= ONE_HALF;
    60. else if(pi_money == 2'b10)
    61. state <= TWO;
    62. else if(cnt_10s == 5'd20)
    63. state <= IDLE;
    64. ONE_HALF: if(pi_money == 2'b01)
    65. state <= TWO;
    66. else if(pi_money == 2'b10)
    67. state <= TWO_HALF;
    68. else if(cnt_10s == 5'd20)
    69. state <= IDLE;
    70. TWO : if(pi_money == 2'b01)
    71. state <= TWO_HALF;
    72. else if(pi_money == 2'b10)
    73. state <= THREE;
    74. else if(cnt_10s == 5'd20)
    75. state <= IDLE;
    76. TWO_HALF: if(cnt_10s == 5'd20) //10秒没有投币
    77. state <= IDLE;
    78. else if(pi_money == 2'b01) //继续投币 0.5元
    79. state <= HALF;
    80. else if(pi_money == 2'b10) //继续投币 1元
    81. state <= ONE;
    82. THREE : if(cnt_10s == 5'd20) //10秒没有投币
    83. state <= IDLE;
    84. else if(pi_money == 2'b01) //继续投币 0.5元
    85. state <= HALF;
    86. else if(pi_money == 2'b10) //继续投币 1元
    87. state <= ONE;
    88. default : state <= IDLE;
    89. endcase
    90. //第二段状态机,cnt:计数器计数 500ms
    91. always@(posedge sys_clk or negedge sys_rst_n)
    92. if(sys_rst_n == 1'b0)
    93. cnt <= 25'b0;
    94. else if(cnt == CNT_MAX)
    95. cnt <= 25'b0;
    96. else if(cnt_10s != 5'd21) //开始计时
    97. cnt <= cnt + 1'b1;
    98. //第二段状态机,10s计数器
    99. always@(posedge sys_clk or negedge sys_rst_n)
    100. if(sys_rst_n == 1'b0)
    101. cnt_10s <= 5'd21;
    102. else if((pi_money != 2'd0) //开始计时
    103. cnt_10s <= 5'd0;
    104. else if(cnt == CNT_MAX)
    105. cnt_10s <= cnt_10s + 1'b1;
    106. else if(cnt_10s == 5'd20)
    107. cnt_10s <= 5'd0 ;
    108. //第二段状态机,led_a:投币控制led状态
    109. always@(posedge sys_clk or negedge sys_rst_n)
    110. if(sys_rst_n == 1'b0)
    111. led_a <= 4'b1111;
    112. else if(state == TWO)
    113. led_a <= 4'b0000;
    114. else if(state == ONE_HALF)
    115. led_a <= 4'b0001;
    116. else if(state == ONE)
    117. led_a <= 4'b0011;
    118. else if(state == HALF)
    119. led_a <= 4'b0111;
    120. else if(state == IDLE)
    121. led_a <= 4'b1111;
    122. //第二段状态机,led_b:led 单向循环流水
    123. always@(posedge sys_clk or negedge sys_rst_n)
    124. if(sys_rst_n == 1'b0)
    125. led_b <= 4'b0001;
    126. else if(led_b == 4'b1000 && cnt == CNT_MAX)
    127. led_b <= 4'b0001;
    128. else if(cnt == CNT_MAX)
    129. led_b <= led_b << 1'b1; //左移
    130. //第二段状态机,led_c:led双向循环流水
    131. always@(posedge sys_clk or negedge sys_rst_n)
    132. if(sys_rst_n == 1'b0)
    133. led_c <= 4'b0001;
    134. else if(move == 1'b0 && cnt == CNT_MAX)
    135. led_c <= led_c >>1'b1; //右移
    136. else if(move == 1'b1 && cnt == CNT_MAX)
    137. led_c <= led_c <<1'b1; //左移
    138. else
    139. led_c <= led_c ;
    140. //led循环流水使能信号
    141. always@(posedge sys_clk or negedge sys_rst_n)
    142. if(sys_rst_n == 1'b0)
    143. move <= 1'b0;
    144. else if(led_c == 4'b1000)
    145. move <= 1'b0;
    146. else if(led_c == 4'b0001)
    147. move <= 1'b1;
    148. //根据状态机控制led状态
    149. always@(posedge sys_clk or negedge sys_rst_n)
    150. if(sys_rst_n == 1'b0)
    151. led <= 4'b1111;
    152. else if((state == THREE)&&(cnt_10s != 5'd20))
    153. led <= led_c;
    154. else if((state == TWO_HALF)&&(cnt_10s != 5'd20))
    155. led <= led_b;
    156. else
    157. led <=~led_a;
    158. //第二段状态机,描述当前状态state和输入pi_money如何影响po_cola输出
    159. always@(posedge sys_clk or negedge sys_rst_n)
    160. if(sys_rst_n == 1'b0)
    161. po_cola <= 1'b0;
    162. else if(cola_flag == 1'b1)
    163. po_cola <= 1'b1;
    164. else
    165. po_cola <= 1'b0;
    166. //第二段状态机,描述当前状态state和输入pi_money如何影响po_money输出
    167. always@(posedge sys_clk or negedge sys_rst_n)
    168. if(sys_rst_n == 1'b0)
    169. po_money <= 1'b0;
    170. else if((state == TWO) && (pi_money == 2'b10))
    171. po_money <= 1'b1;
    172. else
    173. po_money <= 1'b0;
    174. //分别调用两个按键模块
    175. key_filter
    176. #(
    177. .CNT_MAX(CNT_KEY)
    178. )
    179. key_filter_inst1
    180. (
    181. .sys_clk (sys_clk ),
    182. .sys_rst_n (sys_rst_n ),
    183. .key_in (pi_money_half ),
    184. .key_flag (po_money_half )
    185. );
    186. key_filter
    187. #(
    188. .CNT_MAX(CNT_KEY)
    189. )
    190. key_filter_inst2
    191. (
    192. .sys_clk (sys_clk ),
    193. .sys_rst_n (sys_rst_n ),
    194. .key_in (pi_money_one),
    195. .key_flag (po_money_one)
    196. );
    197. endmodule

    5. 总结

    1. 编写状态机的步骤,格式。

    2. 如何绘制状态转换图,抓住“三要素”。

    3. 处理LED输出按显示效果分类后输出。

  • 相关阅读:
    Android 进阶——系统启动之SystemServer创建并启动PackageManagerService服务(十一)
    红黑树(1)——为什么要有红黑树
    笔记本无线网卡MAC一直改动
    AWTK-MODBUS 发布,欢迎一起来完善。
    1949-2020年全国31省铁路里程数据
    一次jenkins-kubernetes服务报错排查记录 (Request Header Fields Too Large)
    java毕业设计家政客户服务管理系统(附源码、数据库)
    引用类型详解
    二叉树的遍历
    在 Flutter 中自定义画笔 Painter
  • 原文地址:https://blog.csdn.net/m0_72885897/article/details/130819923