1、实验现象
通过串口发送数据到 FPGA 中,FPGA 接收到数据后将数据存储在双口 ram 的一段连续空间中,通过 Quartus II 软件提供的 In-System Memory Content Editor 工具查看 RAM 中接收到的数据。当需要时,按下设计好的按键 ,则 FPGA 将 RAM 中存储的数据通过串口发送出去。
2、知识点:
1、Altera 公司 Cyclone IV 系列器件的内部结构。
2、存储器(RAM) IP 核的使用。
3、In-System Memory Content Editor 工具查看内存的使用。
4、串口收发+按键+双口 RAM 组成的简易系统设计。
3、系统模块功能划分及接口设计
在设计前,先进行整体功能划分。本工程划分为如下几个模块:
(1)串口接收模块(UART_Byte_Rx);
(2)按键消抖模块(Key_Filter);
(3)RAM模块(dpram);
(4)串口发送模块(UART_Byte_Tx);
(5)控制模块(CTRL)
整个系统框图如图 1 所示。
明确了系统的整体模块架构之后,即可以开始进行顶层文件设计。
4、顶层文件设计
新建工程后,先将已编写好的模块设计文件添加进工程中,并新建一个以名为 UART_DPRAM.v 的设计文件保存在 rtl 下,并设置为顶层文件。
这样对照图1 用 Top-Down 的设计方式就可以先把顶层文件写出。结构图左边的端口 为 input 类型,右边的为 output 类型,内部连线均为 wire 型。然后例化各个模块,这里将波特率设置为 9600bps。其模块接口列表如表 1 所示。
代码如下:
module UART_DPRAM(
Clk,
Rst_n,
Key_in,
Rs232_Rx,
Rs232_Tx
);
input Clk;
input Rst_n;
input Key_in;
input Rs232_Rx;
output Rs232_Tx;
wire [7:0]rx_data;
wire [7:0]tx_data;
wire key_flag;
wire key_state;
wire Rx_Done;
wire Tx_Done;
wire wren;
wire [7:0]wraddress;
wire [7:0]rdaddress;
wire Send_En;
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.Send_En(Send_En),
.Data_Byte(tx_data),
.Baud_Set(3'd0),
.Rs232_Tx(Rs232_Tx),
.Tx_Done(Tx_Done),
.UART_state()
);
uart_byte_rx uart_byte_rx(
.Clk(Clk),
.Rst_n(Rst_n),
.Baud_Set(3'd0),
.Rs232_Rx(Rs232_Rx),
.Data_Byte(rx_data),
.Rx_Done(Rx_Done)
);
key_filter key_filter(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(Key_in),
.key_flag(key_flag),
.key_state(key_state)
);
dpram dpram(
.clock(Clk),
.data(rx_data),
.rdaddress(rdaddress),
.wraddress(wraddress),
.wren(wren),
.q(tx_data)
);
CTRL CTRL(
.Clk(Clk),
.Rst_n(Rst_n),
.key_flag(key_flag),
.key_state(key_state),
.Rx_Done(Rx_Done),
.Tx_Done(Tx_Done),
.wren(wren),
.wraddress(wraddress),
.rdaddress(rdaddress),
.Send_En(Send_En)
);
endmodule
5、控制模块设计
现在编写本系统的控制模块 CTRL.v,模块的接口可参照系统结构图写出。
module CTRL(
Clk,
Rst_n,
key_flag,
key_state,
Rx_Done,
Tx_Done,
wren,
wraddress,
rdaddress,
Send_En
);
input Clk;
input Rst_n;
input key_flag;
input key_state;
input Rx_Done;
input Tx_Done;
output wren;
output reg [7:0]wraddress;
output reg [7:0]rdaddress;
output reg Send_En;
endmodule
为了实现 FPGA 接收到数据后将数据存储在双口 ram 的一段连续空间中的功能,需要设计一个可以实现写地址数据自加的控制逻辑,且其控制信号为串口接收模块输出的 Rx_Done 信号。每来一个 Rx_Done 也就是每接收成功一字节数,地址数进行加一。
assign wren = Rx_Done; //将wren与Rx_Done相连,Rx_Done直接控制dpram的数据写入
//控制写数据地址信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
wraddress <= 8'd0;
else if(Rx_Done)
wraddress <= wraddress + 1'b1; //写地址加一,延迟一拍,数据自动加入到dpram中
else
wraddress <= wraddress;
当按下按键,FPGA 将 RAM 中存储的数据通过串口发送出去。也就是实现按键按下即启动连续读操作,再次按下即可暂停读操作。
reg do_send; //输出标志信号
//控制读数据地址信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
do_send <= 1'd0;
else if(key_flag && !key_state) //当按键按下时
do_send <= ~do_send;
else
do_send <= do_send;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
rdaddress <= 8'd0;
else if(do_send && Tx_Done) //当"do_send == 1'b1"且"Tx_Done == 1'b1"(1字节的数据发送完成)
rdaddress <= rdaddress + 8'd1;
else
rdaddress <= rdaddress;
在上一节中仿真双端口 RAM 时发现其输出tx_data会延迟两个系统时钟周期。这是为了保证数据变化稳定之后才进行数据输出,所以将驱动 Send_En 的信号接两级寄存器进行延迟两拍。当 按键按下后启动一次发送,然后判断上一字节是否发送结束,是则进行下一字节发送,否则不进行下一次发送。
//加入两个寄存器"r0_send_done","r1_send_done",延迟两拍
reg r0_send_done,r1_send_done;
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
r0_send_done <= 1'b0;
r1_send_done <= 1'b0;
end
else begin
r0_send_done <= (do_send && Tx_Done);
r1_send_done <= r0_send_done;
end
//Send_En控制何时发送数据
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Send_En <= 1'b0;
else if(r1_send_done)
Send_En <= 1'b1;
else if(key_flag && !key_state) //第一次按下按键时,Send_En为1
Send_En <= 1'b1;
else
Send_En <= 1'b0;
编译无误后,可以在 RTL viewer 中查到如图 2 所示的顶层结构图,可与实验之初设计的系统结构图进行对比。
通过对比可以验证本工程逻辑设计输出的顶层 RTL 结构图和系统结构图的设想是一致的。
6、激励创建及仿真测试
为了进行仿真测试,接下来介绍仿真测试激励文件的编写。这里由于使用了按键消抖模块,因此须将之前创建的 key_modle.v 仿真模型加入到工程中。新建 UART_DPRAM_tb.v 文 件除了例化各模块,其激励文件可以直接复制串口发送模块的测试激励文件 uart_byte_tx.v 产生激励信号 Rs232_Rx。再次进行分析和综合直至没有错误以及警告,并保存到 testbench 文件夹下。
`timescale 1ns/1ns
`define clk_period 20
module UART_DPRAM_tb;
reg Clk;
reg Rst_n;
wire Key_in;
wire Rs232_Rx;
wire Rs232_Tx;
reg Send_En;
reg [7:0]Data_Byte;
wire [2:0]Baud_Set;
wire Tx_Done;
reg press;
assign Baud_Set = 3'd0;
UART_DPRAM UART_DPRAM(
.Clk(Clk),
.Rst_n(Rst_n),
.Key_in(Key_in),
.Rs232_Rx(Rs232_Rx),
.Rs232_Tx(Rs232_Tx)
);
uart_byte_tx uart_byte_tx(
.Clk(Clk),
.Rst_n(Rst_n),
.Send_En(Send_En),
.Data_Byte(Data_Byte),
.Baud_Set(Baud_Set),
.Rs232_Tx(Rs232_Rx),
.Tx_Done(Tx_Done),
.UART_state()
);
key_model key_model(
.press(press),
.key(Key_in)
);
initial Clk = 1'b1;
always#(`clk_period/2) Clk = ~Clk;
initial begin
//发送模块激励
Rst_n = 1'b0;
press = 1'b0;
Data_Byte = 8'b0;
Send_En = 1'b0;
#(`clk_period*20 + 1) //让复位信号不与时钟边沿对其,更好观察时序关系
Rst_n = 1'b1;
#(`clk_period*50)
Data_Byte = 8'haa; //第一次,发送"8'haa"
Send_En = 1'b1;
#`clk_period
Send_En = 1'd0;
@(posedge Tx_Done) //当Tx_Done信号出现上升沿时,进行接下来的语句
#(`clk_period*5000)
Data_Byte = 8'h55; //第二次,发送"8'h55"
Send_En = 1'b1;
#`clk_period
Send_En = 1'd0;
@(posedge Tx_Done) //当Tx_Done信号出现上升沿时,进行接下来的语句
#(`clk_period*5000)
Data_Byte = 8'h33; //第三次,发送"8'h33"
Send_En = 1'b1;
#`clk_period
Send_En = 1'd0;
@(posedge Tx_Done) //当Tx_Done信号出现上升沿时,进行接下来的语句
#(`clk_period*5000)
Data_Byte = 8'haf; //第四次,发送"8'haf"
Send_En = 1'b1;
#`clk_period
Send_En = 1'd0;
@(posedge Tx_Done) //当Tx_Done信号出现上升沿时,进行接下来的语句
#(`clk_period*5000)
//按下一次按键
press = 1'b1;
#(`clk_period*3)
press = 1'b0;
#(`clk_period*2500000)
$stop;
end
endmodule
这里将按键的仿真模型加仿真脚本后进行功能仿真就可以看到如图 3 所示的波形文件。每当写入地址加一时数据均可以有效的写入,按键按下后每当一次输出结束后读地址也进行加一,实现数据输出。
放大波形文件的数据发送部分,如图 4 所示。可看出在第一次给出写使能信号后, 将数据 aa 写入地址 0,写入成功后写入地址加一。tx_data 之所以会在没有 Send_en 时数据会更新,是因为读取地址在复位时地址初始值为 0,在成功写入数据 aa 后,自然就会显示更新,但是 Rs232_Tx 不会有数据更新。延迟两个系统时钟周期是因为 RAM IP 本身的特性。 可以放大后面数据写入的过程,tx_data 的值并不会更新。
放大波形文件的发送部分,如图 5 和图 6 所示。可看出每当 Send_en 有效后均 会进行一字节数据的发送。传输完激励中的四个数据后即输出 0,这是由于这里将 RAM 定 为 256 宽度,只用的前四个,没有用到的数据即全为 0。且可以看出 Send_en 与数据发送严 格对齐,与设计预期相符。
以上,仿真部分的验证工作就顺利完成。
7、板级验证
经过上述工作,所有代码都已经设计完毕,接下来介绍板级测试和验证方法:
(1)按照 AC620/AC6102/AC609 开发板的引脚分配表分配正确的引脚, 本设计实例针 对各开发板的管脚分配如下表所示:
(2)分配引脚后进行全编译无误后,下载进开发板,打开串口助手依次输入 11、22、aa、 dd、34、67。按下按键 S0 后即可看见数据源源不断的发送到串口上位机软件,再次按下即 停止。且可以计算得每一个循环的数据量自动补 0 到 256 个字节,与设计的 RAM 存储量相 符合,如图 7 所示
(3)按下 FPGA 复位申请并发送新的数据 68、76、ff,可以看出,RAM 中已有的数据并 不会消除,只是会加入新的数据,且一个循环数据量也是自动补 0 到 256 字节,如图8所示。