软件设计中的Ping Pong 操作

来源:互联网 发布:ubuntu root用户提权 编辑:程序博客网 时间:2024/06/05 04:14

文章1:

原文地址:http://www.voidcn.com/blog/zhuzhiqi11/article/p-1971276.html

1 什么是pingpong?

   pingpong是一种数据缓存的手段,通过pingpong操作可以提高数据传输的效率。

2 什么时候需要pingpong?

在两个模块间交换数据时,上一级处理的结果不能马上被下一级所处理完成,这样上一级必须等待下一级处理完成才可以送新的数据,这样就会对性能产生很大的损失。

引入pingpong后我们可以不去等待下一级处理结束,而是将结果保存在pong路的缓存中,pong路的数据准备好的时刻,ping路的数据也处理完毕(下一级),然后无需等待直接处理pong路数据,上一级也无需等待,转而将结果存储在ping路。这样便提高了处理效率。

3 实现。

实现时我们需要,两个指针,ping_rd,pong_wr. 还要保存数据的缓存reg [31:0] buf [1:0];

always @(posedge clk or negedge rst)if(!rst)ping_rd <= 0;else if(rd)ping_rd <= ~ping_rd;always @(posedge clk or negedge rst)if(!rst)ping_wr <= 0;else if(wr)ping_wr <= ~ping_wr;always @(posedge clk )if(wr)buf[ping_wr] <= data_in;assgin data_out =  buf[ping_rd];// 为了满足模块间的握手,可以再增加两个标识位,表示相应的buffer是否有效。reg[1:0] buf_valid;always @(posedge clk or negedge rst)if(!rst)buf_valid[0]<= 1'b0;else if(rd & ~ping_rd)buf_valid[0]<= 1'b0;else if(wr & ~ping_wr)buf_valid[0]<= 1'b1;always @(posedge clk or negedge rst)if(!rst)buf_valid[1]<= 1'b0;else if(rd & ping_rd)buf_valid[1]<= 1'b0;else if(wr & ping_wr)buf_valid[1]<= 1'b1;wire data_rdy = buf_valid[1] | buf_valid[0];wire m_full    = buf_valid[1] & buf_valid[0];

--------------------------------------------------------------------------------------------------------------------------------

文章2:

下图给出了pingpang的基本原理框图,从图上可以看出使用pingpang的主要作用 就是使用多个低速的数据预处理模块处理高速的输入数据流。这样做可以提高系统的数据吞吐量(如果不使用乒乓的话数据预处理模块会成为设计中限制系统数据吞 吐量的瓶颈),同时增加了数据缓冲延迟。

ping pong buffer的优点 - myswirl - 漩涡的窝

另一个解释:

所谓ping-pong buffer,也就是定义两个buffer,当有数据进来的时候,负责写入buffer的进程就寻找第一个没有被占用而且可写的buffer,进行写入,写好之后,将占用flag释放,同时设置一个flag提示此buffer已经可读,然后再接下去找另外一个可写的buffer,写入新的数据。

而读入的进程也是一直对buffer状态进行检测,一旦发现没有被占用,而且已经可以被读,就把这个buffer的数据取出来,然后标志为可写。

感觉这个靠谱点。

----------------------------------------------------------------------------------------------------------------------------------

文章3:

Cache ping-pong

原文地址:http://www.cnblogs.com/fengshang/p/3524752.html

Cache

CPU为了更快速度读取数据,都会用到Cache,因为直接访问RAM速度会比较慢。现代的CPU架构都会支持多级Cache,有单核独享的cache,也有多核共享的cache。

 

 

这样做读取速度是快了,但是在并发编程时就会遇到问题,如果任何一个核对数据有写操作,如何保证所有core都能及时看到新的数据?考虑如下程序:

复制代码
1 std::atomic<int> counter{0};2 3 void thread_func(void)4 {5      counter++;  6 }
复制代码

