一、基本概念
????????乒乓操作是FPGA开发中的一种数据缓冲优化设计技术,可以看成是另一种形式的流水线技术,具有节约缓冲空间、对数据流无缝处理等优点,其操作原理如图所示。
? ? ? ? 在输入数据流到达时,输入数据流选择单元对其流向进行控制,其执行流程:
①在第一个缓冲周期,输入数据流写入数据缓冲模块1,写完之后进入第二个缓冲周期。
②在第二个缓冲周期,输入数据流写入数据缓冲模块2,同时将数据缓冲模块1中的数据读出。
③在第三个缓冲周期,输入数据流写入数据缓冲模块1,同时将数据缓冲模块2中的数据读出。
????????如此反复循环地操作,即为乒乓操作。
? ? ? ? 乒乓操作特点:实现跨时钟域的数据传输,其基本原理:
????????输入数据流的 面积 × 速度 = 输出数据流的 面积 × 速度
? ? ? ? 面积:即数据传输线的位宽,bit。
? ? ? ? 速度:即数据传输的时钟频率,hz。
????????例:输入数据流为 50Mhz × 8bit,则输出数据流为 25Mhz × 16bit,这样就实现了跨时钟域的数据传输。
?二、题目要求
? ? ? ? 使用数据产生模块输出 50Mhz × 8bit 的数据 8'b0 ~ 8'b199,通过乒乓操作读取数据,对其缓存并输出为 25Mhz × 16bit 的数据 16'h0100、16'h0302 ... 16'h6362 ... 16'hc7c6、16'h0100...
三、思路整理
? ? ? ? 将其划分为四个模块,如图所示。
? ? ? ? 模块框图如图所示。?
????????执行流程:
①data_gen输出数据 0 ~ 199(50Mhz × 8bit)?。
②在第一个缓冲周期,ram_ctrl 将数据流接入 ram1,ram1存入 0 ~ 99。
③在第二个缓冲周期,ram_ctrl 将数据流接入 ram2,ram2存入 100 ~ 199,同时 ram_ctrl 将ram1中的数据输出,即16'h0100、16'h0302 ... 16'h6362(10'd99 10'd98)。
④在第三个缓冲周期,ram_ctrl 将数据流接入 ram1,ram1存入 0 ~ 99,同时 ram_ctrl 将ram2中的数据输出,即16'h6463、16'h6665 ... 16'hc7c6(10'd199 10'd198)。
? ? ? ? 用状态机来实现这个功能。
四、Verilog实战
?1、data_gen模块
module data_gen
(
input wire clk , //50MHZ
input wire rst ,
output wire data_en ,
output reg [7:0] data
);
always@(posedge clk or negedge rst)
if(!rst)
data <= 8'b0;
else if(data == 'd199)
data <= 8'b0;
else
data <= data + 1'b1;
assign data_en = (rst == 1'b1);
endmodule
?2、ram_ctrl模块
module ram_ctrl
(
input wire clk_25m ,
input wire clk_50m ,
input wire rst ,
input wire [15:0] ram1_data ,
input wire [15:0] ram2_data ,
input wire data_en ,
input wire [7:0] data_in ,
output wire ram1_wr_en ,
output reg [6:0] ram1_wr_addr ,
output wire [7:0] ram1_wr_data ,
output wire ram1_rd_en ,
output reg [5:0] ram1_rd_addr ,
output wire ram2_wr_en ,
output reg [6:0] ram2_wr_addr ,
output wire [7:0] ram2_wr_data ,
output wire ram2_rd_en ,
output reg [5:0] ram2_rd_addr ,
output reg [7:0] data_in_reg ,
output wire [15:0] data_out
);
parameter IDLE = 4'b0001,
WRAM1 = 4'b0010,
R1_W2 = 4'b0100,
W1_R2 = 4'b1000;
reg [3:0] state,next_state;
//data_in_reg:读取数据并打拍
always@(posedge clk_50m or negedge rst)
if(!rst)
data_in_reg <= 8'b0;
else if(data_en == 1'b1)
data_in_reg <= data_in;
//state:现态转移
always@(posedge clk_50m or negedge rst)
if(!rst)
state <= IDLE;
else
state <= next_state;
//next_state:次态改变
always@(*)
case(state)
IDLE : next_state = WRAM1;
WRAM1 : next_state = (data_in_reg == 'd99 )?R1_W2:WRAM1;
R1_W2 : next_state = (data_in_reg == 'd199)?W1_R2:R1_W2;
W1_R2 : next_state = (data_in_reg == 'd99 )?R1_W2:W1_R2;
default : next_state = IDLE;
endcase
//ram1、ram2读写使能
assign ram1_wr_en = (state == WRAM1 || state == W1_R2);
assign ram1_rd_en = (next_state == R1_W2 || state == R1_W2);
assign ram2_wr_en = (state == R1_W2);
assign ram2_rd_en = (next_state == W1_R2 || state == W1_R2);
//ram1_wr_addr,ram2_wr_addr:写地址计数
always@(posedge clk_50m or negedge rst)
if(!rst)
begin
ram1_wr_addr <= 7'b0;
ram2_wr_addr <= 7'b0;
end
else if(ram1_wr_addr == 'd99 || ram2_wr_addr == 'd99)
begin
ram1_wr_addr <= 7'b0;
ram2_wr_addr <= 7'b0;
end
else
case(state)
WRAM1 : ram1_wr_addr <= ram1_wr_addr + 1'b1;
R1_W2 : ram2_wr_addr <= ram2_wr_addr + 1'b1;
W1_R2 : ram1_wr_addr <= ram1_wr_addr + 1'b1;
default :
begin
ram1_wr_addr <= 7'b0;
ram2_wr_addr <= 7'b0;
end
endcase
//ram1_rd_addr,ram2_rd_addr:读地址计数
always@(posedge clk_25m or negedge rst)
if(!rst)
begin
ram1_rd_addr <= 6'b0;
ram2_rd_addr <= 6'b0;
end
else if(ram1_rd_addr == 'd49 || ram2_rd_addr == 'd49)
begin
ram1_rd_addr <= 6'b0;
ram2_rd_addr <= 6'b0;
end
else
case(state)
R1_W2 : ram1_rd_addr <= ram1_rd_addr + 1'b1;
W1_R2 : ram2_rd_addr <= ram2_rd_addr + 1'b1;
default :
begin
ram1_rd_addr <= 6'b0;
ram2_rd_addr <= 6'b0;
end
endcase
//ram1、ram2输入数据选择
assign ram1_wr_data = (state == WRAM1 || state == W1_R2) ? data_in_reg:8'b0;
assign ram2_wr_data = (state == R1_W2) ? data_in_reg:8'b0;
//ram1、ram2输出数据选择
assign data_out = (state == R1_W2) ? ram1_data:((state == W1_R2) ? ram2_data:16'b0);
endmodule
3、pingpang模块
module pingpang
(
input wire clk ,
input wire rst ,
output wire [15:0] data_out
);
wire clk_25m ;
wire clk_50m ;
wire locked ;
wire rst_n ;
wire [15:0] ram1_data ;
wire [15:0] ram2_data ;
wire data_en ;
wire [7:0] data_in ;
wire ram1_wr_en ;
wire [6:0] ram1_wr_addr ;
wire [7:0] ram1_wr_data ;
wire ram1_rd_en ;
wire [5:0] ram1_rd_addr ;
wire ram2_wr_en ;
wire [6:0] ram2_wr_addr ;
wire [7:0] ram2_wr_data ;
wire ram2_rd_en ;
wire [5:0] ram2_rd_addr ;
wire [7:0] data_in_reg ;
assign rst_n = locked & rst;
clk_gen clk_gen_inst
(
.areset (~rst ),
.inclk0 (clk ),
.c0 (clk_50m ),
.c1 (clk_25m ),
.locked (locked )
);
data_gen data_gen_inst
(
.clk (clk_50m ), //50MHZ
.rst (rst_n ),
.data_en (data_en ),
.data (data_in )
);
ram_ctrl ram_ctrl_inst
(
.clk_25m (clk_25m ),
.clk_50m (clk_50m ),
.rst (rst_n ),
.ram1_data (ram1_data ),
.ram2_data (ram2_data ),
.data_en (data_en ),
.data_in (data_in ),
.ram1_wr_en (ram1_wr_en ),
.ram1_wr_addr (ram1_wr_addr ),
.ram1_wr_data (ram1_wr_data ),
.ram1_rd_en (ram1_rd_en ),
.ram1_rd_addr (ram1_rd_addr ),
.ram2_wr_en (ram2_wr_en ),
.ram2_wr_addr (ram2_wr_addr ),
.ram2_wr_data (ram2_wr_data ),
.ram2_rd_en (ram2_rd_en ),
.ram2_rd_addr (ram2_rd_addr ),
.data_in_reg (data_in_reg ),
.data_out (data_out )
);
ram ram1_inst
(
.data (ram1_wr_data ),
.rdaddress (ram1_rd_addr ),
.rdclock (~clk_25m ),
.rden (ram1_rd_en ),
.wraddress (ram1_wr_addr ),
.wrclock (~clk_50m ),
.wren (ram1_wr_en ),
.q (ram1_data )
);
ram ram2_inst
(
.data (ram2_wr_data ),
.rdaddress (ram2_rd_addr ),
.rdclock (~clk_25m ),
.rden (ram2_rd_en ),
.wraddress (ram2_wr_addr ),
.wrclock (~clk_50m ),
.wren (ram2_wr_en ),
.q (ram2_data )
);
endmodule
五、Modelsim仿真
????????可以看到数据流实现了无缝衔接,达到预期的效果,实验成功。
?
参考资料:野火《FPGA Verilog开发实战指南——基于Altera EP4CE10》
|