目录
基本框架
使用黑金 ARTIX-7 系列FPGA 开发平台(型号:AX7102,数据手册详见官网)实现UART接口RTL代码的设计,然后简单上板测试。可以配置协议数据位宽,校验类型,停止位宽度和波特率。
对于RX接收模块,使用串口调试工具发送数据,FPGA接收数据后,根据低4位数据点亮FPGA上的LED灯。
对于TX发送模块,为了简单测试模块的功能,在复位后简单打拍几下,将测试数据发送到串口调试工具上。
代码设计
代码主要包括三个模块和一个IP核。
IP核为一个分频模块,将FPGA的200M差分信号晶振分频为5M的时钟信号(主要为了后续项目,可以直接使用差分信号或者任意分频频率)。
一个UART发送模块uart_tx.v
module uart_tx#(
parameter DATA_WIDTH = 4'd8 ,
parameter CHECKSUM = 2'd2 , // 0:无校验位 1:奇校验 2:偶校验
parameter STOP_WIDTH = 2'd2 , // 1:1个停止位 2:2个停止位
parameter BAUD = 11520000 ,
parameter FREQUENCY = 50000000 )
( input i_clk ,
input i_rst ,
input [DATA_WIDTH - 1 : 0] i_data ,
input i_en ,
output o_tx ,
output o_valid );
/*--------------------------------parameter------------------------------*/
localparam CLK = FREQUENCY/BAUD - 1 ;
localparam IDLE = 8'b0000_0001 ,
START = 8'b0000_0010 ,
TX = 8'b0000_0100 ,
CHECK = 8'b0000_1000 ,
STOP = 8'b0001_0000 ;
/*----------------------------------wire---------------------------------*/
wire [1 : 0] w_check_mode;
/*----------------------------------reg----------------------------------*/
reg [7 : 0] r_state;
reg [7 : 0] r_state_next;
reg [15: 0] r_clk_cnt;
reg [3 : 0] r_tx_cnt;
reg [1 : 0] r_stop_cnt;
reg r_tx;
reg [DATA_WIDTH - 1 : 0] r_data;
reg r_div_clk; // 仿真时看波形的,无实际意义
/*---------------------------------assign--------------------------------*/
assign o_valid = (r_state == IDLE);
assign o_tx = r_tx;
assign w_check_mode = CHECKSUM;
/*---------------------------------always--------------------------------*/
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_data <= {DATA_WIDTH{1'b0}};
end
else if (i_en && (r_state == IDLE)) begin
r_data <= i_data;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_state <= IDLE;
end
else begin
r_state <= r_state_next;
end
end
always @(*) begin
case (r_state)
IDLE :begin
if (i_en) begin
r_state_next = START;
end
else begin
r_state_next = r_state;
end
end
START :begin
if (r_clk_cnt == CLK) begin
r_state_next = TX;
end
else begin
r_state_next = r_state;
end
end
TX :begin
if ((r_clk_cnt == CLK) && (r_tx_cnt == (DATA_WIDTH - 1))) begin
if (CHECKSUM == 2'd0) begin
r_state_next = STOP;
end
else begin
r_state_next = CHECK;
end
end
else begin
r_state_next = r_state;
end
end
CHECK :begin
if (r_clk_cnt == CLK) begin
r_state_next = STOP;
end
else begin
r_state_next = r_state;
end
end
STOP :begin
if ((r_clk_cnt == CLK) && (r_stop_cnt == (STOP_WIDTH - 1))) begin
r_state_next = IDLE;
end
else begin
r_state_next = r_state;
end
end
default :begin
r_state_next = IDLE;
end
endcase
end
always @(*) begin
case (r_state)
IDLE :begin
r_tx = 1'b1;
end
START :begin
r_tx = 1'b0;
end
TX :begin
r_tx = r_data[r_tx_cnt];
end
CHECK :begin
if (w_check_mode == 2'd1) begin // 奇校验
r_tx = ~(^r_data);
end
else begin
r_tx = ^r_data;
end
end
STOP :begin
r_tx = 1;
end
default :begin
r_tx = 1;
end
endcase
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_clk_cnt <= 16'b0;
end
else if (r_clk_cnt == CLK) begin
r_clk_cnt <= 16'b0;
end
else if (r_state != IDLE) begin
r_clk_cnt <= r_clk_cnt + 1;
end
else begin
r_clk_cnt <= 16'b0;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_div_clk <= 1'b0;
end
else if (r_clk_cnt == CLK) begin
r_div_clk <= ~r_div_clk;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_tx_cnt <= 4'b0;
end
else if ((r_state == TX) && (r_clk_cnt == CLK)) begin
if (r_tx_cnt == (DATA_WIDTH - 1)) begin
r_tx_cnt <= 4'b0;
end
else begin
r_tx_cnt <= r_tx_cnt + 1;
end
end
else if (r_state != TX) begin
r_tx_cnt <= 4'b0;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_stop_cnt <= 2'b0;
end
else if ((r_state == STOP) && (r_clk_cnt == CLK)) begin
if (r_stop_cnt == STOP_WIDTH - 1) begin
r_stop_cnt <= 2'b0;
end
else begin
r_stop_cnt <= r_stop_cnt + 1;
end
end
else if (r_state != STOP) begin
r_stop_cnt <= 2'b0;
end
end
/*----------------------------------inst---------------------------------*/
endmodule
一个UART接收模块uart_rx.v
module uart_rx#(
parameter DATA_WIDTH = 4'd8 ,
parameter CHECKSUM = 2'd0 , // 0:无校验位 1:奇校验 2:偶校验
parameter STOP_WIDTH = 2'd2 , // 1:1个停止位 2:2个停止位
parameter BAUD = 115_200 ,
parameter FREQUENCY = 5_000_000 )
( input i_clk ,
input i_rst ,
input i_data ,
output [DATA_WIDTH - 1 : 0] o_rx ,
output o_done ,
output o_error );
/*--------------------------------parameter------------------------------*/
localparam CLK = FREQUENCY/BAUD - 1 ;
localparam CLK_SAMPLE = FREQUENCY/(2*BAUD) - 1 ;
localparam IDLE = 8'b0000_0001 ,
START = 8'b0000_0010 ,
RX = 8'b0000_0100 ,
CHECK = 8'b0000_1000 ,
STOP = 8'b0001_0000 ;
/*----------------------------------wire---------------------------------*/
wire w_data_negedge;
wire [1 : 0] w_checksum_mode;
wire w_sample_en;
/*----------------------------------reg----------------------------------*/
reg [DATA_WIDTH - 1 : 0] r_rx;
reg [7 : 0] r_state;
reg [7 : 0] r_state_next;
reg r_data_d1; // 判断下降沿
reg r_check;
reg r_error;
reg [15: 0] r_clk_cnt;
reg r_div_clk;
reg [3 : 0] r_rx_cnt;
/*---------------------------------assign--------------------------------*/
assign o_rx = r_rx;
assign o_done = (r_state == STOP);
assign o_error = r_error;
assign w_data_negedge = (r_state == IDLE) ? ((~i_data) & r_data_d1) : 1'b0;
assign w_checksum_mode = CHECKSUM;
assign w_sample_en = ((r_state == RX) || (r_state == CHECK)) ? (r_clk_cnt == CLK_SAMPLE) : 1'b0;
/*---------------------------------always--------------------------------*/
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_state <= IDLE;
end
else begin
r_state <= r_state_next;
end
end
always @(*) begin
case (r_state)
IDLE :begin
if (w_data_negedge) begin
r_state_next = START;
end
else begin
r_state_next = r_state;
end
end
START :begin
if (r_clk_cnt == CLK) begin
r_state_next = RX;
end
else begin
r_state_next = r_state;
end
end
RX :begin
if ((r_clk_cnt == CLK) && (r_rx_cnt == (DATA_WIDTH - 1))) begin
if (w_checksum_mode == 2'd0) begin
r_state_next = STOP;
end
else begin
r_state_next = CHECK;
end
end
else begin
r_state_next = r_state;
end
end
CHECK :begin
if (r_clk_cnt == CLK) begin
r_state_next = STOP;
end
else begin
r_state_next = r_state;
end
end
STOP :begin
if (r_clk_cnt == CLK) begin
r_state_next = IDLE;
end
else begin
r_state_next = r_state;
end
end
default :begin
r_state_next = IDLE;
end
endcase
end
always @(posedge i_clk) begin
case (r_state)
IDLE :begin
r_rx <= {DATA_WIDTH{1'b0}};
end
START :begin
r_rx <= r_rx;
end
RX :begin
if (w_sample_en) begin
r_rx[r_rx_cnt] <= i_data;
end
end
CHECK :begin
r_rx <= r_rx;
end
STOP :begin
r_rx <= r_rx;
end
default :begin
r_rx <= {DATA_WIDTH{1'b0}};
end
endcase
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_clk_cnt <= 16'b0;
end
else if (r_clk_cnt == CLK) begin
r_clk_cnt <= 16'b0;
end
else if (r_state != IDLE) begin
r_clk_cnt <= r_clk_cnt + 1;
end
else begin
r_clk_cnt <= 16'b0;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_check <= 1'b0;
end
else if ((r_state == CHECK) && w_sample_en && (w_checksum_mode != 2'd0)) begin
r_check <= i_data;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_div_clk <= 1'b0;
end
else if (r_clk_cnt == CLK) begin
r_div_clk <= ~r_div_clk;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_rx_cnt <= 4'b0;
end
else if ((r_state == RX) && (r_clk_cnt == CLK)) begin
if (r_rx_cnt == (DATA_WIDTH - 1)) begin
r_rx_cnt <= 4'b0;
end
else begin
r_rx_cnt <= r_rx_cnt + 1;
end
end
else if (r_state != RX) begin
r_rx_cnt <= 4'b0;
end
end
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
r_data_d1 <= 1'b0;
end
else begin
r_data_d1 <= i_data;
end
end
always @(*) begin
if (r_state == STOP) begin
if (w_checksum_mode == 2'd0) begin
r_error = 1'b0;
end
else if (w_checksum_mode == 2'd1) begin
if (r_check != (^r_rx)) begin
r_error = 1'b0;
end
else begin
r_error = 1'b1;
end
end
else if (w_checksum_mode == 2'd2) begin
if (r_check == (^r_rx)) begin
r_error = 1'b0;
end
else begin
r_error = 1'b1;
end
end
end
else begin
r_error = 1'b0;
end
end
/*----------------------------------inst---------------------------------*/
endmodule
最后是顶层模块top.v
在顶层模块里进行例化和参数配置。同时进行异步复位同步释放和发送模块的简单测试。
module top(
input i_sys_clk_p , // 200M
input i_sys_clk_n , // 200M
input i_sys_rstn ,
input i_data_uart , // uart_rx
output o_uart_tx ,
output o_LED0 ,
output o_LED1 ,
output o_LED2 ,
output o_LED3 );
/*--------------------------------parameter------------------------------*/
parameter DATA_WIDTH = 4'd8 ;
parameter CHECKSUM = 2'd0 ;
parameter STOP_WIDTH = 2'd2 ;
parameter BAUD = 115200 ;
parameter FREQUENCY = 5_000_000 ;
/*----------------------------------wire---------------------------------*/
wire w_clk_5M; // 5M
wire w_rst_syn;
wire w_uart_rx_done;
wire w_uart_done_posedge; // rx_done信号上升沿
wire [DATA_WIDTH - 1:0] w_uart_data;
wire w_uart_error;
wire w_uart_tx_valid;
/*----------------------------------reg----------------------------------*/
reg [7:0] r_cnt;
reg r_rstn_d1;
reg r_rstn_syn; // 异步复位同步释放
reg [DATA_WIDTH - 1:0] r_uart_data;
reg r_uart_done_d1;
reg [DATA_WIDTH - 1:0] r_uart_tx;
reg r_uart_tx_en;
/*---------------------------------assign--------------------------------*/
assign w_rst_syn = ~r_rstn_syn; // 高电平复位
assign o_LED0 = r_uart_data[0];
assign o_LED1 = r_uart_data[1];
assign o_LED2 = r_uart_data[2];
assign o_LED3 = r_uart_data[3];
assign w_uart_done_posedge = w_uart_rx_done & (~r_uart_done_d1);
/*---------------------------------always--------------------------------*/
always @(posedge w_clk_5M or negedge r_rstn_syn) begin
if (!r_rstn_syn) begin
r_cnt <= 8'b0;
r_uart_tx <= {DATA_WIDTH{1'b1}};
r_uart_tx_en <= 1'b0;
end
else if ((r_cnt == 8'd100) && w_uart_tx_valid) begin
r_uart_tx_en <= 1'b1;
end
else if (r_cnt <= 8'd200) begin
r_cnt <= r_cnt + 1;
r_uart_tx_en <= 1'b0;
end
end
always @(posedge w_clk_5M or negedge r_rstn_syn) begin
if (!r_rstn_syn) begin
r_uart_done_d1 <= 1'b0;
end
else begin
r_uart_done_d1 <= w_uart_rx_done;
end
end
always @(posedge w_clk_5M or negedge r_rstn_syn) begin
if (!r_rstn_syn) begin
r_uart_data <= {DATA_WIDTH{1'b1}};
end
else if (w_uart_error) begin
r_uart_data <= {DATA_WIDTH{1'b1}};
end
else if (w_uart_done_posedge) begin
r_uart_data <= w_uart_data;
end
end
always @(posedge w_clk_5M or negedge i_sys_rstn) begin
if (!i_sys_rstn) begin
r_rstn_d1 <= 1'b0;
r_rstn_syn <= 1'b0;
end
else begin
r_rstn_d1 <= i_sys_rstn;
r_rstn_syn <= r_rstn_d1;
end
end
/*----------------------------------inst---------------------------------*/
uart_tx #(
.DATA_WIDTH (DATA_WIDTH ),
.CHECKSUM (CHECKSUM ),
.STOP_WIDTH (STOP_WIDTH ),
.BAUD (BAUD ),
.FREQUENCY (FREQUENCY ))
uart_tx_inst(
.i_clk (w_clk_5M ),
.i_rst (w_rst_syn ),
.i_data (r_uart_tx ),
.i_en (r_uart_tx_en ),
.o_tx (o_uart_tx ),
.o_valid (w_uart_tx_valid ));
uart_rx #(
.DATA_WIDTH (DATA_WIDTH ),
.CHECKSUM (CHECKSUM ),
.STOP_WIDTH (STOP_WIDTH ),
.BAUD (BAUD ),
.FREQUENCY (FREQUENCY ))
uart_rx_inst(
.i_clk (w_clk_5M ),
.i_rst (w_rst_syn ),
.i_data (i_data_uart ),
.o_rx (w_uart_data ),
.o_done (w_uart_rx_done ),
.o_error (w_uart_error ));
clk_wiz_0 clock_div(
.clk_in1_n (i_sys_clk_n ),
.clk_in1_p (i_sys_clk_p ),
.resetn (i_sys_rstn ),
.clk_out1 (w_clk_5M ));
endmodule
上板验证
首先进行管脚的约束,具体内容参考官方手册。
这里遇到一个问题,对于差分信号我们只需要约束p管脚就行,好像会自动约束n管脚。
set_property PACKAGE_PIN T6 [get_ports i_sys_rstn]
set_property IOSTANDARD LVCMOS15 [get_ports i_sys_rstn]
set_property PACKAGE_PIN R4 [get_ports i_sys_clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports i_sys_clk_p]
set_property PACKAGE_PIN Y12 [get_ports i_data_uart]
set_property IOSTANDARD LVCMOS33 [get_ports i_data_uart]
set_property PACKAGE_PIN C17 [get_ports o_LED0]
set_property IOSTANDARD LVCMOS33 [get_ports o_LED0]
set_property PACKAGE_PIN D17 [get_ports o_LED1]
set_property IOSTANDARD LVCMOS33 [get_ports o_LED1]
set_property PACKAGE_PIN V20 [get_ports o_LED2]
set_property IOSTANDARD LVCMOS33 [get_ports o_LED2]
set_property PACKAGE_PIN U20 [get_ports o_LED3]
set_property IOSTANDARD LVCMOS33 [get_ports o_LED3]
set_property PACKAGE_PIN Y11 [get_ports o_uart_tx]
set_property IOSTANDARD LVCMOS33 [get_ports o_uart_tx]
最后将比特流进行烧录,使用micro USB连接uart端口,在Microsoft Store下载了一个串口调试助手,然后需要去下载一个驱动,就可以进行数据的收发。
在收发的过程中,将数据类型选为16进制显示,比如发送一个8位aa,LED0和LED2会点亮。按一下复位键,PC就会接收到FPGA发送的ff数据。