核心内容摘要
冰雪之下的炽热:探寻“甘雨腿法ちゃんこつま”的独特魅力
《FPGA经典例程及解读--基于xilinx K325T平台》系列导航本专栏主要针对与想学习FPGA的同学从基础的点灯到之后的复杂功能实战例程从入门到进阶通过这些例程的学习和了解希望可以帮助你从一个FPGA小白进阶到FPGA中级阶段能够处理工作中大多数的FPGA使用场景。
本篇是该系列的
内容上一篇FPGA例程6UART串口通讯协议解析_fpga串口通信程序详解-CSDN博客下一篇关注我第一时间获取更新1 概述上一篇我们介绍了UART串口的协议本篇我们以UART串口接收程序为案例说明一下状态机的写法。
2 状态机的结构简述状态机的详细理论介绍大家可以查看这一篇FPGA基础知识六状态机设计实战--从概念到可靠实现的完整指南-CSDN博客
1 状态机三要素状态不是简单的计数器而是系统当前所处的工作状态转移条件状态变化的触发事件需要明确且无歧义输出在特定状态下产生的动作响应”
2 状态机常见的的编码方式顺序二进制编码按
的顺序编码资源最省但多个状态位同时变化易产生毛刺格雷码编码按
的顺序编码状态转化时只有一位变化可靠性高功耗低但编码没有顺序二进制的方式直观热编码按
的方式编码时序性能好但需要更多的资源。
3 三段式状态机经典模板如下// 第一段状态寄存器时序逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) current_state IDLE; else current_state next_state; end // 第二段下一状态逻辑组合逻辑 always (*) begin next_state current_state; // 默认保持当前状态 case (current_state) IDLE: begin if (start_signal) next_state START; end START: begin if (ready_signal) next_state WORK; else if (timeout) next_state IDLE; end WORK: begin if (done_signal) next_state DONE; end DONE: begin next_state IDLE; end default: next_state IDLE; endcase end // 第三段输出逻辑Moore风格 - 时序逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin output_bus b0; control_sig 1b0; end else begin // 默认输出值 output_bus b0; control_sig 1b0; case (current_state) // Moore输出只依赖于当前状态 START: control_sig 1b1; WORK: begin output_bus work_data; control_sig 1b1; end DONE: output_bus result_data; endcase end end为什么推荐这种结构时序清晰状态注册和输出生成都在时钟沿完成避免毛刺组合逻辑只用于状态转移判断综合友好工具能够很好地进行时序分析和优化3 UART串口接收模块程序设计参考FPGA例程6UART串口通讯协议解析_fpga串口通信程序详解-CSDN博客的串口接收协议。
1 接收模块配置说明串口接收模块是个参数可配置模块BAUD_DIV依据波特率和采样时钟计算出的分频系数例如采样时钟50MHz我们希望波特率为115200bps那BAUD_DIV 50_000000 / 115200 434D_WORD_NUM数据位我们使用的是8位那这里就是8有的时候会用到6位或者7位修改这个值即可。
2 接收模块状态转移流程说明接收模块状态机转移流程如下IDLE是空闲状态上电后进入IDLE状态如果rx为低我们认为接收到了串口的起始位进入状态STARTSTART接收完起始位即等待一个数据位的时间后进入数据接收状态RECEIVERECEIVE接收数据位我们设定为8位等待8位都接收完成之后进入STOPSTOP停止位之后再一次进入IDLE状态等待下一包数据即可。
3 接收状态机设计我们使用三段式状态机完成接收模块的设计下面是第一段和第二段主要控制状态机的状态转移逻辑其中第一段是时序逻辑内部使用第二段是组合逻辑内部使用这里一定要注意在同一个always块中绝对不可以混用阻塞赋值和非阻塞赋值。
// // 状态机定义 localparam IDLE 4d0; localparam START 4d1; localparam RECEIVE 4d2; localparam STOP 4d3; // --------- 控制状态机 reg [3:0] current_state,next_state; always (posedge clk or negedge rstn) begin if (!rstn) begin current_state IDLE; end else begin current_state next_state; end end // ----------- 主状态机 always (*) begin next_state current_state; case(current_state) IDLE:begin if (!rx_c
begin next_state START; end end START:begin if(baud_clk)begin next_state RECEIVE; end end RECEIVE:begin if ((bit_cnt D_WORD_NUM-1 ) (baud_cnt BAUD_DIV-
) begin // 已接收8位 next_state STOP; end end STOP:begin next_state IDLE; end default:begin next_state IDLE; end endcase end第三段为状态输入输出逻辑一般按照我们的功能分为独立的always块例如1接收数据位计数块always块内部仅产生bit_cnt当在RECEIVE状态下每采样一次bit_cnt1// ------------ 接收数据位计数 reg [3:0] bit_cnt; // 位计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin bit_cnt 4d0; end else begin if (baud_clk) begin if (current_state START) begin bit_cnt 4d0; end else if (current_state RECEIVE) begin bit_cnt bit_cnt 4d1; end else begin bit_cnt bit_cnt; end end end end例如2数据包有效的使能信号当我们接收到rx的下降沿时认为接收到有效数据当我们接收够D_WORD_NUM的数据位后认为该包数据接收完成always块中仅产生start信号。
// ----------- 生成 单帧接收使能信号 reg start; // start有效时 才接收数据 always (posedge clk or negedge rstn) begin if (!rstn) begin start 1b0; end else begin if (!rx_c0 rx_c
begin start 1b1; end else if ((baud_cnt BAUD_DIV-
(bit_cnt D_WORD_NUM-
) begin start 1b0; end else begin start start; end end end最终的实现代码如下timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: // // Create Date: 2026/01/16 16:29:11 // Design Name: // Module Name: uart_rx // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision
01 - File Created // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module uart_rx # ( parameter D_WORD_NUM 4d8, parameter BAUD_DIV 16d434 )( input clk, input rstn, input uart_rx_i, output [D_WORD_NUM-1:0] uart_rx_data_o, output uart_rx_done ); // parameter D_WORD_NUM 8; // parameter BAUD_DIV 16d434; // // 状态机定义 localparam IDLE 4d0; localparam START 4d1; localparam RECEIVE 4d2; localparam STOP 4d3; // --------- 控制状态机 reg [3:0] current_state,next_state; always (posedge clk or negedge rstn) begin if (!rstn) begin current_state IDLE; end else begin current_state next_state; end end // ----------- 主状态机 always (*) begin next_state current_state; case(current_state) IDLE:begin if (!rx_c
begin next_state START; end end START:begin if(baud_clk)begin next_state RECEIVE; end end RECEIVE:begin if ((bit_cnt D_WORD_NUM-1 ) (baud_cnt BAUD_DIV-
) begin // 已接收8位 next_state STOP; end end STOP:begin next_state IDLE; end default:begin next_state IDLE; end endcase end // // ------------- 输入信号同步 reg rx_c0,rx_c1; always (posedge clk) begin rx_c0 uart_rx_i; rx_c1 rx_c0; end // ------------ 接收数据位计数 reg [3:0] bit_cnt; // 位计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin bit_cnt 4d0; end else begin if (baud_clk) begin if (current_state START) begin bit_cnt 4d0; end else if (current_state RECEIVE) begin bit_cnt bit_cnt 4d1; end else begin bit_cnt bit_cnt; end end end end // ----------- 生成 单帧接收使能信号 reg start; // start有效时 才接收数据 always (posedge clk or negedge rstn) begin if (!rstn) begin start 1b0; end else begin if (!rx_c0 rx_c
begin start 1b1; end else if ((baud_cnt BAUD_DIV-
(bit_cnt D_WORD_NUM-
) begin start 1b0; end else begin start start; end end end // ------------ 波特率时钟生成 reg [15:0] baud_cnt; // 波特率计数器 always (posedge clk or negedge rstn) begin if (!rstn) begin baud_cnt 16d0; end else begin if (start) begin if (baud_cnt BAUD_DIV -
begin baud_cnt 16d0; end else begin baud_cnt baud_cnt 16d1; end end else begin baud_cnt 16d0; end end end wire baud_clk; // 波特率时钟 assign baud_clk (baud_cnt BAUD_DIV -
? 1b1:1b0; // ------------ 串并转换 wire data_en ; assign data_en (baud_cnt BAUD_DIV
?1b1:1b0; reg [ D_WORD_NUM-1:0] rx_shift; // 接收移位寄存器 always (posedge clk or negedge rstn) begin if (!rstn) begin rx_shift 8d0; end else begin if (data_en (current_state RECEIVE)) begin rx_shift {rx_c1, rx_shift[ D_WORD_NUM-1:1]}; // 右移LSB在前 end else begin rx_shift rx_shift; end end end // -------------- 数据锁存 reg [7:0] data; always (posedge clk or negedge rstn) begin if (!rstn) begin data 8d0; end else begin if (current_state STOP) begin data rx_shift; // 保存接收的数据 end else begin data data; end end end assign uart_rx_data_o data; // ----------------- 生成一帧数据接收完成的标志 reg rx_done; always (posedge clk or negedge rstn) begin if (!rstn) begin rx_done 1b0; end else begin if (current_state STOP) begin rx_done 1b1; end else begin rx_done 1b0; end end end assign uart_rx_done rx_done; // endmodule4
总结本文以串口接收程序为案例讲述了状态机的编写方式我很推荐大家进行这样编写因为这种每个always块中仅产生一个信号的编写方式能让我们更明确这个信号的时序当出现问题或者bug的时候我们也仅需要对一个always块进行分析即可。
那下一篇我们继续来说一说串口发送程序。