乒乓设计的思想 乒乓操作一般用来处理从快速的数据流到慢速的数据流之间的转换,也经常被用来处理跨时钟域的问题,采用串转并的思想,是一种异样的流水线技术的思想。如下图所示:
文章图片
现在面临的问题是输入数据要从快的时钟域到慢的时钟域,如何对数据进行有效的传输并处理。这时便采用乒乓操作。现在假设时钟域1的时钟频率是50MHz,时钟2的频率是25MHz。假设在一个缓冲周期(其实就是BUFFER的存储容量)内要传输16位8比特的数据。在第一周缓冲周期内对BUFFER1写数据。在第二个缓冲周期内,对BUFFER2写数据,同时对BUFFER1采用时钟2的频率对BUFFER进行读数据,但是此时你会发现,在第二个时钟周期读完之后,此时对于BUFFER1的数据的读取只完成了一半(如果只是按照原有的数据宽度读取),换句话说在在第三个缓冲周期写BUFFER1时,发现此时BUFFER1的数据还没有读完。解决这个问题的方法就是我们说的串转并,就是我们每次不是读取8位的数据,而是每次读取16位的数据,这样我们就可以在第二个缓冲周期内把BUFFER1的数据读取完毕,以便在第三个缓冲周期写BUFFER1同时读取BUFFER2的数据,周而复始形成了流水线。
RAM IP 声明的RAM的IP是一个双端口的ram,输入数据宽度是8位宽,深度是16。输出数据的宽度是16位宽,深度是8.如下图所示:
文章图片
文章图片
锁相环IP 【verilog实战|基于RAM实现乒乓buffer】其作用是输入50MHz的时钟,输出25MHz的时钟。
文章图片
文章图片
乒乓BUFFER的设计思想
写BUFFER 采用了状态机作为控制逻辑,决定写BUFFER1还是BUFFER2.
IDLE | 空闲状态 |
---|---|
WR_BUF1 | 写BUFFER1 |
WR_BUF2 | 写BUFFER2 |
END_WR | 结束写状态 |
always @(*) begin
case(state)
IDLE:next_state <= start ? WR_BFR1 : IDLE;
WR_BFR1:next_state <= wr_buf1_end ? WR_BFR2 : WR_BFR1;
WR_BFR2:next_state <= wr_buf2_end ? END_WR : WR_BFR2;
END_WR: next_state <= IDLE;
endcase
end
读BUFFER 采用了状态机作为控制逻辑,决定读BUFFER1还是BUFFER2.
IDLE | 空闲状态 |
---|---|
RD_BUF1 | 读BUFFER1 |
RD_BUF2 | 读BUFFER2 |
END_RD | 结束读状态 |
跳转逻辑如下: |
always @(*) begin
case(read_state)
IDLE:rd_nxt_state <= wr_buf1_end ? RD_BFR1 : IDLE;
RD_BFR1:rd_nxt_state <= rd_buf1_end ? RD_BUBBLE : RD_BFR1;
RD_BUBBLE:rd_nxt_state <= RD_BFR2;
RD_BFR2:rd_nxt_state <= rd_buf2_end ? END_RD : RD_BFR2;
END_RD: rd_nxt_state <= IDLE;
endcase
end
实现的全部代码(注释详细)
//date:2022/9/7
//function:实现一个ram的pingpongbuffer
//输入数据流的时钟是50MHz
//输出数据流(处理数据流)的时钟是25MHz
//采用vivado的IP生成的ram
module pingpong_buffer(
inputwireclk,
inputwirerst_n,
inputwire[7:0]data_in,
inputwirestart,outputwire[15:0]data_out,
outputwirelocked
);
parameterIDLE=3'd0,//空闲状态
WR_BFR1 =3'd1,//写BUF1状态
WR_BFR2 =3'd2,//写BUF2状态
END_WR=3'd3,//写BUF结束状态
RD_BFR1 =3'd4,//读BUF1状态
RD_BFR2 =3'd5,//读BUF2状态
END_RD=3'd6,//结束读状态
RD_BUBBLE=3'd7;
//读气泡wireclk_25mhz;
//wirelocked;
wireasso_rst_n;
wirewr_buf1_end ;
wirewr_buf2_end ;
wirerd_buf1_end ;
wirerd_buf2_end ;
wire[15:0]data_o_buf1 ;
wire[15:0]data_o_buf2 ;
regwea;
//ram写使能信号
reg[2:0]state;
reg[2:0]next_state;
reg[3:0]wr_buf1_addr;
//写ram1地址
reg[3:0]wr_buf2_addr;
//写ram2地址
reg[3:0]read_state;
reg[3:0]rd_nxt_state;
reg[2:0]rd_buf1_addr;
//读ram1地址
reg[2:0]rd_buf2_addr;
//读ram1地址
regwr_end;
//写结束
regrd_end;
//读结束assign asso_rst_n = rst_n && locked;
//生成一个新的复位信号always @(posedge clk or negedge asso_rst_n) begin
if(!asso_rst_n) begin
state <= IDLE;
end
else begin
state <= next_state;
end
endalways @(posedge clk_25mhz or negedge asso_rst_n) begin
if(!asso_rst_n) begin
read_state <= IDLE;
end
else begin
read_state <= rd_nxt_state;
end
endalways @(posedge clk or negedge asso_rst_n) begin
if(~asso_rst_n) begin
wea <= 1'b0;
end
else if (next_state == WR_BFR1) begin
wea <= 1'b1;
end
else if (next_state == WR_BFR2) begin
wea <= 1'b0;
end
else begin
wea <= 1'b0;
end
end
always @(posedge clk or negedge asso_rst_n) begin
if(!asso_rst_n) begin
wr_buf1_addr <= 4'd0;
wr_buf2_addr <= 4'd0;
wr_end <= 1'b0;
end
else begin
case(state)
IDLE:begin
wr_buf1_addr <= 4'd0;
wr_buf2_addr <= 4'd0;
wr_end <= 1'b0;
end
WR_BFR1: begin
wr_buf1_addr <= (wr_buf1_addr == 4'd15) ? 4'd0 : wr_buf1_addr + 1'b1;
wr_buf2_addr <= 4'd0;
end
WR_BFR2: begin
wr_buf1_addr <= 4'd0;
wr_buf2_addr <= (wr_buf2_addr == 4'd15) ? 4'd0 : wr_buf2_addr + 1'b1;
end
END_WR: begin
wr_buf1_addr <= 4'd0;
wr_buf2_addr <= 4'd0;
wr_end <= 1'b1;
end
default: begin
wr_buf1_addr <= 4'd0;
wr_buf2_addr <= 4'd0;
wr_end <= 1'b0;
end
endcase
end
end
always @(*) begin
case(state)
IDLE:next_state <= start ? WR_BFR1 : IDLE;
WR_BFR1:next_state <= wr_buf1_end ? WR_BFR2 : WR_BFR1;
WR_BFR2:next_state <= wr_buf2_end ? END_WR : WR_BFR2;
END_WR: next_state <= IDLE;
endcase
endassign wr_buf1_end = (wr_buf1_addr == 4'd15) ? 1'b1 : 1'b0;
assign wr_buf2_end = (wr_buf2_addr == 4'd15) ? 1'b1 : 1'b0;
assign rd_buf1_end = (rd_buf1_addr == 3'd7) ? 1'b1 : 1'b0;
assign rd_buf2_end = (rd_buf2_addr == 3'd7) ? 1'b1 : 1'b0;
always @(posedge clk_25mhz or negedge asso_rst_n) begin
if(!asso_rst_n) begin
rd_buf1_addr <= 3'd0;
rd_buf2_addr <= 3'd0;
rd_end <= 1'b0;
end
else begin
case(read_state)
IDLE:begin
rd_buf1_addr <= 3'd0;
rd_buf2_addr <= 3'd0;
rd_end <= 1'b0;
end
RD_BFR1: begin
rd_buf1_addr <= (rd_buf1_addr == 3'd7) ? 3'd0 : rd_buf1_addr + 1'b1;
rd_buf2_addr <= 3'd0;
end
RD_BFR2:begin
rd_buf1_addr <= 3'd0;
rd_buf2_addr <= (rd_buf2_addr == 3'd7) ? 3'd0 : rd_buf2_addr + 1'b1;
end
END_RD:begin
rd_buf1_addr <= 3'd0;
rd_buf2_addr <= 3'd0;
rd_end <= 1'b1;
end
default: begin
rd_buf1_addr <= 3'd0;
rd_buf2_addr <= 3'd0;
rd_end <= 1'b0;
end
endcase
end
endalways @(*) begin
case(read_state)
IDLE:rd_nxt_state <= wr_buf1_end ? RD_BFR1 : IDLE;
RD_BFR1:rd_nxt_state <= rd_buf1_end ? RD_BUBBLE : RD_BFR1;
RD_BUBBLE:rd_nxt_state <= RD_BFR2;
RD_BFR2:rd_nxt_state <= rd_buf2_end ? END_RD : RD_BFR2;
END_RD: rd_nxt_state <= IDLE;
endcase
end
//调用时钟锁相环的ip
clk_pll_v1u_clk_pll (
.clk_in1(clk),
.reset(~rst_n),.locked(locked),
.clk_out1(clk_25mhz)
);
//调用ram的ip
blk_mem_gen_0 buffer1(
.clka(clk),
.clkb(clk_25mhz),
.wea(wea),
.addra(wr_buf1_addr),
.addrb(rd_buf1_addr),
.dina(data_in),
.doutb(data_o_buf1)
);
blk_mem_gen_0 buffer2(
.clka(clk),
.clkb(clk_25mhz),
.wea(~wea),
.addra(wr_buf2_addr),
.addrb(rd_buf2_addr),
.dina(data_in),
.doutb(data_o_buf2)
);
assign data_out = ((read_state == RD_BFR1 && rd_buf1_addr >= 1) || read_state == RD_BUBBLE ) ? data_o_buf1 : (read_state == RD_BFR2) ? data_o_buf2 : 16'd0;
endmodule
testbench如下:
`timescale 1ns/1ns
`define CLK_CYCLE 20
module pingpong_tb();
regclk;
regrst_n;
reg[7:0]data_in;
regstart;
wire[15:0]data_out;
wirelocked;
pingpong_buffer u_pingpong_buffer(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.start(start),.data_out(data_out),
.locked(locked)
);
initial begin
clk = 0;
rst_n = 0;
data_in = 8'd0;
start = 1'b0;
#30
rst_n = 1;
#40
@(posedge locked);
start = 1'b1;
#20
start = 1'b0;
repeat(50) begin
data_in = $random() % 256;
@(posedge clk);
end
endalways # (`CLK_CYCLE / 2) clk = ~clk;
endmodule
仿真波形(可见结果正确):
文章图片
总结 知道了乒乓设计的思想,对于状态机的设计更加熟练了。继续加油!
推荐阅读
- verilog实战|双边沿计数器verilog设计(详细说明)
- verilog实战|verilog实现时钟的偶数与奇数分频
- 物联网基础入门,实战可落地的 AIoT 项目含资料
- 嵌入式系统|嵌入式系统之bootloader简介
- 单片机|猿创征文 | 实验一 单片机keil51软件使用及IO控制
- 〖单片机〗原理|猿创征文|『单片机原理』程序存储器的结构
- 【51单片机】系列|猿创征文|『51单片机』串口通信