兜兜转转看了好多家视频和好几本书,明白了FPGA难学的原因之一是因为讲的好(我觉得就是很详细,告诉你为什么这么来写代码)的视频比较少,之前看到的那本书其实也很好,只是没有说为什么这么写,以及某些步骤的用意,这次看了野火的视频,发现挺符合我的需求,他们视频和我借的那本书思路是相同的,野火的视频能将那些东西详细讲解我觉得挺好,接下来就开始更新
整体代码思路:rx每次接收一位数据,先打一拍,从而与系统时钟同步----->再多打两拍消除亚稳态------>开始提取数据(包含起始位和终止位)------->使能信号置为高电平,抽效数据--------> 提取完八位有效数据后结束计数,并且关闭使能信号------>拼接数据并传输----->结束
附上代码
/*
串口重要的参数有波特率、数据位、起始位和停止位、奇偶校验位
波特率bps:一秒可以传输的bit位数,所以与时间有关(有时序逻辑)
数据位通常是8位,起始位是第一位,停止位通常是最后一位
起始位是低电平0,停止位是高电平1
因为是用于与pc的通信,所以还需要数据,所以输入有数据,clk,重置(感觉好像每个目前每个设计都有外部重置)
还要有一个rx信号,是因为他是传入fpga的信号(他应该就是接收数据的状态的意思空闲为1,起始为0结尾为1)
还要有一个的reg1打一拍,因为串行数据与时钟不同步,所以进行打一拍来进行信号的同步,然而rx作为一个异步信号被同步会进入亚稳态
因为实际上rx信号变化并不是理想的瞬间阶跃信号,而是一个斜坡信号,消除亚稳态要靠多级寄存器,所以用多打几拍来消除
单比特信号从高速时钟到低速时钟,通常打两个时钟拍就好,所以使用打拍一次后调整为同步,后面再打两拍就是为了消除亚稳态
消除亚稳态以后要清楚在什么位置提取有效数据,所以又要有一个标志信号(start_flag)表示开始提取数据,因为空闲状态和停止位都是高电平
起始位是低电平,所以可以利用下降沿来作为提取标志位的开始,保持一个时钟周期的高电平再回到低电平,直到下一次下降沿出现
还要有一个使能信号,表示数据提取开始的范围,他是在start_flag变为高电平以后他才开始变,因为是时序逻辑,所以他要落后一个时钟周期
接下来是重要的计数器,记录一个波特所占用的时间,来判断什么时候完成,50m时钟一个时钟周期为20ns,9600bps是一秒9600个数据,所以一个数据为
1/9600s,再除以20ns即可得到计数器满多少完成一个数据传输
使能信号为1时计数器才开始工作!!!
还要保证提取数据时数据是在最稳定的状态下提取,那么就是在bode计数器的一半时候这个时候数据最为稳定令为bit标志位
然而我们还是要注意,十个数据我们只需要中间八个,所以还要需要一个bit计数器因为总共是0-9,所以我们只需要1-8位,在使能端为1的时候,bit标志位每次拉高一个时钟周期
就自加1,提取完有效数据以后使能端便可变为低电平,此时比特计数器是要在使能端和bit标志位都为1的时候才开始计数,所以是一个与条件
最后要有一个数据拼接,由于rs232是lsb至msb,所以是最低位先输入,所以数据拼接是 rx_data = {reg3,rx_data[7:1]},所以相当于左移(相对来说是收集到的数据自己向左移)
收集数据完毕要有一个标志位,标志位为高电平以后,把收集数据传递给输出,,同时一个新的flag就可以诞生了
发送部分
重置和时钟都要有
,接收的并行数据(此时变为pi_data)以及flag确定是否可以发送
他的输出就是tx
*/
module uart_new
(
parameter UART_BPS = 'd9600,
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_BPS;
reg rx_reg1;
reg rx_reg2;
reg rx_reg3;
reg start_flag;
reg word_en;
reg [12: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)
rx_reg1 <= 1'b1;//因为初始值是高电平
else
rx_reg1 <= rx;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_reg2 <= 1'b1;//因为初始值是高电平
else
rx_reg2 <= rx_reg1;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_reg3 <= 1'b1;//因为初始值是高电平
else
rx_reg3 <= rx_reg2;
end
//接下来是开始标志
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
start_flag <= 1'b0;
else if ((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1) && (word_en == 1'b0))//注意画波形图以后来写判断条件,同时由于是时序电路所以被产生的东西会比生其者慢一个时钟周期,
//所以写条件在start_flag上升沿前面半个时钟周期来判定,有点投机取巧,但是感觉应该可以,为什么还要有后面的条件是因为如果传输过程中reg2和3碰巧出现该情况则会出错,所以
//要记得相互制约
start_flag <= 1'b1;
else
start_flag <= 1'b0;//其他时候为低电平
end
//接下来是使能信号
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
word_en <= 1'b0;
else if(start_flag == 1'b1)
word_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))//还是老规矩,变为0看前半个时钟周期其他的变量来写条件
word_en <= 1'b0;
else
word_en <= word_en;//我的理解是如果能有一个状态保持多个时钟周期,那么就要这么写
end
//baud计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
baud_cnt <= 13'b0;
else if ((baud_cnt == BAUD_CNT_MAX - 1'b1) || (word_en == 1'b0))//还有没有使能信号也要清零
baud_cnt <= 13'b0;
else if (word_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
end
//bit_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX / 2 - 1'b1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;//只保持一个时钟周期就这么写
end
//bit_cnt
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
bit_cnt <= 4'd0;//这个变量是多少位那么就要写多少
else if ((bit_cnt == 4'd8) && (bit_flag == 1'b1))//出现bug:是将4'd8写为了d0
bit_cnt <= 4'd0;
else if (bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
end
//rx_data
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_data <= 8'd0;
else if ((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))//多一个条件来约束便于稳定
rx_data <= {rx_reg3,rx_data[7:1]};
end
//rx_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
rx_flag <= 1'b0;
else if ((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
end
//po_data
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
po_data <= 8'd0;
else if ((rx_flag == 1'b1))
po_data <= rx_data;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if (!sys_rst_n)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
end
endmodule
仿真文件
`timescale 1ns / 1ns
module uart_new_tb();
reg sys_clk;
reg sys_rst_n;
reg rx;
wire [7:0] po_data;
wire po_flag;
initial begin//先是模拟产生两路信号,时钟加复位信号
sys_clk = 1'b1;
sys_rst_n = 1'b0;
rx = 1'b1;//这一步是在两路信号写完以后再写的,因为是空闲状态,所以得是高电平
#20
sys_rst_n = 1'b1;
end
always #10 sys_clk = ~sys_clk;//产生时间信号
initial begin//用initial对任务函数进行调用,直接使用任务名进行调用
#201
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 名称(端口列表,我感觉相当于形参) 最后要有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
#(520820);//模拟传输一个数据因为是5208个时钟周期,所以要等待520820
end
endtask
uart_new
(
.UART_BPS(9600),
.CLK_FREQ(50_000_000)
)//宏定义
uart_new_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.rx(rx),
//输入输出分开来
.po_data(po_data),
.po_flag(po_flag)
);
endmodule
仿真时要记得自己对照着仿真波形验证是否出现了想要的结果