反压信号的产生与流水线的设计息息相关,由于每一级流水线需要进行握手,流水线最后一级的反压信号可能会一直串扰到最前一级造成严重的时序问题,需要使用一些比较高级的技巧来解决此类反压时序问题。关于流水线参见 芯片设计之流水线设计-IC学习笔记(四)。
1.1 反压信号处理解决方法
- 取消握手:杜绝反压的发生,时序表现非常好,但流水线中的每一级并不会与其下一级进行握手,可能会造成功能错误或者指令丢失。因此这种方法往往需要配合其他的机制,譬如重执行、预留大缓存等。
- 加入乒乓缓存:是一种用面积换时序的方法,也是在解决反压的最简单方法。通过使用乒乓缓存(有两个表项)替换掉普通的一级流水线(只有一个表项),可以使得此级流水线向上一级流水线的握手接收信号仅关注乒乓缓存中是否有一个以上有空的表项即可,而无需将下一级的握手接受信号串扰至上一级。
- 加入向前旁路缓存:也是一种用面积换时序的方法。旁路缓存仅只有一个表项,由于增加了这一个额外的缓存表项,可以将后向的握手信号时序路径砍断,但是对前向路径不受影响,因此可以广泛使用于握手接口。
1.2 乒乓缓存
外部输入数据流通过“输入数据选择控制”模块送入两个数据缓冲区中,数据缓冲模块可以为任何存储模块,比较常用的存储单元为双口RAM(Dual RAM)、SRAM、SDRAM、FIFO等。在第1个数据缓冲周期,将输入的数据流缓存到“数据缓冲1”模块。在第2个缓冲周期,“输入数据选择控制”模块将输入的数据流缓存到“数据缓冲2”模块中的同时,“输出数据选择控制”模块将“数据缓冲1”模块第1个周期缓存的数据流送到“后续处理”模块进行后续的数据处理。在第3个缓冲周期,在“输入数据选择控制”模块的再次切换后,输入的数据流缓存到“缓冲数据1”模块,与此同时,“输出数据选择控制”模块也做切换,将“数据缓冲2”模块缓存的第2个周期的数据送到“后续处理”模块,如此不断循环。
双口RAM乒乓操作示例:参考参考文档三。
问题:输入数据速率20MHz,输出数据速率100Mhz,使用双口RAM完成跨时钟域处理。一次传输的数据为1024个,假设数据位宽为8bit,使用两片宽度为8、深度为1024的双口RAM完成数据传输。
解决方法:使用乒乓操作提高读写效率,写RAM1时,读取RAM2中的数据;写RAM2时,读取RAM1中的数据。数据读取速率为数据写入速率的5倍,因此写数据端可以一直保持数据写入,而读数据端按写入一组数据时间的1/5进行,使用out_valid信号表示读出的数据有效。省略了读数据期间不能写的时间。
`timescale 1ns / 1ps
module DualRAM
(
input clk_wr, //写时钟速率20Mhz
input clk_rd, //读时钟速率100Mhz
input rst_n,
input [7:0] din,
output reg out_valid,
output reg [7:0] dout
);
reg [9:0] addr_wr, addr_rd;
reg en_wr1, en_wr2, we_wr1, we_wr2, en_rd1, en_rd2;
wire [7:0] dout1, dout2;
dual_port_ram u1 (
.clka(clk_wr), //写端口
.ena(en_wr1),
.wea(we_wr1),
.addra(addr_wr),
.dina(din),
.douta(),
.clkb(clk_rd), //读端口
.enb(en_rd1),
.web(1'b0),
.addrb(addr_rd),
.dinb(8'd0),
.doutb(dout1)
);
dual_port_ram u2 (
.clka(clk_wr), //写端口
.ena(en_wr2),
.wea(we_wr2),
.addra(addr_wr),
.dina(din),
.douta(),
.clkb(clk_rd), //读端口
.enb(en_rd2),
.web(1'b0),
.addrb(addr_rd),
.dinb(8'd0),
.doutb(dout2)
);
//写端口乒乓操作
always @ (posedge clk_wr) //写地址信号控制0~1023
if (!rst_n) addr_wr <= 1023;
else addr_wr <= addr_wr + 1'b1;
//主要通过切换en_wr1和en_wr2切换写不同RAM
always @ (posedge clk_wr) //轮流写RAM1与RAM2
if (!rst_n) begin we_wr1 <= 1'b1; we_wr2 <= 1'b0;
en_wr1 <= 1'b1; en_wr2 <= 1'b0; end
else if (addr_wr == 1023) begin //先写RAM2
we_wr1 <= ~we_wr1; we_wr2 <= ~we_wr2;
en_wr1 <= ~en_wr1; en_wr2 <= ~en_wr2;
end
//读端口乒乓操作
always @ (posedge clk_rd) //读地址信号控制0~1023
if (!rst_n) addr_rd <= 1021; //匹配延迟
else addr_rd <= addr_rd + 1'b1;
reg [15:0] cnt;
always @ (posedge clk_rd) //读时钟为写时钟的5倍
if (!rst_n) cnt <= 16'hFFFE; //匹配延迟
else if (cnt == 5119) cnt <= 0;
else cnt <= cnt + 1'b1;
reg flag1, flag2;
always @ (posedge clk_rd) //读RAM标志,RAM1或RAM2
if (!rst_n) begin flag1 <= 1'b1; flag2 <= 1'b0; end
else if (cnt == 5119) begin flag1 = ~flag1; flag2 = ~flag2; end
else begin flag1 <= flag1; flag2 <= flag2; end
always @ (posedge clk_rd) //读RAM使能,选择cnt的前1/5时间读取
if (!rst_n) begin en_rd1 <= 1'b1; en_rd2 <= 1'b0; end
else if (cnt < 1024) begin en_rd1 <= flag1; en_rd2 <= flag2; end
else begin en_rd1 <= 1'b0; en_rd2 <= 1'b0; end
reg en_rd1_reg, en_rd2_reg;
always @ (posedge clk_rd) //延迟一级,匹配时序
if (!rst_n) begin en_rd1_reg <= 0; en_rd2_reg <= 0; end
else begin en_rd1_reg <= en_rd1; en_rd2_reg <= en_rd2; end
always @ (posedge clk_rd) //输出选择,RAM1或RAM2;控制输出使能信号
if (!rst_n) begin dout <= 0; out_valid <= 0; end
else if (en_rd1_reg) begin dout <= dout1; out_valid <= 1; end
else if (en_rd2_reg) begin dout <= dout2; out_valid <= 1; end
else begin dout <= 0; out_valid <= 0; end
endmodule
1.3 蜂鸟E200处理器旁路缓存
旁路缓存涉及到数据冲突,三种数据冲突为WAW,WAR,RAW,RAW:是真的数据相关,必须在前一个数据计算后才能进行使用。就可以采用数据旁路传播(Data Bypass and Forward )技术,尽可能让前序指令的计算结果更快地旁路传播给后序相关指令的操作数。
参考文档
【1】处理器流水线介绍
【2】FPGA——乒乓操作
【4】蜂鸟E200处理器笔记