FPGA_学习_11_IP核_RAM_乒乓操作

news2024/11/18 18:14:24

本篇博客学习另一个IP核,RAM。 用RAM实现什么功能呢? 实现乒乓操作。 乒乓操作是什么呢?

参考:

FPGA中的乒乓操作思想_fpga中乒乓操作的原因_小林家的龙小年的博客-CSDN博客

何为乒乓操作_fanyuandrj的博客-CSDN博客

以下是本人理解:

乒乓操作可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

例如:

数据位宽的转换,要将8位的数据转换为16位,按照传统方法,每两个时钟周期完成一次转换,输出数据的变化与时钟信号不是同步的。使用乒乓操作,数据写入数据缓冲模块的时候使用50M的时钟,读出时使用25M的时钟,每次读出16位,这样不仅实现了数据位宽的转换,还使得输出的数据可以被一个低速的时钟同步。

乒乓骚操作1:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 先前写入 数据缓冲模块1的100个8位的数据,已经被输出模块读取完了。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟也是50Mhz, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了。

如此循环,就可以,... 嗯,... , 我反正暂时不清楚这个操作有什么意义。

乒乓骚操作2:

假设输入数据的时钟50Mhz,100个8位的数据先写入 数据缓冲模块1, 然后再100个8位的数据写入 数据缓冲模块2,与此同时,输出模块读取 数据缓冲模块1的数据。 如果输出模块读取时钟也是25Mhz, 待100个8位的数据已存入 数据缓冲模块2时, 要想把先前写入 数据缓冲模块1的100个8位的数据全部取出,输出模块读取的位宽得1次读16位。

50Mhz 8位宽的写100的时间 =  25Mhz 16位宽的读50次的时间。

下面再次把100个8位的数据先写入 数据缓冲模块1,与此同时,输出模块读取 数据缓冲模块2的数据。 如果输出模块读取时钟是25Mhz读取位宽是16位, 待100个8位的数据已存入 数据缓冲模块1时, 先前写入 数据缓冲模块2的100个8位的数据,也已经被输出模块读取完了,50个16位。

如此循环,好像有那么点意思了,连续的 50M 8位宽数据,变成了连续的25M 16位宽的数据。 时钟变慢了,位宽变大了。

本文先尝试实现乒乓骚操作1,后续如果我功力深厚了再来尝试实现乒乓骚操作2

1 RAM IP核配置步骤

(Vivado 赛灵思)

截图warning!

Block Memory Generator是用FPPA内部专用存储资源给你生成RAM。

上面Distributed Memory Generator这个是用D触发器和查找表来帮你实现的RAM。

我取名IP_RAM

 本次实验位宽为8,深度为256

 

 OK后,后面的弹窗你就一顿OK,下一步就可以了。

我们是看Verilog的例化模板。 

2 时序图

乒乓操作是要例化个简单口RAM的IP核的,绿线上面例化的第一个简单口RAM→RAM1的相关时序,绿线下面是例化的第而个简单口RAM→RAM2的相关时序。

RAM_wea_d表示RAM_wea延迟(delay)一拍,可用于指示当前是RAM1输出有效还是RAM2的输出有效。

画时序图是费劲的活儿,但是画完自己的理解确实也会更进一分。

