FPGA入门笔记011_B——搭建串口收发与存取双口RAM简易应用系统
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所示。
本文作者:Yamada_Ryo
本文链接:https://www.cnblogs.com/little55/p/18122329
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步