FPGA-串口接收模块
图1 串口接收时序图
图2 串口接收时序框图
串口接收的信号 rx 相对于 FPGA 内部信号来说是一个异步信号,如不进行处理直接将其输入使用,容易出现时序违例导致亚稳态。因此这里就需要先将信号同步到 FPGA 的时钟域内才可以供后续模块使用,常见的同步方法即使用两级触发器,也就是使用触发器对信号打两拍的方式进行与系统时钟进行同步。
其次,由串口接收时序图可知,数据接收的起始信号是串行数据由空闲的高电平变为低电平, 即电平由高变低的下降沿,这就需要对下降沿进行检测。可以将同步后的信号放入一个两位的寄存器中,当寄存器的高位为1,低位为0时说明检测到下降沿。当检测到下降沿之后,使能信号rx_en拉高,直到rx_done信号到来之后拉低。串口接收模块波特率的设置与串口发送模块类似。串口接收需要对接收到的rx信号高低电平进行判断,为了判断的准确性,在每一位的中间时刻进行采样,为了实现这一目的,将每一位信号又分为两位,例如对应115200波特率时,baudrate_cnt=1000000000/115200/20/2-1。
如第一位为起始位0,bps_cnt为1时为该位的中间位置,此时进行采样电平。
串口接收模块代码:
module uart_rx( input wire clk , input wire rstn , input wire [ 2:0] baudrate_set , input wire rx , output reg [ 7:0] data , output reg rx_done ); //++++++++++++++++++++++++++++++++++++++++++++++++\ //++++++++++++++ Parameters&Signals ++++++++++ //++++++++++++++++++++++++++++++++++++++++++++++++/ reg [ 11:0] baudrate_cnt ; reg [ 11:0] div_cnt ; reg [ 4:0] bps_cnt ; reg [ 1:0] r_sync ; reg [ 1:0] r_rx ; reg rx_en ; reg p_edge ; wire n_edge ; //++++++++++++++++++++++++++++++++++++++++++++++++\ //++++++++++++++ Main code +++++++++++++++++++ //++++++++++++++++++++++++++++++++++++++++++++++++/ always @(*) begin case (baudrate_set) 3'd0:baudrate_cnt=1000000000/115200/20/2-1; 3'd1:baudrate_cnt=1000000000/57600/20/2-1; 3'd2:baudrate_cnt=1000000000/38400/20/2-1; 3'd3:baudrate_cnt=1000000000/19200/20/2-1; 3'd4:baudrate_cnt=1000000000/9600/20/2-1; default:baudrate_cnt=1000000000/115200/20/2-1; endcase end always @(posedge clk or negedge rstn) begin //sync if(!rstn) r_sync<=2'b00; else begin r_sync[0]<=rx; r_sync[1]<=r_sync[0]; end end always @(posedge clk or negedge rstn) begin //register if(!rstn) r_rx<=2'b00; else begin r_rx[0]<=r_sync[1]; r_rx[1]<=r_rx[0]; end end assign n_edge=(r_rx==2'b10); always@(posedge clk or negedge rstn)//rx_en if(!rstn) rx_en<=0; else if(n_edge) rx_en<=1; else if(rx_done) rx_en<=0; always @(posedge clk or negedge rstn) begin //div_cnt if(!rstn) div_cnt<=0; else if(rx_en)begin if(div_cnt==baudrate_cnt) div_cnt<=0; else div_cnt<=div_cnt+1'b1; end else div_cnt<=0; end always @(posedge clk or negedge rstn) begin //bps_cnt if(!rstn) bps_cnt<=0; else if(rx_en)begin if(div_cnt==baudrate_cnt/2)begin if(bps_cnt==20) bps_cnt<=0; else bps_cnt<=bps_cnt+1'b1; end end else bps_cnt<=0; end reg [ 2:0] r_data[7:0] ; reg [ 2:0] start_bit ; reg [ 2:0] stop_bit ; always @(posedge clk or negedge rstn) begin if(!rstn) begin start_bit<=0; r_data[0]<=0; r_data[1]<=0; r_data[2]<=0; r_data[3]<=0; r_data[4]<=0; r_data[5]<=0; r_data[6]<=0; r_data[7]<=0; stop_bit<=0; end else begin case (bps_cnt) 0:begin start_bit<=0; r_data[0]<=0; r_data[1]<=0; r_data[2]<=0; r_data[3]<=0; r_data[4]<=0; r_data[5]<=0; r_data[6]<=0; r_data[7]<=0; stop_bit<=0; end 1:start_bit<=start_bit+r_rx[1]; 3:r_data[0]<=r_data[0]+r_rx[1]; 5:r_data[1]<=r_data[1]+r_rx[1]; 7:r_data[2]<=r_data[2]+r_rx[1]; 9:r_data[3]<=r_data[3]+r_rx[1]; 11:r_data[4]<=r_data[4]+r_rx[1]; 13:r_data[5]<=r_data[5]+r_rx[1]; 15:r_data[6]<=r_data[6]+r_rx[1]; 17:r_data[7]<=r_data[7]+r_rx[1]; 19:stop_bit<=stop_bit+r_rx[1]; endcase end end always @(posedge clk or negedge rstn) begin if(!rstn) data<=0; else if (rx_done)begin data[0]<=(r_data[0]==1)?1:0; data[1]<=(r_data[1]==1)?1:0; data[2]<=(r_data[2]==1)?1:0; data[3]<=(r_data[3]==1)?1:0; data[4]<=(r_data[4]==1)?1:0; data[5]<=(r_data[5]==1)?1:0; data[6]<=(r_data[6]==1)?1:0; data[7]<=(r_data[7]==1)?1:0; end end always @(posedge clk or negedge rstn) begin //rx_done if(!rstn) rx_done<=0; else if((bps_cnt==20)&&(div_cnt==213)) rx_done<=1; else rx_done<=0; end endmodule
testbench代码:
`timescale 1ns / 1ns module uart_rx_tb(); reg clk ; reg rstn ; reg rx ; wire [ 7:0] data ; wire rx_done ; uart_rx u_uart_rx( .clk (clk ), .rstn (rstn ), .baudrate_set (3'd0 ), .rx (rx ), .data (data ), .rx_done (rx_done ) ); initial clk=1; always#10 clk=!clk; initial begin rstn=0; #201; rstn=1; #200; uart_tx_byte(8'h0f); @(posedge rx_done); #50000; uart_tx_byte(8'h53); @(posedge rx_done); #50000; uart_tx_byte(8'hf0); @(posedge rx_done); #50000; uart_tx_byte(8'h0f); @(posedge rx_done); #50000; $stop; end task uart_tx_byte; //name input [ 7:0] tx_data ;//parameter begin rx = 1; #20; rx = 0; #8680; rx = tx_data[0]; #8680; rx = tx_data[1]; #8680; rx = tx_data[2]; #8680; rx = tx_data[3]; #8680; rx = tx_data[4]; #8680; rx = tx_data[5]; #8680; rx = tx_data[6]; #8680; rx = tx_data[7]; #8680; rx = 1; #8680; end endtask endmodule
在testbench中使用到了task写法,其格式为task+task名字、输入信号,调用时为task名字(输入)。
仿真波形:
可以看到,0f、53、f0、0f被正确接收,实现了串口接收模块串行转并行的结果。