UART串口通信-基于FIFO

说明:

  1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
  2. 本文记录使用FPGA实现简单UART串口通信-基于FIFO

修订历史:

文档名称 版本 作者 时间 备注
UART串口通信-基于FIFO v1.0.0 DuRuofu 2024-01-28 首次建立

UART串口通信-基于FIFO

代码:https://github.com/DuRuofu/FPGA_Learning/tree/master/06_uart_fifo

目标

实现收发双缓冲的串口回环实验

一、整体框图

顶层模块:

子模块模块:

整体框图:

二、模块波形图

以9600波特率为例。

接收模块:

发送模块:

三、代码编写

3.1 串口接收模块

模块代码:

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
`timescale 1ns / 1ns

// 串口接收模块
module uart_rx
#(
parameter UART_BSP = 'd9600, // 波特率
parameter CLK_FREQ = 'd50_000_000 // 时钟频率
)
(
input wire sys_clk, // 系统时钟
input wire sys_rst_n, // 系统复位信号
input wire rx, // 串口接收数据

output reg [7:0] po_data, // 模块输出数据
output reg po_flag // 串口接收数据完成标志
);

parameter BAUD_CNT__MAX = CLK_FREQ/UART_BSP; // 波特率计数器最大值

// 中间变量
reg rx_reg_1; // 用于同步数据
reg rx_reg_2; // 用于缓冲数据
reg rx_reg_3; // 用于缓冲数据
reg start_flag; // 用于判断起始位
reg work_en; // 用于使能接收模块
reg [15:0] baud_cnt;// 用于计数波特率
reg bit_flag; // 读取标志
reg [3:0] bit_cnt; // 读取位数计数
reg [7:0] rx_data; // 读取到的数据
reg rx_flag ; // 读取完成标志

// 对输入信号进行同步
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rx_reg_1 <= 1'b1; // 初值为1
end
else begin
rx_reg_1 <= rx;
end
end

// 用于同步数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rx_reg_2 <= 1'b1; // 初值为1
end
else begin
rx_reg_2 <= rx_reg_1;
end
end

// 用于同步数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rx_reg_3 <= 1'b1; // 初值为1
end
else begin
rx_reg_3 <= rx_reg_2;
end
end

// 开始标志信号 start_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
start_flag <= 1'b0; // 初值为0
end
else if((rx_reg_3 == 1'b1) && (rx_reg_2== 1'b0) && (work_en == 1'b0)) begin
start_flag <= 1'b1;
end
else begin
start_flag <= 1'b0;
end
end

// 使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
work_en <= 1'b0; // 初值为0
end
// 开始接收拉高
else if(start_flag == 1'b1) begin
work_en <= 1'b1;
end
// 接收结束拉低(比特计数器为8,且读取标志为高)
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1)) begin
work_en <= 1'b0;
end
else begin
work_en <= work_en;
end
end

// 波特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
baud_cnt <= 1'b0; // 初值为0
end
// 归零条件
else if((baud_cnt == BAUD_CNT__MAX - 1'b1) || (work_en == 1'b0) ) begin
baud_cnt <= 16'b0;
end
// 波特计数器递增
else if (work_en == 1'b1) begin
baud_cnt <= baud_cnt + 1'b1;
end
else begin
baud_cnt <= baud_cnt;
end
end

// 比特标志信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
bit_flag <= 1'b0; // 初值为0
end
// 波特计数器达到波特率的一半时,比特标志拉高
else if(baud_cnt == BAUD_CNT__MAX/2 - 1'b1) begin
bit_flag <= 1'b1;
end
else begin
bit_flag <= 1'b0;
end
end

// 比特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
bit_cnt <= 4'b0; // 初值为0
end
// 归零条件
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1)) begin
bit_cnt <= 4'b0;
end
// 比特计数器递增
else if (bit_flag == 1'b1) begin
bit_cnt <= bit_cnt + 1'b1;
end
else begin
bit_cnt <= bit_cnt;
end
end

// 数据拼接
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rx_data <= 8'd0; // 初值为0
end
// 数据拼接(串行转并行数据)
else if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1)) begin
rx_data <= {rx_reg_3, rx_data[7:1]};
end
else begin
rx_data <= rx_data;
end
end

// rx标志信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rx_flag <= 1'b0; // 初值为0
end
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1)) begin
rx_flag <= 1'b1;
end
else begin
rx_flag <= 1'b0;
end
end

// 输出数据
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
po_data <= 8'd0; // 初值为0
end
else if(rx_flag == 1'b1) begin
po_data <= rx_data;
end
else begin
po_data <= po_data;
end
end

// 输出标志信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
po_flag <= 1'b0; // 初值为0
end
else begin
po_flag <= rx_flag;
end
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
71
72
73
74
75
76
77
`timescale 1ns / 1ns

