上一个帖子中我做了固定参数的uart方案,如果模块的参数需要可设置该怎么做呢?奇偶校验位,停止位可变如何实现的呢?今天我们就来看看,使用状态机的写法如何实现这些。作者看过各种写法,对比来说,使用状态机的写法简单而又功能完善,易于学习和理解。话不多说,上代码。

uart字符帧结构
|
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
`define NONE 0 `define ODD 1 `define EVEN 2 `define MARK 3 `define SPACE 4 `define STOPBIT_ONE 0 `define STOPBIT_ONEHALF 1 `define STOPBIT_TWO 2 module uart_st #( parameter BAUD = 9600, //波特率 parameter CLK_F = 50_000_000, //时钟频率 parameter PARITY = `ODD, //校验方式 parameter DATABITS = 8, //数据位长度 parameter STOPBIT = `STOPBIT_ONEHALF //停止位长度 ) ( input wire clk, input wire rst, input wire [ DATABITS - 1: 0 ] data, input wire enable, output reg tx, output reg busy //指示外部调用模块,在忙状态时候,不能接受新的数据 ); localparam [31:0] CNT_MAX = CLK_F / BAUD; // 【1】缓存外部数据 data reg [ DATABITS - 1 : 0 ] data_r; always@( posedge clk or posedge rst ) begin if ( rst ) data_r <= 0; else if ( enable && busy == 0 ) data_r <= data; else data_r <= data_r; end //【2】用状态机来完成发送 reg [ 31: 0 ] cnt; reg [ 3: 0 ] num; //[1] 定义状态空间 localparam FSM_IDLE = 0; //空闲状态 localparam FSM_START = 1; //起始位状态 localparam FSM_DATA = 2; //数据位状态 localparam FSM_PARITY = 3; //校验位状态 localparam FSM_STOP = 4; //停止位状态 wire baud_tick = (cnt == CNT_MAX - 1) ? 1'b1 : 1'b0; //校验位 reg ifparity; //是否有检验位 reg parity_value; //校验位的值 //[2] 定义状态变量 reg [ 2: 0 ] cstate; //[3] 完成状态机 //每一个状态里:状态转移和判断、根据状态输出 always@( posedge clk or posedge rst ) begin if ( rst ) begin cstate <= FSM_IDLE; busy <= 0; tx <= 1; cnt <= 0; num <= 0; end else case ( cstate ) FSM_IDLE : begin //空闲状态 tx <= 1; //空闲时发送1 if ( enable ) //需要发送数据 begin cstate <= FSM_START; busy <= 1; cnt <= 0; end else cstate <= FSM_IDLE; end FSM_START : begin tx <= 0; //发送起始位 if( cnt == CNT_MAX - 1 ) begin cnt <= 0; cstate <= FSM_DATA; end else begin cnt <= cnt + 1; cstate <= FSM_START; end end FSM_DATA : begin tx <= data_r[ num ]; //并串转换 if ( cnt == CNT_MAX - 1 && num == DATABITS - 1 ) //单bit位接受完成,并且接受到指定bit位长度 begin cstate <= ifparity ? FSM_PARITY : FSM_STOP; //根据有无校验位来决定状态跳转 cnt <= 0; num <= 0; end else if ( cnt == CNT_MAX - 1 && num < DATABITS - 1 ) //单bit位接受完成,但还没接收完所有的bit位 begin num <= num + 1; cnt <= 0; end else begin cnt <= cnt + 1; cstate <= FSM_DATA; num <= num; end end FSM_PARITY : begin tx <= parity_value; //将计算出来的校验位发送出去 if ( cnt == CNT_MAX - 1 ) begin cnt <= 0; cstate <= FSM_STOP; end else begin cnt <= cnt + 1; cstate <= FSM_PARITY; end end FSM_STOP : begin tx <= 1; //停止位的值是1,下面是控制发送长度(时间) case ( STOPBIT ) `STOPBIT_ONE : begin if ( cnt == CNT_MAX - 1 ) //1位停止位 begin cnt <= 0; cstate <= FSM_IDLE; busy <= 0; end else begin cnt <= cnt + 1; cstate <= FSM_STOP; end end `STOPBIT_ONEHALF : begin if ( cnt == ( CNT_MAX[31:0] + CNT_MAX[31:1] ) - 1 ) //1.5位停止位,CNT_MAX[31:0] + CNT_MAX[31:1] begin $display("cnt = %d", cnt); cnt <= 0; cstate <= FSM_IDLE; busy <= 0; end else begin cnt <= cnt + 1; cstate <= FSM_STOP; end end `STOPBIT_TWO : begin if ( cnt == ( ( CNT_MAX << 1 ) - 1 ) ) //2位停止位,CNT_MAX<<1 begin cnt <= 0; cstate <= FSM_IDLE; busy <= 0; end else begin cnt <= cnt + 1; cstate <= FSM_STOP; end end default : begin if ( cnt == CNT_MAX - 1 ) begin cnt <= 0; cstate <= FSM_IDLE; busy <= 0; end else begin cnt <= cnt + 1; cstate <= FSM_STOP; end end endcase end default : begin cstate <= FSM_IDLE; busy <= 0; tx <= 1; cnt <= 0; num <= 0; end endcase end always@( * ) begin if ( rst ) begin ifparity = 0 ; parity_value = 0 ; end else case ( PARITY ) `NONE : begin ifparity = 0 ; parity_value = 0 ; end `ODD : begin ifparity = 1 ; parity_value = ~( ^ data_r ) ; end `EVEN : begin ifparity = 1 ; parity_value = ^ data_r ; end `MARK : begin ifparity = 1 ; parity_value = 1 ; end `SPACE : begin ifparity = 1 ; parity_value = 0 ; end default : begin ifparity = 0 ; parity_value = 0 ; end endcase end endmodule |
仿真程序:
|
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 |
module tb(); reg clk = 0; always #10 clk = ~clk; wire busy; reg [7:0] data; reg tx_en = 0; reg rst = 0; initial begin data = 0; tx_en = 0; rst = 1; #20; rst = 0; #10; @(posedge clk); repeat(10) begin while(busy) @(posedge clk); data = data + 1; tx_en = 1; @(posedge clk); tx_en = 0; @(posedge clk); end #200; $stop; end wire tx; `define NONE 0 `define ODD 1 `define EVEN 2 `define MARK 3 `define SPACE 4 `define STOPBIT_ONE 0 `define STOPBIT_ONEHALF 1 `define STOPBIT_TWO 2 uart_st # ( .BAUD (115200), .CLK_F (50_000_000), .PARITY (`NONE), .DATABITS (8), .STOPBIT (`STOPBIT_TWO) ) uart_st_inst ( .clk (clk), .rst (rst), .data (data), .enable (tx_en), .tx (tx), .busy (busy) // 指示外部调用模块,在忙的时候,不接收新的数据 ); endmodule |
仿真波形:
