目录
一、工程简介
使用正点原子达芬奇A7板子,在学习8位以及多字节数据的UART串口的接受和发送,以及ROM,RAM存储器IP核的建立和使用等课程后,尝试完成了一道自命题项目:向RAM中循环写入2048个16位数据,通过UART串口将数据发送至上位机,且在发送数据的同时还在向RAM中继续存数据。
二、功能介绍
1.数据是16位的,使用串口发送2个字节的数据到电脑上。
2.写入的数据这里暂定与写地址相同,因为位宽不等(2048个数据地址是12位的,这里写入的数据是16位的,进行高位补零)。
3.在发送数据的同时还在向RAM中继续存数据,因此简单的单口RAM不能实现数据的同时读写,所以这里使用伪双端RAM。
4.要求RAM数据的读写速度和串口数据发送速度相匹配,这里暂时不用FIFO,所以需要根据数据发送波特率调整RAM读写的时钟频率。
根据设计功能,可将本项目大体可划分为五个模块:
- RAM核调用
- 读写时钟模块
- RAM写模块
- RAM读模块
- 串口发送模块
三、具体实现
1.RAM IP核的配置
![](https://img-blog.csdnimg.cn/cf71cbf3b54b454ba6639a11df7cefc5.png)
![](https://img-blog.csdnimg.cn/26daa60a5970437897ef3a8270ed7b76.png)
![](https://img-blog.csdnimg.cn/a825581a8dd7433ba32c562c7147c8b0.png)
2.读写模块
1)RAM 写模块设计
module ram_wr(
input Clk , //时钟信号
input Reset_n , //复位信号,低电平有效4
//RAM 写端口操作
output ram_wr_we , //ram 写使能
output reg ram_wr_en , //端口使能
output reg rd_flag , //读启动信号
output reg [11:0] ram_wr_addr , //ram 写地址
output [15:0] ram_wr_data //ram 写数据
);
//*****************************************************
//** main code
//*****************************************************
//ram_wr_we 为高电平表示写数据
assign ram_wr_we = ram_wr_en ;
//写数据与写地址相同,因位宽不等,所以高位补 0
assign ram_wr_data = {2'b0,ram_wr_addr} ;
//控制 RAM 使能信号
always @(posedge Clk or negedge Reset_n) begin
if(!Reset_n)
ram_wr_en <= 1'b0;
else
ram_wr_en <= 1'b1;
end
//写地址信号 范围:0~2048
always @(posedge Clk or negedge Reset_n) begin
if(!Reset_n)
ram_wr_addr <= 12'd0;
else if(ram_wr_addr < 12'd2048 && ram_wr_we)
ram_wr_addr <= ram_wr_addr + 1'b1;
else
ram_wr_addr <= 12'd0;
end
//当写入 2048 个数据(0~1024)后,拉高读启动信号
always @(posedge Clk or negedge Reset_n) begin
if(!Reset_n)
rd_flag <= 1'b0;
else if(ram_wr_addr == 11'd1024)
rd_flag <= 1'b1;
else
rd_flag <= rd_flag;
end
endmodule
2)RAM 读模块设计
![](https://img-blog.csdnimg.cn/2f901078e4d944cc9c4f3e7d4ed2787e.png)
module ram_rd(
input Clk , //时钟信号
input Reset_n , //复位信号,低电平有效4
//RAM 读端口操作
input rd_flag , //读启动标志
input [15:0] ram_rd_data, //ram 读数据
output ram_rd_en , //端口使能
output reg [11:0] ram_rd_addr //ram 读地址
);
//*****************************************************
//** main code
//*****************************************************
//控制 RAM 使能信号
assign ram_rd_en = rd_flag;
//读地址信号 范围:0~2048
always @(posedge Clk or negedge Reset_n) begin
if(!Reset_n)
ram_rd_addr <= 12'd0;
else if(ram_rd_addr < 12'd2048 && ram_rd_en)
ram_rd_addr <= ram_rd_addr + 1'b1;
else
ram_rd_addr <= 12'd0;
end
endmodule
3.串口发送模块
1)串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口。
2)波特率是 UART 通信中需要设置的参数之一。在波特率时钟生成模 块中,计数器需要的计数值与波特率之间的关系如下表所示,其中系统时钟周期为 Clk,这里为 20ns。如果接入到该模块的时钟频率为其他值,需要根据具体的频率值修改该参数。
baud_set
|
波特率
|
波特率周期
|
波特率分频计数值
|
50M
系统时钟计数值
|
0 | 9600 |
104167ns
|
104167/Clk
|
5208-1
|
1 | 19200 |
52083ns
| 52083/Clk |
2604-1
|
2 | 38400 |
26041ns
| 26041/Clk |
1302-1
|
3 | 57600 |
17361ns
| 17361/Clk |
868-1
|
4 | 115200 |
8680ns
| 8680/Clk |
434-1
|
reg [17:0]bps_DR;
always@(*)
case(Baud_set)
0:bps_DR= 1000000000/9600/20;
1:bps_DR= 1000000000/19200/20;
2:bps_DR= 1000000000/38400/20;
3:bps_DR= 1000000000/57600/20;
4:bps_DR= 1000000000/115200/20;
default: bps_DR= 1000000000/9600/20;
endcase
3)串口通信的本质就是将8位的并行数据通过一根信号线在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。
4)串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位高电平标志传输的结束。
5)控制信号,控制并转串模块,什么时候开始工作,什么时候一个8位数据发送完成?须有一个发送开始信号以及一个发送完成信号。
编写代码
module uart_8(
Clk,
Reset_n,
Data,
Baud_set,
uart_tx,
Send_Go,
Tx_done
);
input Clk;
input Reset_n;
input Send_Go;
input [7:0]Data;
input [2:0]Baud_set;
output reg Tx_done;
output reg uart_tx;
//Baud_set=0,波特率=9600;
//Baud_set=1,波特率=19200;
//Baud_set=2,波特率=38400;
//Baud_set=3,波特率=57600;
//Baud_set=4,波特率=115200;
reg [17:0]bps_DR;
always@(*)
case(Baud_set)
0:bps_DR= 1000000000/9600/20;
1:bps_DR= 1000000000/19200/20;
2:bps_DR= 1000000000/38400/20;
3:bps_DR= 1000000000/57600/20;
4:bps_DR= 1000000000/115200/20;
default: bps_DR= 1000000000/9600/20;
endcase
reg Send_en; //send_en变成内部信号,由顶层send_go信号输入到这里进行控制
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_en<=0;
else if(Send_Go)
Send_en<=1;
else if(Tx_done)
Send_en<=0;
reg [7:0]r_Data;
always@(posedge Clk )
if(Send_Go)
r_Data<=Data;
else
r_Data<=r_Data;
//bps_clk
wire bps_clk;
assign bps_clk=(counter==1);
//自定义数据位传输时间,小状态计数器 Time
reg [17:0]counter;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter<=0;
else if(Send_en)begin
if(counter==bps_DR-1)
counter<=0;
else
counter<=counter+1'b1;
end
else
counter<=0;
reg [3:0]counter2;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter2<=0;
else if(Send_en) begin
if(bps_clk)begin
if(counter2==11)
counter2<=0;
else
counter2<=counter2+1'b1;
end
end
else
counter2<=0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx<=1; //要求数据在不传送状态时保持高电平。为在开始传输数据时可以辨别起始位
else begin
case(counter2)
1:uart_tx<=0;
2:uart_tx<=r_Data[0];
3:uart_tx<=r_Data[1];
4:uart_tx<=r_Data[2];
5:uart_tx<=r_Data[3];
6:uart_tx<=r_Data[4];
7:uart_tx<=r_Data[5];
8:uart_tx<=r_Data[6];
9:uart_tx<=r_Data[7];
10:uart_tx<=1;
11:uart_tx<=1;
default:uart_tx<=uart_tx;
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Tx_done<=0;
else if((bps_clk==1)&&(counter2==10)) //这是为了让Tx_done只保持一个时钟周期时间 所作出的条件改变
Tx_done<=1;
else
Tx_done<=0;
endmodule
因为UART协议规定了,发送的数据位只能有6,7,8位。
在第一种情况下,等待传输请求(Trans_Go)的到来,Data40[7:0]给到uart_byte_tx的Data,并同时产生Send_Go信号,启动第一个字节的发送。
接着等待Tx_Done信号的到来。
16位数据是否发完了?
发完了,回到第一步继续等Trans_Go。
没发完,启动下一个8位数据的发送
module uart_rx_16bit(
Clk,
Reset_n,
Data16,
Trans_Go,
uart_tx,
Trans_done
);
input Clk;
input Reset_n;
input [15:0]Data16;
input Trans_Go;
output uart_tx;
output reg Trans_done;
reg [7:0]Data; //Data成为一个内部信号
reg Send_Go; //Send_Go作为一个内部信号使能uart_0中的send_en
wire Tx_done;
uart_8 uart_8(
.Clk(Clk),
.Reset_n(Reset_n),
.Data(Data),
.Baud_set(3'd4),
.uart_tx(uart_tx),
.Send_Go(Send_Go),
.Tx_done(Tx_done)
);
reg [2:0]state;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
state<=0;
Data<=0;
Send_Go<=0;
Trans_done<=0;
end
else case(state)
0:begin //情况0:等待传输请求。
Trans_done<=0;
if(Trans_Go)begin //请求到来,使能Send_Go,把第1字节数据赋给数据传输模块,情况从0转到1.
Data<=Data16[7:0];
// Data<=Data16[15:8];
Send_Go<=1;
state<=1;
end
else begin //请求没有到来,继续保持不变;
Data<=Data;
Send_Go<=0;
state<=0;
end
end
1:begin //情况1:传输数据
if(Tx_done)begin //第1节数据传输完成Tx_done信号拉高,把第2字节数据赋给数据传输模块,情况从1转到2.
Data<=Data16[15:8];
// Data<=Data16[7:0];
Send_Go<=1;
state<=2;
end
else begin //第1节数据传输没有完成,继续传输;
Data<=Data;
Send_Go<=0;
state<=1;
end
end
2:begin
if(Tx_done)begin
Send_Go<=0;
state<=0;
Trans_done<=1;
end
else begin
Data<=Data;
Send_Go<=0;
state<=2;
end
end
default:begin
Data<=Data;
Send_Go<=0;
state<=0;
end
endcase
endmodule
这里写的uart_rx16bit模块,其中调用uart_8,波特率在例化时直接写的4即选择的115200。
4.读写时钟模块
数据发送的波特率是115200,即1s发送115200个数据位,由于每8位数据位有1个起始位和1个停止位,发送两个字节数据共20位。即频率f=1/(115200/20)=5760Hz。计数器计数MNCA=1000000000/5760/20/2=4340
编写代码
module clk_divider(
input Reset_n,
input Clk,
output reg clk_5760Hz
);
//ram读写时钟,要符合115200波特率的数据发送速度
parameter MNCA=4340;
reg [22:0] counter = 0;
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
clk_5760Hz<=0;
else if (counter ==MNCA) begin
counter <= 0;
clk_5760Hz <= ~clk_5760Hz;
end
else
counter <= counter + 1;
endmodule
5.顶层模块设计
顶层模块中实例化各个功能模块并调用,根据上述几个部分编写功能模块。
module uart_ram_test(
input Clk,
input Reset_n,
output uart_tx
);
wire clk_5760Hz;
wire rd_flag;
wire [15:0]rd_data;
wire rd_en;
wire [11:0]rd_addr;
wire wr_we;
wire wr_en;
wire [11:0]wr_addr;
wire [15:0]wr_data;
wire Trans_done;
reg Trans_Go;
clk_divider clk_ram(
.Reset_n(Reset_n),
.Clk(Clk),
.clk_5760Hz(clk_5760Hz)
);
RAM RAM(
.clka(clk_5760Hz),
.ena(wr_en),
.wea(wr_we),
.addra(wr_addr),
.dina(wr_data),
.clkb(clk_5760Hz),
.enb(rd_en),
.addrb(rd_addr),
.doutb(rd_data)
);
ram_wr ram_wr(
.Clk(clk_5760Hz),
.Reset_n( Reset_n),
.ram_wr_we(wr_we),
.ram_wr_en(wr_en),
.rd_flag(rd_flag),
.ram_wr_addr(wr_addr),
.ram_wr_data(wr_data)
);
ram_rd ram_rd(
.Clk (clk_5760Hz),
.Reset_n( Reset_n) ,
.rd_flag (rd_flag),
.ram_rd_data(rd_data),
.ram_rd_en (rd_en),
.ram_rd_addr(rd_addr)
);
uart_rx_16bit uart_rx_16bit(
.Clk(Clk),
.Reset_n(Reset_n),
.Data16(rd_data),
.Trans_Go(1'b1),
.uart_tx(uart_tx),
.Trans_done(Trans_done)
);
endmodule
四、仿真与板级验证
进行综合后的原理图如下:
写个tb文件,进行行为级仿真,结果如下:
添加约束后上板验证,结果如下:
总结:这仅仅作为自己的学习笔记进行整理,其中还有很多不完善的地方,还请大家多多指教。