module tb_uart_rx();
reg sys_clk;
reg sys_rst_n;
reg rx = 1'b1;

wire [7:0] po_data;
wire po_flag;

initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20
sys_rst_n = 1'b1;
end

// 时钟信号
always #10 sys_clk = ~sys_clk;

// 测试
initial begin
#200
rx_bit(8'd0);
rx_bit(8'd1);
rx_bit(8'd2);
rx_bit(8'd3);
rx_bit(8'd4);
rx_bit(8'd5);
rx_bit(8'd6);
rx_bit(8'd7);
end

// 引出输出



// 模拟串口数据的任务
task rx_bit (
input [7:0] data
);

integer i;
for( i= 0 ; i<10 ; i=i+1 ) begin
case (i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20);
end
endtask

uart_rx
#(
.UART_BSP(9600), // 波特率
.CLK_FREQ(50_000_000) // 时钟频率
)
uart_rx_inst
(
.sys_clk (sys_clk), // 系统时钟
.sys_rst_n (sys_rst_n), // 系统复位信号
.rx (rx), // 串口接收数据

.po_data (po_data), // 模块输出数据
.po_flag (po_flag) // 串口接收数据完成标志
);

endmodule

3.2 串口发送模块

模块代码:

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
`timescale 1ns / 1ns
module uart_tx
#(
parameter UART_BSP = 'd9600, // 波特率
parameter CLK_FREQ = 'd50_000_000 // 时钟频率
)
(
input wire sys_clk, // 系统时钟
input wire sys_rst_n, // 系统复位信号
input wire [7:0] pi_data, // 模块输入数据
input wire pi_flag, // 串口输入数据标志

output reg tx // 数据输出
);

parameter BAUD_CNT__MAX = CLK_FREQ/UART_BSP; // 波特率计数器最大值

reg work_en; // 工作使能
reg [15:0] baud_cnt; // 波特计数器
reg bit_flag; // 数据标志
reg [3:0] bit_cnt; // 比特计数器

// 使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
work_en <= 1'b0;
end
else if(pi_flag == 1'b1) begin
work_en <= 1'b1;
end
else if((bit_cnt == 4'd9) && (bit_flag == 1'b1)) begin
work_en <= 1'b0;
end
else
work_en <= work_en;
end

// 波特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
baud_cnt <= 16'd0;
end
// 归零条件
else if((work_en == 1'b0) || (baud_cnt == BAUD_CNT__MAX - 1'b1)) begin
baud_cnt <= 16'd0;
end
// 计数条件
else if(work_en == 1'b1) begin
baud_cnt <= baud_cnt + 1'b1;
end
else
baud_cnt <= baud_cnt;
end

// 比特标志信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
bit_flag <= 1'b0;
end
else if(baud_cnt == 16'b1) begin
bit_flag <= 1'b1;
end
else begin
bit_flag <= 1'b0;
end
end

// 比特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
bit_cnt <= 4'd0;
end
// 归零条件
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9)) begin
bit_cnt <= 4'd0;
end
// 计数条件
else if((bit_flag == 1'b1) && (work_en == 1'b1)) begin
bit_cnt <= bit_cnt + 1'b1;
end
else begin
bit_cnt <= bit_cnt;
end

end

// 输出信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx <= 1'b1; //空闲为高
end
else if(bit_flag == 1'b1)begin
case(bit_cnt)
4'd0: tx <= 1'b0; //起始位为低
4'd1: tx <= pi_data[0];
4'd2: tx <= pi_data[1];
4'd3: tx <= pi_data[2];
4'd4: tx <= pi_data[3];
4'd5: tx <= pi_data[4];
4'd6: tx <= pi_data[5];
4'd7: tx <= pi_data[6];
4'd8: tx <= pi_data[7];
4'd9: tx <= 1'b1; //停止位为高
default: tx <= 1'b1;
endcase
end
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
`timescale 1ns / 1ns

module tb_uart_tx();

reg sys_clk;
reg sys_rst_n;
reg [7:0] pi_data;
reg pi_flag;

wire tx;

// 系统时钟初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20;
sys_rst_n = 1'b1;
end

// 生成系统时钟
always #10 sys_clk = ~sys_clk;

// 测试序列
initial begin
pi_data <= 8'd0;
pi_flag <= 1'b0;
#200;
// 0
pi_data <= 8'd0;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 1
pi_data <= 8'd1;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 2
pi_data <= 8'd2;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 3
pi_data <= 8'd3;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 4
pi_data <= 8'd4;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 5
pi_data <= 8'd5;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 6
pi_data <= 8'd6;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
#(5208*10*20);
// 7
pi_data <= 8'd7;
pi_flag <= 1'b1;
#20;
pi_flag <= 1'b0;
end

// 实例化UART发送模块
uart_tx
#(
.UART_BSP(9600), // 波特率
.CLK_FREQ(50_000_000) // 时钟频率
)
uart_tx_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (pi_data),
.pi_flag (pi_flag),

.tx (tx)
);
endmodule

3.3 串口接收缓冲

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
`timescale 1ns / 1ns

// 串口输入 + 缓冲
module uart_fifo_rx
#(
parameter UART_BSP = 'd9600, // 波特率
parameter CLK_FREQ = 'd50_000_000 // 时钟频率
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire rx, // 串口接收数据
input wire rd_en, // 串口输入缓冲区读取数据使能

output wire [7:0] data_out, // 串口输入缓冲区输出数据
output wire empty // 串口输入缓冲区空标志
//output wire full // 串口输入缓冲区满标志
);

wire [7:0] data_in;
wire data_in_flag;

// 实例化UART接收模块
uart_rx
#(
.UART_BSP(UART_BSP), // 波特率
.CLK_FREQ(CLK_FREQ) // 时钟频率
)
uart_rx
(
.sys_clk (sys_clk), // 系统时钟
.sys_rst_n (sys_rst_n), // 系统复位信号
.rx (rx), // 串口接收数据

.po_data (data_in), // 模块输出数据
.po_flag (data_in_flag) // 串口接收数据完成标志
);


// 实例化FIFO
dcfifo_uart_1024x8 dcfifo_uart_1024x8_rx (
.rst(~sys_rst_n), // input wire rst
.wr_clk(sys_clk), // input wire wr_clk
.rd_clk(sys_clk), // input wire rd_clk
.din(data_in), // input wire [7 : 0] din
.wr_en(data_in_flag), // input wire wr_en
.rd_en(rd_en), // input wire rd_en

.dout(data_out), // output wire [7 : 0] dout
.full(), // output wire full
.empty(empty) // output wire empty
);

endmodule


3.3 串口发送缓冲

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
`timescale 1ns / 1ns

// 串口输出 + 缓冲

module uart_fifo_tx
#(
parameter UART_BSP = 'd9600, // 波特率
parameter CLK_FREQ = 'd50_000_000 // 时钟频率
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire [7:0] data_in, // 串口输出缓冲区输入数据
input wire wr_en, // 串口输出缓冲区输入数据标志(写入使能)

//output wire empty, // 串口输出缓冲区空标志
//output wire full, // 串口输出缓冲区满标志
output wire tx // 串口发送数据
);

wire [7:0] data_out; // 串口输出数据
wire rd_en;
wire empty;

// 缓冲区非空就使能缓冲区数据读出,并且和串口发送模块的写入使能相连
assign rd_en =!empty;

// 实例化UART发送模块
uart_tx
#(
.UART_BSP(UART_BSP), // 波特率
.CLK_FREQ(CLK_FREQ) // 时钟频率
)
uart_tx
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.pi_data (data_out),
.pi_flag (wr_en),

.tx (tx)
);

// 实例化UART发送缓冲
dcfifo_uart_1024x8 dcfifo_uart_1024x8_tx (
.rst(~sys_rst_n), // input wire rst
.wr_clk(sys_clk), // input wire wr_clk
.rd_clk(sys_clk), // input wire rd_clk
.din(data_in), // input wire [7 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en

.dout(data_out), // output wire [7 : 0] dout
.full(), // output wire full
.empty(empty) // output wire empty
);

endmodule

3.3 串口顶层模块

模块代码:

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
`timescale 1ns / 1ns

module uart_fifo_demo(
input wire sys_clk,
input wire sys_rst_n,
input wire rx,

output wire tx
);

parameter UART_BSP = 115200 ; // 波特率
parameter SYS_CLK_FREQ = 50_000_000 ; // 系统时钟频率

wire [7:0] data;

wire rx_empty;
wire rx_rd_en;
wire tx_wr_en;

// 接收缓冲区读取使能
assign rx_rd_en = !rx_empty;

// 发送缓冲区写入使能
assign tx_wr_en = !rx_empty;



// 实例化UART接收模块
uart_fifo_rx
#(
.UART_BSP(UART_BSP), // 波特率
.CLK_FREQ(SYS_CLK_FREQ) // 时钟频率
)
uart_fifo_rx
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.rx(rx), // 串口接收数据
.rd_en(rx_rd_en), // 串口输入缓冲区读取数据使能

.data_out(data), // 串口输入缓冲区输出数据
.empty(rx_empty) // 串口输入缓冲区空标志
//.full() // 串口输入缓冲区满标志
);


// 实例化UART发送模块
uart_fifo_tx
#(
.UART_BSP(UART_BSP), // 波特率
.CLK_FREQ(SYS_CLK_FREQ) // 时钟频率
)
uart_fifo_tx
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.data_in(data), // 串口输出缓冲区输入数据
.wr_en(tx_wr_en), // 串口输出缓冲区输入数据标志(写入使能)

//.empty(), // 串口输出缓冲区空标志
//.full(), // 串口输出缓冲区满标志
.tx(tx) // 串口发送数据
);



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
71
72
73
74
75
`timescale 1ns / 1ns

// 串口模块测试,模拟串口数据发送,波特率9600

module tb_uart_fifo_demo();

reg sys_clk;
reg sys_rst_n;
reg rx;
wire tx;
// 系统时钟初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
rx = 1'b1;
#20;
sys_rst_n = 1'b1;
end

// 生成系统时钟
always #10 sys_clk = ~sys_clk;

initial begin
#200;
rx_byte();
end


// 字节发送任务
task rx_byte();
integer j;
for(j =0;j<8;j=j+1) begin
rx_bit(j);
end
endtask

// 模拟串口数据的任务
task rx_bit (
input [7:0] data
);

integer i;
for( i= 0 ; i<10 ; i=i+1 ) begin
case (i)
0: rx <= 1'b0;
1: rx <= data[0];
2: rx <= data[1];
3: rx <= data[2];
4: rx <= data[3];
5: rx <= data[4];
6: rx <= data[5];
7: rx <= data[6];
8: rx <= data[7];
9: rx <= 1'b1;
endcase
#(5208*20);
end
endtask


// 实例化串口模块
uart_fifo_demo uart_fifo_demo(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.rx(rx),

.tx(tx)
);

// 重定义参数,定义串口波特率为9600
defparam uart_fifo_demo.UART_BSP = 9600;

endmodule


仿真效果(顶层模块):

四、实际测试效果

参考链接

  1. https://search.bilibili.com/all?vt=76757802&keyword=%E6%AD%A3%E7%82%B9%E5%8E%9F%E5%AD%90fpga&from_source=webtop_search&spm_id_from=333.1007&search_source=5
  2. https://www.bilibili.com/video/BV1y34y147s5/?spm_id_from=333.337.top_right_bar_window_history.content.click&vd_source=ef5a0ab0106372751602034cdd9ab98e
  3. https://blog.csdn.net/wanerXR/article/details/125120315
  4. https://www.cnblogs.com/lzbmeee/p/17624957.html
  5. https://www.bilibili.com/video/BV1N5411v7GN/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=ef5a0ab0106372751602034cdd9ab98e

UART串口通信-基于FIFO
https://www.duruofu.xyz/posts/60775/
作者
DuRuofu
发布于
2024年1月28日
更新于
2025年1月10日
许可协议