UART串口接收V2
背景
在前几天写完串口接收、发送代码后,自己也去查看了小梅哥的FPGA教程课,在他的教程中,接收模块还做了多次采集以确保模块在面对不稳定链路时的健壮性;个人感觉还是值得重写一遍作为新人练手的,其中代码思路延续小梅哥的,但是代码复现为本人从头重写,故会与小梅哥所提供代码例程存在差异。
代码难度
代码总体不难,但是量对比之前版本多上不少。我花了一天的时间去复现+调试,总是存在连续接收数据时会错过前三位数据,当时大致猜到了是错过了接收开始标志位导致。调试一天主要原因是屏幕太小了,看时序图真的很难受,自己也有点摆烂了,后面回家接上27寸大屏一分析,不到一小时便定位到错误并进行了代码更正。
UART接收代码V2
//
//
// UART 接收模块
// 此代码中将额外考虑通讯链路稳定问题
// 接收数据将进行7次采样,出现大于4次的信号数据将作为最终采样结果
//
/
module uart_rx_v2(
input clk,
input rst_n,
input rx_data,
output reg rx_done,
output reg [7:0]save_rx_data
);
//
//
// 检测开始接收标志位
//
/
wire RX_START;
reg RX_data_0;
reg RX_data_1;
always @(posedge clk, negedge rst_n)begin
if(!rst_n) begin
RX_data_0 <= 0;
RX_data_1 <= 0;
end else begin
RX_data_0 <= rx_data;
RX_data_1 <= RX_data_0;
end
end
assign RX_START = !RX_data_0 && RX_data_1;
//
//
// bps设置以及计时器
//
/
parameter CLK_HZ = 50_000_000,
BPS = 9600,
BPS_CNT = CLK_HZ / BPS, //每BPS
BPS_CNT_7 = BPS_CNT / 7; //1BPS的7次采样
//计数7分之1个bps的中间位置--更加精确采集数据
reg [31:0]bps_cnter;
reg [7:0]receive_num;
reg START_CNT;
reg half_bps_flag;
always @(posedge clk, negedge rst_n)begin
if(!rst_n) begin
bps_cnter <= 32'b0;
receive_num <= 8'b0;
half_bps_flag <= 1'b0;
end else begin
if(START_CNT) begin
if(bps_cnter > BPS_CNT_7 - 1) begin
receive_num <= receive_num + 1'b1;
half_bps_flag <= 1'b1;
bps_cnter <= 1'b0;
end else begin
bps_cnter <= bps_cnter + 1'b1;
half_bps_flag <= 1'b0;
receive_num <= receive_num;
end
end else begin
bps_cnter <= 32'b0;
half_bps_flag <= 1'b0;
receive_num <= 8'b0;
end
end
end
//产生开始计数标志位
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
START_CNT <= 1'b0;
end else begin
if(RX_START) begin
START_CNT <= 1'b1;
end else begin
if(rx_done) begin
START_CNT <= 1'b0;
end
end
end
end
//
//
// 数据接收
//
/
parameter IDLE = 3'd1,
STRAT_JUDGE = 3'd2,
RECEIVE_BEGIN = 3'd3,
FINISH = 3'd4;
reg [2:0]current_state;
reg [2:0]each_data_7[9:0];
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
current_state <= IDLE;
end else begin
case(current_state)
IDLE: if(RX_START) current_state <= STRAT_JUDGE;
STRAT_JUDGE: begin
if(each_data_7[0][2] == 0 && receive_num > 7 - 1) current_state <= RECEIVE_BEGIN;
else begin
if(receive_num > 7 - 1) begin
current_state <= IDLE;
end
end
end
RECEIVE_BEGIN: if(receive_num > 69 - 1) current_state <= FINISH;
FINISH: current_state <= IDLE;
default: current_state <= IDLE;
endcase
end
end
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
save_rx_data <= 8'b0;
each_data_7[0] <= 'd0;
each_data_7[1] <= 'd0;
each_data_7[2] <= 'd0;
each_data_7[3] <= 'd0;
each_data_7[4] <= 'd0;
each_data_7[5] <= 'd0;
each_data_7[6] <= 'd0;
each_data_7[7] <= 'd0;
each_data_7[8] <= 'd0;
each_data_7[9] <= 'd0;
end else begin
case(current_state)
IDLE: begin
rx_done <= 1'b0;
each_data_7[0] <= 'd0;
each_data_7[1] <= 'd0;
each_data_7[2] <= 'd0;
each_data_7[3] <= 'd0;
each_data_7[4] <= 'd0;
each_data_7[5] <= 'd0;
each_data_7[6] <= 'd0;
each_data_7[7] <= 'd0;
each_data_7[8] <= 'd0;
each_data_7[9] <= 'd0;
end
STRAT_JUDGE: begin
if(half_bps_flag) begin
case(receive_num)
'd0,'d1,'d2,'d3,'d4,'d5, 'd6: each_data_7[0] <= each_data_7[0] + rx_data;
endcase
end
end
RECEIVE_BEGIN: begin
if(half_bps_flag) begin
case(receive_num)
'd7,'d8,'d9,'d10,'d11,'d12, 'd13: each_data_7[1] <= each_data_7[1] + rx_data;
'd14,'d15,'d16,'d17,'d18,'d19, 'd20: each_data_7[2] <= each_data_7[2] + rx_data;
'd21,'d22,'d23,'d24,'d25,'d26, 'd27: each_data_7[3] <= each_data_7[3] + rx_data;
'd28,'d29,'d30,'d31,'d32,'d33, 'd34: each_data_7[4] <= each_data_7[4] + rx_data;
'd35,'d36,'d37,'d38,'d39,'d40, 'd41: each_data_7[5] <= each_data_7[5] + rx_data;
'd42,'d43,'d44,'d45,'d46,'d47, 'd48: each_data_7[6] <= each_data_7[6] + rx_data;
'd49,'d50,'d51,'d52,'d53,'d54, 'd55: each_data_7[7] <= each_data_7[7] + rx_data;
'd56,'d57,'d58,'d59,'d60,'d61, 'd62: each_data_7[8] <= each_data_7[8] + rx_data;
'd63,'d64,'d65,'d66,'d67,'d68, 'd69: each_data_7[9] <= each_data_7[9] + rx_data; //停止位
endcase
end
end
FINISH: begin
rx_done <= 1'b1;
save_rx_data <= {each_data_7[8][2], each_data_7[7][2],each_data_7[6][2],each_data_7[5][2],each_data_7[4][2],
each_data_7[3][2],each_data_7[2][2],each_data_7[1][2]};
end
endcase
end
end
endmodule
自我告诫
1、在调试过程中,自己还修改了部分发送模块代码,自以为不会存在问题便没有进行功能仿真,但是就是因为一时的自大,反而耽误了一上午的时间。在此警示自己,凡是改动的代码一定要提前备份+再次验证,不然将花费更多的精力去分析查错。
2、代码设计完成,不管多简单,一定要进行功能仿真,有时候你真的不知道自己会犯怎么离谱的错(误把复位输入给成时钟信号的我表示泪目)
题外话
之前和一个已经工作了三年的FPGAer交流时,发现他写的一个延时功能模块让我的一整个世界观都崩塌了。以下是他的代码:
`timescale 1ns / 1ps
module delay_time#(
parameter DELAY_COUNT = 2'd1
)(
input sys_clk,
input signal,
output signal_delay
);
reg signal_delay_reg = 1'b0;
assign signal_delay = signal_delay_reg;
always@(*) begin
if(DELAY_COUNT == 2'd1) begin
signal_delay_reg <= #10000000000 signal;
end
else if(DELAY_COUNT == 2'd2) begin
signal_delay_reg <= #20000000000 signal;
end
else begin
signal_delay_reg <= #10000000000 signal;
end
end
endmodule
疑惑点
1、从代码来看,这就是个很简单的延时输入功能代码。但是!!!据我目前所学、以及查询到的相关资料来看,在设计中使用 # 来进行延时是不可综合的,在上述代码中应该是直接等价于赋值操作。
2、我当时也反复跟那个工程师表达了疑惑,他确定的跟我说这个操作是可以的;而且当时我把该代码生成的电路图给他看,他也还是确定该操作时可行的。所以我很好奇是在什么平台上该操作是可行的呢?
3、像initial这种虽然不可综合,但是利用它来进行寄存器初始化操作也是会实际生效的,但是#这种我并不认为会起作用。
实际验证
代码综合出的电路图:
相信大家看到这,都可以发现这个根本就是个端到端的电路,根本不会生成任何延时的功能电路。
但是!!!!!我对这个代码进行了功能仿真测试,在功能测试里面这个代码确实表现出了延时功能!
后续我把这段代码烧录进cyclone IV系列的FPGA中进行验证,实际测试也说明了这段代码是不可行的。在我按下板上按键时,对应LED灯立马作出了反应,表明延时根本不存在。
结论
这段代码是不可综合的,无法实现其功能,但在功能测试中会表现正常。