| **银杏科技有限公司旗下技术文档发布平台** |||| |技术支持电话|**0379-69926675-801**||| |技术支持邮件|Gingko@vip.163.com||| ^ 版本 ^ 日期 ^ 作者 ^ 修改内容 ^ | V1.0 | 2020-07-01 | gingko | 初次建立 | \\ \\ ===== 实验二十:单口RAM实验——基于单口RAM的ARM与FPGA通信 ===== ==== 一、 实验目的与意义 ==== - 掌握单口RAM IP核的调用及例化方法。 - 掌握RAM读写时序。 - 掌握单口RAM在工程中的使用方法。 ==== 二、 实验设备及平台 ==== - iCore4TX 双核心板[[https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22598974120.3.29da532fLkazHH&id=614919247574|点击购买]]。 - USB CABLE(或相同功能)仿真器。 - USB CABLE(或相同功能)仿真器。 - USB CABLE(或相同功能)仿真器。 - JLINK(或相同功能)仿真器。 - Micro USB线缆。 - Keil MDK 开发平台。 - ISE开发平台。 - 电脑一台。 ==== 三、 实验原理 ==== * RAM全称是Random Access Memory(随机存取存储器),是与处理器进行数据交互的存储器。既可向指定地址单元写入信息也可从指定地址单元中读取信息,掉电后数据丢失,属于易失性存储器。 * 单口RAM IP核是FPGA内部常用的一种存储单元,只有一组数据线和地址线,因此不能同时进行读写操作。其操控时序比较简单,以时钟上升沿作为触发事件,在上升沿到来前保证地址和数据稳定即可将数据准确的写入对应的地址单元中,读取时,也是如此。其读写时序如图20-1所示。 * 20-1(a) 读时序图 {{ :icore4tx:icore4tx_fpga_20_1_a.png?direct |20-1(a) 读时序图}} * 20-1(b) 写时序图 {{ :icore4tx:icore4tx_fpga_20_1_b.png?direct |20-1(b) 写时序图}} * 本实验基于ARM+FPGA构架,FPGA例化单口RAM,作为ARM的外部存储器。ARM通过SPI总线对FPGA内部的RAM进行读写操作。本实验重点是学习如何在工程中例化单口RAM IP核,以及利用SPI总线对FPGA内部RAM进行读写操作。 ==== 四、 RAM IP核调用 ==== 1、新建一个工程名为spram的工程,然后在工程目录选中工程,右键单击,下来菜单中点击New Source …。 {{ :icore4tx:icore4tx_fpga_20_2.png?direct |图20-2}} 2、弹出界面中选中IP(CORE Generator & Architecture Wizard)选项,并在右侧File name栏输入新建IP核的文件名,然后点击Next。 {{ :icore4tx:icore4tx_fpga_20_3.png?direct&800 |图20-3}} 3、在Select IP界面的Memories & Storage Elements 栏下找到RAM & ROM,点击前面的“+”展开,单击选中Block Memory Generator,点击Next 进入下一步。 {{ :icore4tx:icore4tx_fpga_23_4.png?direct&800 |图20-4}} 4、直接点击Finished。 {{ :icore4tx:icore4tx_fpga_20_5.png?direct |图20-5}} 5、然后弹出RAM IP 核相关信息设置的界面,这里的Interface Type选项保持默认设置Native即可,然后点击Next,如下图所示: {{ :icore4tx:icore4tx_fpga_20_6.png?direct |图20-6}} 6、在Memory Type 栏选择RAM类型,本实验是单口RAM实验,所以这里选择Single Port RAM,然后点击Next。 {{ :icore4tx:icore4tx_fpga_20_7.png?direct |图20-7}} 7、设置单口RAM的位宽参数为8位,存储深度为1024,Operating Mode和Enable下勾选如下选项,如图中所示;然后点击Next。 {{ :icore4tx:icore4tx_fpga_20_8.png?direct |图20-8}} 8、此界面配置RAM初始化内容,本实验不需要初始化RAM数据,保持默认即可,直接点击Next。 {{ :icore4tx:icore4tx_fpga_20_9.png?direct |图20-9}} 9、接下来默认设置,点击Next。 {{ :icore4tx:icore4tx_fpga_20_10.png?direct |图20-10}} 10、点击Generate,完成单口RAM IP核的添加。 {{ :icore4tx:icore4tx_fpga_20_11.png?direct |图20-11}} 11、回到工程界面,可以看到工程目录中多了RAM IP核文件。 {{ :icore4tx:icore4tx_fpga_20_12.png?direct |图20-12}} ==== 五、 代码讲解 ==== * IP核调用只是生成相关的模块文件,在应用中要实现存储、读写功能,还需要对IP核进行实例化操作,其例化代码如下: //------------------------------------------------// //spi_ram //负责存储SPI通信的数据(具体的存储位置可通过spi命令寄存器设置) //单口RAM的缺点在于只有一套数据线和地址线、一个时钟信号,需要分时复用。 //有点在于相同容量的RAM占用的逻辑资源小于双口RAM。 spi_ram u0( .clock(clk), .wren(wr_en), .address(address), .data(receive_byte), .rden(rd_en), .q(data_out) ); - 工程中添加了单口RAM IP核以及进行例化之后,还要实现基于RAM时序的读写操作。由于ARM芯片对单口RAM的读写操作是通过SPI总线进行的,那么,首先要实现ARM和FPGA之间的SPI通信。 - 有关ARM和FPGA通信的具体内容,可以参考4TX实验十八SPI通信的内容。而ARM对FPGA内部单口RAM的相关操作则是通过对传输指令的解析实现的。具体代码如下: case(rx_state) rx_idle_state:begin//空闲状态,匹配伪命令时钟 rx_state <= rx_judge_state; end rx_judge_state:begin//接收指令字节,判断指令含义分配跳转状态机状态 case(receive_byte) 8'h01:begin//存储指令编码(获取设备状态) order <= receive_byte; end 8'h02:begin//存储指令编码,跳转状态(写数据的长度信息) rx_state <= rx_wr_length_state; order <= receive_byte; end 8'h04:begin//存储指令编码,跳转状态(写数据的地址信息) rx_state <= rx_wr_add_state; order <= receive_byte; end 8'h05:begin//存储指令编码,跳转状态(读数据的长度信息) rx_state <= rx_rd_ledgth_state; order <= receive_byte; end 8'h07:begin//存储指令编码,跳转状态(读数据的地址信息) rx_state <= rx_rd_add_state; order <= receive_byte; end default:; endcase end rx_wr_length_state:begin//获取写数据长度信息,高字节在前,低字节在后 case(rx_cnt) 8'd0:begin wr_length_r[15:8] <= receive_byte; rx_cnt <= rx_cnt + 1'd1; end 8'd1:begin wr_length_r <= {wr_length_r[15:8],receive_byte}; end default:begin end endcase end rx_wr_add_state:begin//获取写数据地址,高字节在前,并产生RAM的写地址信息 case(rx_cnt) 8'd0:begin wr_address_r[15:8] <= receive_byte; rx_cnt <= rx_cnt + 1'd1; end 8'd1:begin wr_address_r <= {wr_address_r[15:8],receive_byte}; rx_cnt <= rx_cnt + 1'd1; wr_en_r <= 1'd1; end 8'd2:begin wr_address_r <= wr_address_r + 1'd1; if(wr_cnt==wr_length_r-1'd1) begin wr_cnt <= 12'h00; rx_cnt <= rx_cnt + 1'd1; end else begin wr_cnt <= wr_cnt + 1'd1; end end default:begin end endcase end rx_rd_ledgth_state:begin//获取读数据长度信息,高字节在前,低字节在后 case(rx_cnt) 8'd0:begin rd_length_r[15:8] <= receive_byte; rx_cnt <= rx_cnt + 1'd1; end 8'd1:begin rd_length_r <= {rd_length_r[15:8],receive_byte}; end default:begin end endcase end rx_rd_add_state:begin//获取读数据地址信息,高字节在前,并产生RAM的读地址信息 case(rx_cnt) 8'd0:begin rd_address_r[15:8] <= receive_byte; rx_cnt <= rx_cnt + 1'd1; end 8'd1:begin rd_address_r <= {rd_address_r[15:8],receive_byte}; rx_cnt <= rx_cnt + 1'd1; rd_en_r <= 1'd1; end 8'd2:begin rd_address_r <= rd_address_r + 1'd1; if(rd_cnt==rd_length_r-1'd1) begin rd_cnt <= 12'h00; rx_cnt <= rx_cnt + 1'd1; end else begin rd_cnt <= rd_cnt + 1'd1; end end default:; endcase end * 通过case语句对接收数据进行判断,根据数据代表的指令含义执行相应的操作,从而实现ARM芯片通过SPI总线发送命令,FPGA解析命令并对RAM进行相关操作的功能。 ==== 六、 实验步骤及实验结果 ==== {{ :icore4tx:icore4tx_fpga_20_13.png?direct |图20-13}} - 将硬件正确连接,如图20-13所示。 - 打开putty串口调试工具,打开设备管理器查看对应的端口信息,在putty中打开对应的端口,设置波特兰为115200,用于打印串口信息及控制RAM读写; - 将编写好的FPGA代码进行编译,并下载到开发板中; - 将编写好的ARM代码编译,并下载到开发板中,putty工具中会打印相应的SPI通信相关信息(若想多次测试,查看结果在putty中输入“test”即可); - 观察实验现象及putty终端打印信息——FPGA_LED闪烁,putty终端打印如图20-14所示. {{ :icore4tx:icore4tx_fpga_20_14.png?direct |图20-9}} ==== 七、 拓展实验 ==== - 通过Signaltap观察RAM读写时序是否和参考时序一致。 - 别设置双时钟和输出端口增加寄存器,看看读取数据有什么变化。