乒乓RAM
今天给大侠带来基于FPGA的乒乓ram设计,话不多说,上货。
一、概述
在现在的数据采集分析系统中,随着采集数据的速度剧增,每次都对这些庞大的数据量直接进行分析,这将会占用很多的CPU,使得CPU不能及时的去做其它的事情。我们可以在传输这些数据的时候提供适当的通道,建立一个缓冲电路,来实现数据流的无缝缓存和处理,提高系统的处理速度和性能。
输入输出缓存电路一般有三种结构和形式:
1、双口ram结构:双口RAM是在一个SRAM存储器上具有两套完全独立的数据线、地址线、读写控制线、并允许两个独立的系统同时对该存储器进行随机访问。
2、FIFO结构:FIFO(First In First Out)是一种先进先出的数据缓存器,可以进行双端操作,但是数据必须先进先出,不能进行随机性的访问。从容量大小来看,双口RAM比FIFO要大一些,但总的来说,这两种缓冲结构的存储容量还是相对较小,对高速图像处理系统而言,还不是特别适合。
3、乒乓ram结构:这种结构是将输入数据流通过输入数据选择单元等时地将数据流分配到两个ram缓冲区。通过两个ram读和写的切换,来实现数据的流水式传输。
综上所述,乒乓缓存结构实际上相当于一个双口RAM,但它与普通的双口RAM又有所不同。普通双口RAM是单个存储体构成的IC,乒乓ram结构则由包含两个相互独立存储体的多片IC构成,从而使其在结构、速度、容量等方面具有更大的灵活性;若双口在访问同一地址时,普通双口SAM指向的必定是存储体内的同一存储单元,而乒乓ram结构则分别指向属于SRAM1和SRAM2的两个不同的存储单元,更易操作。乒乓缓存结构的上述特点决定了可以相对较便宜的高速大容量SRAM、外围逻辑器件构成比双口RAM以及高速FIFO更适合视频处理的系统所需要的缓冲存储器。
乒乓ram结构的上述特点决定了可以相对较便宜的高速大容量RAM、外围逻辑器件构成比双口RAM以及高速FIFO更适合大数据传输系统所需要的缓冲存储器。
二、乒乓ram控制原理
"乒乓操作"是一个常用的数据流控制处理技巧。典型的乒乓操作结构如图1所示。
图1 乒乓操作结果示意图
乒乓操作的处理流程为:
输入数据流通过输入数据选择单元将数据流等时分配到两个数据缓冲区,数据缓冲模块一般为ram。在第一个缓冲周期,将输入的数据流缓存到数据缓冲模块ram A;在第2个缓冲周期,通过输入数据选择单元的切换,将输入的数据流缓存到数据缓冲模块ram B,同时将ram A缓存的第1个周期数据传给输出数据选择单元。在第3个缓冲周期通过输入数据选择单元的再次切换,将输入的数据流缓存到ram A同时将ram B缓存的第2个周期的数据传给输出数据选择单元。如此循环。
乒乓操作的最大特点是通过“输入数据选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿,因此非常适合对数据流进行流水线式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。
三、乒乓ram控制的FPGA设计
当保存数据到ram时,在输入数据时同时要产生相应的地址,这样ram才会把数据与地址一一对应,按顺序存取。FPGA读写控制模块需要给出两组地址线,两组输入、输出数据总线以及读、写片选等控制ram的控制信号线,分别单独的控制ram A和ram B,以便输入的信号总线交替输出。
为了解决共用总线时的资源冲突问题,还需适当的控制两片ram的通断。可以用一个信号控制ram A和ram B的切换。实际上,ram的数据线也有两组,其工作方式和地址线一样。为了给数据处理模块以充裕的时间读取ram中的数据,还要对每次存入ram中的数据做一定量的处理,选择出有用的信号。
如下图,图2为乒乓ram的模块示意图。先在时钟控制下输入两路信号,经过粗略处理,产生两路数据线和地址线,以及两个ram模块的控制线,分别控制两个ram的读和写,并且两个ram的读(或者写)互锁,即一个若处于读状态,则另一个处于写状态。最后把另个ram保存的数据经过一个二选一模块输出,分时复用,产生在时间上连续的数据流输出。
图2 乒乓ram模块示意图
四、乒乓ram的设计流程
乒乓ram的输入数据流宽度是16位,ram深度是1024位。其主要外围接口包括输入、控制和输出几个部分,采用verilog DHL进行编写。
输入部分包括两路输入数据in0[15:0],in1[15:0],时钟信号clk,复位信号rst;控制部分包括两个ram的读写切换信号pingpang,控制两个ram的写信号,还有数据的地址信号in0_addr_wr[9:0],in1_addr_wr[9:0];输出部分包括ram模块的输出out0[15:0],out1[15:0],以及两个选择后的最终输出out[15:0]。其大概流程图如下:
图3 乒乓ram设计流程图
其中,ram是调用IP核产生的,这可以直接选择FPGA自带的ram器件,从而极大的减少了寄存器组的使用,提高了FPGA的利用率。
五、设计代码
参考代码如下:
module lcd12864(
clock, //系统时钟输入
reset_n, //系统复位输入
//in_data, //输入的要显示的数据
LCD_RS, //LCD的寄存器选择输出信号
LCD_RW, //LCD的读、写操作选择输出信号
LCD_E, //LCD使能信号
lcd_data, //LCD的数据总线(不进行读操作,故为输出)
);
input clock;
input reset_n;
//input in_data;
output reg LCD_RS; //LCD的寄存器选择输出信号
output LCD_RW; //LCD的读、写操作选择输出信号
output LCD_E; //LCD使能信号
output reg [7:0]lcd_data; //LCD的数据总线(不进行读操作,故为输出)
reg [31:0] in_data = 32'd2294967296;
// wire [31:0] in_data;
reg clock_lcd; //LCD时钟信号
reg [15:0]cnt; // CLK频率为50MHz, 产生LCD时钟信号, 10Hz
always @(posedge clock or negedge reset_n)
begin
if (!reset_n)
begin
cnt <= 16'b0;clock_lcd <= 0;
end
else if(cnt == 49999)
begin
cnt <= 0;clock_lcd <= ~clock_lcd;
end
else cnt <= cnt +1'b1;
end
/*******************************************************************/
reg[8:0] state; //State Machine code
parameterIDLE = 9'b00000000; //初始状态,下一个状态为CLEAR
parameterSETFUNCTION = 9'b00000001; //功能设置:8位数据接口
parameterSETFUNCTION2 = 9'b00000010;
parameterSWITCHMODE = 9'b00000100; //显示开关控制:开显示,光标和闪烁关闭
parameterCLEAR = 9'b00001000; //清屏
parameterSETMODE = 9'b00010000; //输入方式设置:数据读写操作后,地址自动加一/画面不动
parameterSETDDRAM = 9'b00100000; //设置DDRAM的地址:第一行起始为0x80/第二行为0x90
parameterWRITERAM = 9'b01000000; //数据写入DDRAM相应的地址
parameterSTOP = 9'b10000000; //LCD操作完毕,释放其控制
wire[7:0] disp_1,disp_2,disp_3,disp_4,disp_5,disp_6,disp_7,disp_8,disp_9,disp_10;
//+8'h30为转换为ASCII
assign disp_1 =in_data/32'd1000000000+8'h30;
assign disp_2 =in_data%32'd1000000000/32'd100000000+8'h30; //1011 1110 1011 1100 0010 0000 000
assign disp_3 =in_data%32'd100000000/32'd10000000+8'h30; //1001 1000 1001 0110 1000 0000
assign disp_4 =in_data%32'd10000000/32'd1000000+8'h30; //1111 0100 0010 0100 0000
assign disp_5 =in_data%32'd1000000/32'd100000+8'h30; //1100 0011 0101 0000 0
assign disp_6 =in_data%32'd100000/32'd10000+8'h30; //1001 1100 0100 00
assign disp_7 =in_data%32'd10000/32'd1000+8'h30;
assign disp_8 =in_data%32'd1000/32'd100+8'h30;
assign disp_9 =in_data%32'd100/32'd10+8'h30;
assign disp_10 =in_data%32'd10+8'h30;
reg flag; //标志位,LCD操作完毕为0
reg [5:0] char_cnt;
reg [7:0] data_disp;
assign LCD_RW = 1'b0; //没有读操作,R/W信号始终为低电平
assign LCD_E = (flag == 1)?clock_lcd:1'b0; //E信号出现高电平以及下降沿的时刻与LCD时钟相同
always @(posedge clock_lcd or negedge reset_n) //只有在写数据操作时,RS信号才为高电平,其余为低电平
begin
if(!reset_n)
LCD_RS <= 1'b0;
else if(state == WRITERAM) //写数据
LCD_RS <= 1'b1;
else
LCD_RS <= 1'b0; //写指令
end
/*********************************************************
// State Machine
*********************************************************/
always @(posedge clock_lcd or negedge reset_n)
begin
if(!reset_n)
begin
state <= IDLE;
lcd_data <= 8'bzzzzzzzz;
char_cnt <= 5'b0;
flag <= 1'b1;
end
else
begin
case(state)
IDLE:
begin
state <= SETFUNCTION;
lcd_data <= 8'bzzzzzzzz;
end
SETFUNCTION:
begin
state <= SETFUNCTION2;
lcd_data <= 8'h30; // 8-bit 控制界面,基本指令集动作
end
SETFUNCTION2:
begin
state <= SWITCHMODE;
lcd_data <= 8'h30; // 清屏
end
SWITCHMODE:
begin
state <= CLEAR;
lcd_data <= 8'h0c; // 显示开关:开显示,光标和闪烁关闭
end
CLEAR:
begin
state <= SETMODE;
lcd_data <= 8'h01;
end
SETMODE:
begin
state <= SETDDRAM;
lcd_data <= 8'h06; // 输入方式设置: 数据读写后,地址自动加1,画面不动
end
SETDDRAM:
begin
state <= WRITERAM;
if(char_cnt == 0) //如果显示的是第一个字符,则设置第一行的首字符地址
begin
lcd_data <= 8'h80; //Line1
end
else if(char_cnt ==16)//第二次设置时,是设置第二行的首字符地址
begin
lcd_data <= 8'h90;
end
else if(char_cnt == 32)
begin
lcd_data <= 8'h88;
end
else if(char_cnt == 48)
begin
lcd_data <= 8'h98;
end
end
WRITERAM:
begin
if(char_cnt <= 15)
begin
char_cnt <= char_cnt + 1'b1;
lcd_data <= data_disp;
if( char_cnt == 15 )
state <= SETDDRAM;
else
state <= WRITERAM;
end
else if( char_cnt >= 16 && char_cnt <= 31)
begin
lcd_data <= data_disp;
state <= WRITERAM;
char_cnt <= char_cnt + 1'b1;
if( char_cnt == 31 ) //返回SETDDRAM修改写地址
state <= SETDDRAM;
else
state <= WRITERAM; //
end
else if(char_cnt >= 32 && char_cnt <= 47)
begin
lcd_data <= data_disp;
state <= WRITERAM;
char_cnt <= char_cnt + 1'b1;
if( char_cnt == 47 )
state <= SETDDRAM;
else
state <= WRITERAM;
end
else if(char_cnt >= 48 && char_cnt <= 63)
begin
lcd_data <= data_disp;
state <= WRITERAM;
char_cnt <= char_cnt + 1'b1;
if( char_cnt == 63 )
state <= SETDDRAM;
else
state <= WRITERAM;
end
end
STOP: state <= STOP;
default: state <= IDLE;
endcase
end
end
always @(char_cnt) //输出的字符
begin
case (char_cnt)
6'd0: data_disp <= 8'h20;
6'd1: data_disp <= 8'h20; //
6'd2: data_disp <= 8'hB5;
6'd3: data_disp <= 8'hE7; //
6'd4: data_disp <= 8'hD7;
6'd5: data_disp <= 8'hD3; //
6'd6: data_disp <= 8'hB9;
6'd7: data_disp <= 8'hA4; //
6'd8: data_disp <= 8'hB3;
6'd9: data_disp <= 8'hCC;
6'd10: data_disp <= 8'hCA;
6'd11: data_disp <= 8'hC0;
6'd12: data_disp <= 8'hBD;
6'd13: data_disp <= 8'hE7;
6'd14: data_disp <= 8'h20;
6'd15: data_disp <= 8'h20;
6'd16: data_disp <= 1;
6'd17: data_disp <= 1;
6'd18: data_disp <= 1;
6'd19: data_disp <= 1;
6'd20: data_disp <= "E";
6'd21: data_disp <= "E";
6'd22: data_disp <= "W";
6'd23: data_disp <= "O";
6'd24: data_disp <= "R";
6'd25: data_disp <= "L";
6'd26: data_disp <= "D";
6'd27: data_disp <= 1;
6'd28: data_disp <= 1;
6'd29: data_disp <= 1;
6'd30: data_disp <= 1;
6'd31: data_disp <= 1;
6'd32: data_disp <= "F";
6'd33: data_disp <= "P";
6'd34: data_disp <= "G";
6'd35: data_disp <= "A";
6'd36: data_disp <= 8'hD6;
6'd37: data_disp <= 8'hFA;
6'd38: data_disp <= 8'hD1;
6'd39: data_disp <= 8'hA7;
6'd40: data_disp <= 8'hCF;
6'd41: data_disp <= 8'hB5;
6'd42: data_disp <= 8'hC1;
6'd43: data_disp <= 8'hD0;
6'd44: data_disp <= 8'h20;
6'd45: data_disp <= 8'h20;
6'd46: data_disp <= 8'h20;
6'd47: data_disp <= 8'h20;
6'd48: data_disp <= disp_1;
6'd49: data_disp <= disp_2; //
6'd50: data_disp <= disp_3;
6'd51: data_disp <= disp_4;
6'd52: data_disp <= disp_5;
6'd53: data_disp <= disp_6;
6'd54: data_disp <= disp_7;
6'd55: data_disp <= disp_8;
6'd56: data_disp <= disp_9;
6'd57: data_disp <= disp_10;
6'd58: data_disp <= 8'h20;
6'd59: data_disp <= 8'h20;
6'd60: data_disp <= 8'h20;
6'd61: data_disp <= 8'h20;
6'd62: data_disp <= 8'h20;
6'd63: data_disp <= 8'h20;
//default : data_disp <= 8'd20;
endcase
end
endmodule
六、仿真测试
仿真测试波形图
调用IP核产生的ram要存满数据后才会逐步读出数据,即在下一个地址周期才会读出该ram中保存的数据。开始ram中无数据,此地址周期内输出0;第二个地址周期读出ram1,也是0;第三个地址周期读出ram0,第四个周期读出ram1,可以看出in0和in1的数据逐渐输出,并且在乒乓信号切换时数据流也相应切换,衔接得刚好。说明了乒乓ram实现大量高速数据流的无缝缓冲和处理。
后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!
精彩推荐