如果两个线程在两个核上并发执行这个函数,会怎么样?如果每个core都使用自己cache中的counter值(初始值为0,所以两个core的私有cache中可能都是0)进行++操作,那么最终的结构有可能counter==1.因为如果两核不同步cache中对counter的缓存,就会出现这种问题。这就是cache的一致性问题。为了保证cache的一致性,那么有私有cache的CPU体系架构必须保证同一个cache line(cache里缓存的最小单位,可能是32、64或128字节)同一时刻只能由一个core缓存。通常的write-through cache(写操作一直穿透到RAM写完毕)为了实现这种一致性,会在某个core发现自己缓存的cache line被其他core命中(进入其他core的cache中)时,主动使自己缓存的cache line失效。也就是说如果这个core下面计算需要这份数据,需要重新从RAM中一级一级载入cache,而不再使用之前cache中的缓存。这就好像是对这个数据的读写失去了cache,每次都需要穿透到RAM的操作。

 

Cache ping-pong

当多个core要并行操作内存中的同一份数据,就会出现Cache ping-pong的问题,举个例子:

1. core1 从内存中加载Cache line做操作

2. core2 也要对这个数据操作,于是它也加载了这个Cache line。core1发现core2加载了这个cache line,于是让自己的加载的cache line失效

3. core1 又需要处理这个数据,core1需要重新从内存中加载这个cache line,同时导致core2的cache line失效

4. core2 又要处理这个数据,又要加载cache line,导致core1的cache line失效

5. 如此反复。

 

例如如下示例程序的处理过程。考虑core1,core2同时执行这个thread_func的情况。