{signal: [
    {name: 'clk',   		wave: 'p...........................................'	},
    {},
    {name: 'rst_n',			wave: '01..........................................'	},
    {},
  	{name: 'RAM1_wea',		wave: '01....0....1....0....1....0....1....0....1..'	},
    {},
  	{name: 'RAM1_addra',	wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},    
  	{name: 'RAM1_dina',		wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {},	
  
  	{name: 'RAM1_addrb',	wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  	{name: 'RAM1_doutb',	wave: '2.......22222.....22222.....22222.....22222.'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {}, 
  	
  	{name: 'RAM2_wea',		wave: '0.....1....0....1....0....1....0....1....0..'	},
    {},
    
  	{name: 'RAM2_addra',	wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
  	{name: 'RAM2_dina',		wave: '2......22222.....22222.....22222.....22222..'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {}, 
  
   	{name: 'RAM2_addrb',	wave: '2.22222.....22222.....22222.....22222.....22'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},    
  	{name: 'RAM2_doutb',	wave: '2..22222.....22222.....22222.....22222.....2'	, data: ['0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255','0','1','2','...','255']},
    {},	
    {name: 'RAM1_wea_d',	wave: '0.1....0....1....0....1....0....1....0....1.'	},

  
    {}
]}

3 测试代码

本博客先暂时只实现第一种乒乓操作

`timescale 1ns / 1ps

module ram_pp(
        input   wire            clk     ,
        input   wire            rst_n   ,
        output  reg     [7:0]   dout
);


//==================================================================
//                        Parameter define
//==================================================================

parameter		MAX = 256 - 1;


//==================================================================
//                        Internal Signals
//==================================================================
// 一个简单双口RAM的IP里面,a代表写,b代表读
reg             RAM1_wea        ;       // 写-使能
reg     [7:0]   RAM1_addra      ;       // 写-地址
wire    [7:0]   RAM1_dina       ;       // 写-数据
reg     [7:0]   RAM1_addrb      ;       // 读-地址
wire    [7:0]   RAM1_doutb      ;       // 读-数据

reg             RAM2_wea        ;
reg     [7:0]   RAM2_addra      ;
wire    [7:0]   RAM2_dina       ;
reg     [7:0]   RAM2_addrb      ; 
wire    [7:0]   RAM2_doutb      ;

reg             RAM1_wea_d      ;       // RAM1的写使能延迟一拍

IP_RAM inst_RAM1 (
  .clka(clk),           // input wire clka              写-时钟
  .wea(RAM1_wea),       // input wire [0 : 0] wea       写-使能
  .addra(RAM1_addra),   // input wire [7 : 0] addra     写-地址
  .dina(RAM1_dina),     // input wire [7 : 0] dina      写-数据
  .clkb(clk),           // input wire clkb              读-时钟
  .addrb(RAM1_addrb),   // input wire [7 : 0] addrb     读-地址
  .doutb(RAM1_doutb)    // output wire [7 : 0] doutb    读-数据
);                      //                              谨记:不要同时读写同一地址的数据。

IP_RAM inst_RAM2 (
  .clka(clk),           // input wire clka              写-时钟
  .wea(RAM2_wea),       // input wire [0 : 0] wea       写-使能
  .addra(RAM2_addra),   // input wire [7 : 0] addra     写-地址
  .dina(RAM2_dina),     // input wire [7 : 0] dina      写-数据
  .clkb(clk),           // input wire clkb              读-时钟
  .addrb(RAM2_addrb),   // input wire [7 : 0] addrb     读-地址
  .doutb(RAM2_doutb)    // output wire [7 : 0] doutb    读-数据
);

// 在对RAM1做写操作的时候,与此同时对RAM2做读操作
// 对RAM1做写操作完成,RAM2的做读操作也会完成,此时则对RAM1做读操作,对RAM2做写操作
// 对RAM2做写操作完成,RAM1的做读操作也会完成,此时则对RAM2做读操作,对RAM1做写操作
// 如此循环。

// 上面的文字描述有点绕,但告诉了我们三个道理。
// 1、一个RAM在写,则另一个RAM必然在读,所以两个RAM的写使能是完全相反的电平。
// 2、当RAM2写完的时候,就到我RAM1写了,这时候RAM1写使能拉高。
// 3、当RAM1写完的时候,RAM1进入读操作,这时候RAM1写使能拉低。


//----------------------------- RAM1_wea -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_wea <= 1'b0;                  
        end
        // RAM2写完,到RAM1写,RAM1_wea拉高
        else if ( RAM2_addra == MAX && RAM1_wea == 1'b0) begin
                RAM1_wea <= 1'b1;
        end
        // RAM1写完,则RAM1变成读,RAM1_wea拉低
        else if ( RAM1_addra == MAX && RAM1_wea == 1'b1) begin
                RAM1_wea <= 1'b0;
        end
        else begin
                RAM1_wea <= RAM1_wea;
        end
end

//----------------------------- RAM2_wea -----------------------------
always @(*) begin
        RAM2_wea        <= ~RAM1_wea;
end



//----------------------------- RAM1_addra -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_addra      <= 'd0;     
        end
        else if (RAM1_wea == 1'b1) begin
                if (RAM1_addra == MAX) begin
                        RAM1_addra      <= 'd0;
                end
                else begin
                        RAM1_addra      <= RAM1_addra + 1'b1;
                end
        end
        else begin
                RAM1_addra <= 'd0;
        end
end

//----------------------------- RAM1_addrb -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_addrb      <= 'd0;     
        end
        else if (RAM1_wea == 1'b0) begin
                if (RAM1_addrb == MAX) begin
                        RAM1_addrb      <= 'd0;
                end
                else begin
                        RAM1_addrb      <= RAM1_addrb + 1'b1;
                end
        end
        else begin
                RAM1_addrb <= 'd0;
        end
end

//----------------------------- RAM2_addra -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM2_addra      <= 'd0;     
        end
        else if (RAM2_wea == 1'b1) begin
                if (RAM2_addra == MAX) begin
                        RAM2_addra      <= 'd0;
                end
                else begin
                        RAM2_addra      <= RAM2_addra + 1'b1;
                end
        end
        else begin
                RAM2_addra <= 'd0;
        end
end

//----------------------------- RAM2_addrb -----------------------------
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM2_addrb      <= 'd0;     
        end
        else if (RAM2_wea == 1'b0) begin
                if (RAM2_addrb == MAX) begin
                        RAM2_addrb      <= 'd0;
                end
                else begin
                        RAM2_addrb      <= RAM2_addrb + 1'b1;
                end
        end
        else begin
                RAM2_addrb <= 'd0;
        end
end

//----------------------------- RAM1_dina -----------------------------
assign RAM1_dina = RAM1_addra;

//----------------------------- RAM2_dina -----------------------------
assign RAM2_dina = RAM2_addra;

//----------------------------- RAM1_wea_d -----------------------------
// 由于RAM的输出信号要延迟一拍
// 因此利用RAM1的写使能信号RAM1_wea,延迟一拍作为整个模块输出dout的读有效信号。
always @(posedge clk or negedge rst_n) begin
        if (rst_n == 1'b0) begin
                RAM1_wea_d <= 1'b0;        
        end
        else begin
                RAM1_wea_d <= RAM1_wea;         
        end
end

//----------------------------- dout -----------------------------
always @(*) begin
        // RAM1_wea_d为高,则RAM1正在进行写操作,这时候不能读RAM1,而RAM2这时候正在输出,正好读RAM2的数据。
        if (RAM1_wea_d == 1'b1) begin
                dout <= RAM2_doutb;
        end
        // RAM1_wea_d为低,则RAM1正在进行读操作,这时候正好RAM1在输出,正好读RAM1
        else begin
                dout <= RAM1_doutb;
        end
end



endmodule

 这个代码里面的注释是目前为止我写的最多的啦,希望看了会帮助理解。

4 仿真代码

`timescale 1ns/1ps

module tb_ram_pp (); /* this is automatically generated */

    parameter MAX = 256 - 1;
    reg         clk;
    reg         rst_n;
    wire [7:0]  dout;

    ram_pp inst_ram_pp (.clk(clk), .rst_n(rst_n), .dout(dout));

    initial begin
        clk = 1;
        forever #(10) clk = ~clk;
    end

    initial begin
        rst_n <= 0;
        #200
        rst_n <= 1;
    end

endmodule

这个仿真代码比较简单,因为咱们写的ram_pp模块本身的输入输出接口很少,所以不怎么操心,直接调用ram_pp模块就行了。

By the way,我用的Modelsim仿真,本文不讨论怎么搞Modelsim仿真。

5 仿真结果

刚仿真出来的时候我以为哪里有问题,因为发现第一个周期是没有输出的。 后来想明白了原因,因为更早之前RAM没写入任何数据,所以它第一个周期就应该没有输出。 另外,建议仿真的时长弄长一点(我用的30us),从第二个周期开始看。

仿真结果来看,实验和预期的目标时序是相符的,实验是成功的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/669798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Clion开发STM32之日志模块(参考RT-Thread)

前言 日志对于开发和排错方面有着很重要的影响。通过查看RT-Thread的源码&#xff0c;将日志的打印输出划分到具体的文件和通过宏定义对具体的日志等级进行划分&#xff0c;这样就比较方便。结合此源码的形式将其分离出来&#xff0c;作为自己项目的日志框架进行使用分为日志驱…

crossover软件下载2023最新版虚拟机

在Mac系统中一直存在一个比较令用户们头疼的问题&#xff0c;那就是安装不了想要的Windows软件。如果使用的第一台电脑就是MacBook那接触到的Windows软件想必并不是很多。但我们中的大多数人都是从小先学习了Windows的操作系统&#xff0c;再过渡到Mac系统上的。 那有小伙伴会…

最新基于MATLAB 2023a的机器学习、深度学习实践应用

MATLAB 2023版的深度学习工具箱&#xff0c;提供了完整的工具链&#xff0c;使您能够在一个集成的环境中进行深度学习的建模、训练和部署。与Python相比&#xff0c;MATLAB的语法简洁、易于上手&#xff0c;无需繁琐的配置和安装&#xff0c;能够更快地实现深度学习的任务。 M…

Flink流批一体计算(4):Flink功能模块

目录 Flink功能架构 Flink输入输出 Flink功能架构 Flink是分层架构的分布式计算引擎&#xff0c;每层的实现依赖下层提供的服务&#xff0c;同时提供抽象的接口和服务供上层使用。 Flink 架构可以分为4层&#xff0c;包括Deploy部署层、Core核心层、API层和Library层 部署层…

基于SSM的宠物领养系统的设计与实现

1.引言 动物作为人类的宠物已经有几千年的历史了&#xff0c;尤其是猫和狗因其天性被人类所喜爱和推崇&#xff0c;好多的成语故事、俗语等都及它们有关。但是&#xff0c;近几年来由于生活节奏的加快&#xff0c;人们的压力增大&#xff0c;没有时间和空间去照顾宠物&#xf…

ProGuard 进阶系列(三) Java 类文件解析

书接上文&#xff0c;当我们从用户的配置文件中读取到所有的配置信息后&#xff0c;下一步便是将配置中的指定的类文件进行读取&#xff0c;构建需要混淆的 Java 类文件的语法树。在阅读类文件之前&#xff0c;先来看一下输入输出参数中的内容&#xff0c;我使用的是一个 Andro…

大一下暑期计划 + 2023字节青训营预告直播

目录 &#x1f33c;前言 &#x1f339;后端学习方法 &#x1f333;1&#xff0c;层次 &#x1f333;2&#xff0c;体系 &#x1f333;3&#xff0c;算法和数据结构 &#x1f333;4&#xff0c;总结 &#x1f339;前端学习方法 &#x1f333;基础 &#x1f339;求职中如…

如何在Microsoft Excel中使用LEN函数

如果你曾经想尝试查找一行文本中的字符数&#xff0c;你可以使用Microsoft Excel来查找&#xff0c;这要归功于LEN函数。以下是如何使用它。 一、什么是 LEN 函数 LEN函数是一个简单的计算函数&#xff0c;用于计算给定文本字符串中的所有字符&#xff0c;包括数字、字母、特…

【数据库课设】图书馆资源管理系统 源码+流程图+结构设计(借还图书 逾期罚款 图书管理 读者管理 信息查询)python实现

文章目录 一 实现功能&#xff08;1&#xff09;管理员功能&#xff08;2&#xff09;读者功能 二 数据流图三 概念结构设计四 文件目录五 源码&#xff1a;main.py六 运行截图 一 实现功能 &#xff08;1&#xff09;管理员功能 一个管理员编号对应一个密码&#xff0c;且需…

redis—安装以及可视化

前言 redis 是一种非关系型数据库&#xff0c;什么是非关系型数据库&#xff0c;之前我们在mysql专栏 也有提到过&#xff0c;这边就不再过多的赘述&#xff0c;忘记了的小伙伴可以再次阅读这篇文章 终于明白了数据库的【关系型】与【非关系型】 其实这还是挺重要的&#xff…

ruoyi+vue回显数字的问题,解决方案

在项目中用ruoyi框架和前端vue进行开发&#xff0c; 需求是在前端生成下拉框&#xff0c;下拉框中的内容需要调用后端接口进行数据返回&#xff0c; 现在新增的时候&#xff0c;数据已经返回了&#xff0c;但是再修改的时候&#xff0c;进行回显数据导致前端列表中展示出来的…

城市排水监测系统为城市排水防涝提质增效

城市化进程中&#xff0c;城市排水系统成为城市基础设施建设的重要组成部分。然而&#xff0c;随着气候变化和城市建设规模的扩大&#xff0c;极端天气和内涝风险不断增加&#xff0c;城市的排水系统面临巨大挑战。因此&#xff0c;建立一套智能化城市排水监测系统&#xff0c;…

【python】一些常用的pandas技巧

有了gpt之后&#xff0c;确实很多代码都可以让gpt给改错。嘎嘎香 merge多个dateframe https://stackoverflow.com/questions/44327999/how-to-merge-multiple-dataframes data_net [a,b,c,d] net_merged reduce(lambda left,right: pd.merge(left,right,on[key column],ho…

小程序安全指南:保护用户数据的最佳实践

第一章&#xff1a;引言 近年来&#xff0c;小程序已成为移动应用开发的重要组成部分。它们为用户提供了方便的功能和个性化的体验&#xff0c;然而&#xff0c;与此同时&#xff0c;小程序安全问题也引起了广泛的关注。保护用户数据是开发者应该高度重视的问题。在本指南中&a…

JavaScript ES11新特性

文章目录 导文可选链操作符&#xff08;Optional Chaining Operator&#xff09;空值合并操作符&#xff08;Nullish Coalescing Operator&#xff09;动态 import() 函数BigInt 类型Promise.allSettled() 导文 JavaScript ES11&#xff0c;也被称为ES2020&#xff0c;引入了一…

经纬度、时差知识整理(理解与应用)

经纬度是经度与纬度的合称组成一个坐标系统&#xff0c;称为地理坐标系统&#xff0c;它是一种利用三度空间的球面来定义地球上的空间的球面坐标系统&#xff0c;能够标示地球上的任何一个位置。初一的同学在学地理这门课的时候&#xff0c;一上来很快就会学到经纬度这个概念。…

PC市场寒冬,大众还需要PC吗?

PC市场寒冬&#xff0c;大众还需要PC吗&#xff1f; PC&#xff08;个人电脑&#xff09;市场从2016年智能手机兴起之时便进入下滑态势&#xff0c;到2020年疫情发生后&#xff0c;居家办公、在线教育等需求曾给PC市场带来连续六个季度的增长。⁴ 好景不长&#xff0c;进入202…

mybatisplus分页total总数为0

mybatisplus分页total总数为0 背景&#xff1a;最近初始化新项目时&#xff0c;使用mybatisplus分页功能发现 records 有记录&#xff0c;但是 total 总是为0&#xff0c;于是开启了一顿“知识寻求”之路SpringBoot版本 <parent><groupId>org.springframework.boo…

Makerbase CANable V2.0 固件升级或替换

第1部分 应用软件与固件 应用软件CANable V2.0CANable V1.0cangaroocandleLight/slcan(支持CAN FD)candleLight/slcan/cantactBUSMASTER V3.2.2candleLightcandleLight/pcan/cantactTSMastercandleLightcandleLight/pcan/cantactPCAN-Explorer 5、pcan view不支持pcancantacts…

一文带你全面理解 MySQL 中的常用函数

0️⃣前言 MySQL是一种常用的关系型数据库管理系统&#xff0c;它提供了许多内置函数来处理数据。本文将介绍MySQL中的各种常用函数&#xff0c;包括字符串函数、日期函数、数学函数、聚合函数等。 文章目录 0️⃣前言1️⃣字符串函数1.1CONCAT函数1.2SUBSTRING函数1.3REPLACE函…