Zynq开发实践(FPGA之流水线和冻结)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
谈到fpga相比较cpu的优势,很多时候我们都会谈到数据并发、边接收边处理、流水线这三个方面。所以,第三个优势,也就是流水线的概念,对大家来说其实并不陌生。而且大家如果学的是计算机科学与技术专业,那么肯定会学过计算机组成原理或者是计算机体系结构这些课程,里面谈到cpu设计的时候,也会涉及到流水线的概念。
1、流水线基础
cpu的流水线其实是比较直观的。如果是单周期cpu,那么就相当于一个状态机了,任何时候就只有一个指令在处理。但是如果cpu是多周期,那么意味着任何一个周期内可能有几条指令在处理。之所以会这样,是因为采用了流水线的概念。假设cpu的处理分成了三个阶段,分别是取值、译码和执行。那么最理想的情况,就是同一时刻有三条指令在处理,一条在取指阶段,一条在译码阶段,一条在执行阶段。
2、流水线的注意事项
如前面所说,流水线分的越细,就意味着单位时间内的吞吐量就越多。还是以cpu为例,如果cpu只是单周期处理,那么一个周期内只能有一条指令在处理,而流水线的思路可以让我们同时处理多条指令。因此,在设计流水线的时候,一个很重要的原则,就是不同阶段之间的时间尽可能等同。这样才不会出现木桶效应。
所谓木桶效应,就是如果一个阶段的处理时间过长,那么其他阶段都要逼迫等待这个阶段完成。鉴于这个原因,我们也看到cpu的流水线越来越长,从三级流水线、五级流水线,到七级流水线、九级流水线、十一级流水线。整体上流水线的长度越长,效率会越来越高,不过复杂度也是越来越复杂。
3、流水线冻结
流水线冻结是经常发生的现象。还是以cpu为例,假设cpu某一个阶段的处理突然需要花很多花很多的时间,例如除法、浮点、访存等等,这个时候就会产生流水线冻结。冻结的时候,除了这个阶段的指令还在处理,其他阶段的指令处理都被冻结了,这就是流水线冻结。等到那个阶段的所有工作都处理好了,流水线重新开始流动。
4、举例说明
为了说明流水线处理的基本原理,我们首先写一个简单的数据处理过程,流程很简单,
always@(posedge clk or negedge rst)if(!rst) begindata1 <= 4'h0;data2 <= 4'h0;data3 <= 4'h0;endelse if(!freeze) begindata1 <= in_data;data2 <= data1;data3 <= data2;end
首先,data1、data2、data3初始化为0。只要流水线没有freeze,整个数据传输的过程就会这样一直走下去。那么流水线什么时候freeze呢?
always@(posedge clk or negedge rst)if(!rst)hang <= 1'b0;else if(hang)hang <= 1'b0; // freeze one clockelse if(data1[0] && !hang)hang <= 1'b1; // only freeze in first clockassign freeze = data1[0] & !hang;
这里freeze是一个wire信号,它取决于两点,第一点当前data1是不是奇数,第二点是确认hang是不是0,即当前是不是已经处于hang状态。hang的时间可以很长,比如是2个clock、3个clock,或者是等待某个事件发生等等。
这里需要注意下hang要比freeze晚处理,并且hang恢复为0的操作要放在前面,这样处理的优先级才比较高。当然这里的freeze不仅仅要在这里处理,还得把它送给数据输入端,这样才能从源头控制好输入,防止数据丢失。
always@(posedge clk or negedge rst)if(!rst)num <= 4'h0;else if(!freeze) beginif(num == 4'h9)num <= 4'h0;elsenum <= num + 1;end
完成了freeze生成、hang的生成和恢复、freeze发送给数据输入端这几个动作,整个处理就算是基本告一段落。
5、验证和测试
开发的时候,如果verilog代码过多,可以切分到不同的module里面,这样也比较简单一点,
module pipeline_top(input clk,input rst,output led);wire[3:0] num;
wire freeze;trigger trigger(.clk(clk),.rst(rst),.freeze(freeze),.num(num));pipeline pipeline(.clk(clk),.rst(rst),.in_data(num),.freeze(freeze),.led(led));endmodule
有了top文件之后,接下来就是写好testbench文件,准备开始生成波形,
`timescale 1ns/1ps
module test();reg rst;
reg clk;
wire out;initialbeginrst = 1;clk = 0;#12 rst = 0;#21 rst = 1;#1000 $finish;endinitial
beginwhile(1)clk = #5 !clk;
endpipeline_top pipeline_top(.rst(rst),.clk(clk),.led(out));initial
begin$dumpfile("hello.vcd");$dumpvars(0, test);
endendmodule
不出意外的话,就可以看到对应的波形。验证的时候,没有必要导入所有的信号。一方面信号比较多,很多信号是重复的,比如clock、rst,还有wire信号;另外一方面信号多了,都放在一起也不容易分析。所以选取一个module为主,其他需要的信号拖过来即可。稍微调整下顺序,就可以一个、一个去分析了,