复制代码
std::atomic<int> counter{0};void thread_func(void){    while (counter < 100)    {          counter++;            // do other thing.    }}
复制代码

cache ping-pong等效于cache失效,每次CPU都要从RAM中加载数据,这会导致很大的性能问题,这也是编写多线程程序需要考虑的设计问题。应该尽量避免代码中多个线程对同一数据资源的竞争。

 

False sharing

即使两个线程不操作同一个数据,也可能会导致cache ping-pong的问题,因为cache line的存在。考虑以下结构体:

struct flags {      bool pushed;      bool popped;};

由于pushed和popped内存分布极为接近,它们几乎总会在一个cache line中被加载(cache line可能为32-128字节),如果两个core并行操作,一个修改pushed,一个修改popped,虽然修改的变量不同,一样会导致cache line ping-pong,因为cache line是一个整体(当然支持cache line能修改单字节而不是整体加载的CPU体系结构可能没有这个问题). 这就是False sharing的问题:虽然并行访问不同对象,但却像是访问同一个对象一样引起cache line ping-pong。

简单的解决方法通常也很粗暴——通过添加padding,强制pushed和popped分布在不同的cache line上,例如下面这样定义flags:

struct flags {      bool pushed[N];      char pad[LINE_SIZE];      bool popped[N];   } states;

关于cache的问题,先说这么多,祝大家好运。

--------------------------------------------------------------------------------

文章4:乒乓操作及串并转换设计篇

地址:https://www.mianbaoban.cn/blog/post/20294

**FPGA/CPLD重要设计思想及工程应用 **

乒乓操作及串并转换设计篇

概述

“ 乒乓操作”是一个常常应用于数据流控制的处理技巧,典型的乒乓操作方法如下图所示。

乒乓操作的处理流程

输入数据流通过“输入数据选择单元”将数据流等时分配到两个数据缓冲区,数据缓冲模块可以为任何存储模块,比较常用的存储单元为双口RAM(DPRAM) 、单口RAM(SPRAM) 、FIFO等。

在第一个缓冲周期,将输入的数据流缓存到“数据缓冲模块1”。在第2 个缓冲周期,通过“输入数据选择单元”的切换,将输入的数据流缓存到“数据缓冲模块2”,同时将“数据缓冲模块1”缓存的第1 个周期数据通过“输出数据选择单元”的选择,送到“数据流运算处理模块”进行运算处理。

在第3 个缓冲周期通过“输入数据选择单元”的再次切换,将输入的数据流缓存到“数据缓冲模块1”,同时将“数据缓冲模块2”缓存的第2 个周期的数据通过“输出数据选择单元”切换,送到“数据流运算处理模块”进行运算处理。如此循环。

利用乒乓操作完成数据的无缝缓冲与处理

乒乓操作可以通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。

把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线设计中,完成数据的无缝缓冲与处理。

串并转换

串并转换是FPGA 设计的一个重要技巧,它是高速数据流处理的常用手段,串并转换的实现方法多种多样,根据数据的排序和数量的要求,可以选用寄存器、双口RAM(DPRAM) 、单口RAM(SPRAM) 、FIFO 等实现。

若想数据的缓冲区开得很大,可以通过DPRAM 实现了数据流的串并转换,对于数量比较小的设计可以采用寄存器完成串并转换。如无特殊需求,系统中应该用同步时序设计完成串并之间的转换。

那么在工程应用中,程序里怎么才能体现出串并转换设计的思想呢?怎么才能提高系统的处理速度呢?我们可以先来做一个串并转换的框架型设计。

这个章节老师没有给留下什么固定题目,所以自己构思起来也有点麻烦。想来想去就做个串入并出的设计吧。

设计的思想是这样的,有一组数据以50MHZ的速率从FPGA的一个I/O口传入,要实现在FPGA的另一端8个I/O口以50/8MHZ的速率把传入的速率吐出。也就是说每隔8个主时钟周期要从8个输出口输出从输入口输入的8个数据。

功能仿真的波形如下:

如图,从rst完成复位(拉低)并且输入使能信号en置位(拉高)后输入的数据(头8个时钟周期)为10101010,在第9个时钟周期,输出使能信号en_out拉高了,说明此时可以从8位并行数据输出口取数了,data_out的输出16进制aa正好就是输入的10101010,所以第一个数据的串并转换正确无误。往后输入11110000,输出是16进制f0也没错……

该程序实现了串并转换的要求,这样原来50MH速率传送的数据经过FPGA串并转换后只要用1/8的时钟频率就能完成数据流的传输,也可以说这是一个面积换速度的典型。

程序:

module sp_top(scl,rst,en,sda,data_out,en_out);

input scl;//50MHz主时钟信号

input rst;//复位信号,高有效

input en;//数据输入使能,高有效

input sda;//串行输入数据

output[7:0] data_out;//并行输出数据

output en_out;//输出使能信号,高有效

wire[7:0] data_reg;//并行输出数据寄存器

wire  rdy;

series_in series_in(scl,rst,en,sda,data_reg,rdy);

parallel_out parallel_out(scl,rst,data_reg,rdy,data_out,en_out);

endmodule

module series_in(scl,rst,en,sda,data_reg,rdy);

input scl;

input rst;

input en;

input sda;

output[7:0] data_reg;

output rdy;

reg[7:0] data_reg;

reg[2:0] i;

reg rdy;

always @ ( posedge scl )

begin

if(rst) begin i <= 3'd0; data_reg <= 8'dz; rdy <= 0; end

else if(en)

begin

data_reg <= {data_reg[6:0],sda};

i <= i+1;

if(i==3'd7) rdy <= 1;

else rdy <= 0;

end

else begin i <= 3'd0; data_reg <= 8'dz; rdy <= 0; end

end

endmodule

module parallel_out(scl,rst,data_reg,rdy,data_out,en_out);

input scl;

input rst;

input[7:0] data_reg;

input rdy;

output[7:0] data_out;

output en_out;

reg[7:0]   data_out;

reg en_out;

always @ ( posedge scl )

begin

if(rst) begin data_out <= 8'dz; en_out <= 0; end

else if(rdy) begin data_out <= data_reg; en_out <= 1; end

else begin data_out <= 8'dz; en_out <=0; end

end

endmodule



1 0