数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)

这篇具有很好参考价值的文章主要介绍了数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 前言:

        本专栏旨在记录高频笔面试手撕代码题,以备数字前端秋招,本专栏所有文章提供原理分析、代码及波形,所有代码均经过本人验证。

目录如下:

1.数字IC手撕代码-分频器(任意偶数分频)

2.数字IC手撕代码-分频器(任意奇数分频)

3.数字IC手撕代码-分频器(任意小数分频)

4.数字IC手撕代码-异步复位同步释放

5.数字IC手撕代码-边沿检测(上升沿、下降沿、双边沿)

6.数字IC手撕代码-序列检测(状态机写法)

7.数字IC手撕代码-序列检测(移位寄存器写法)

8.数字IC手撕代码-半加器、全加器

9.数字IC手撕代码-串转并、并转串

10.数字IC手撕代码-数据位宽转换器(宽-窄,窄-宽转换)

11.数字IC手撕代码-有限状态机FSM-饮料机

12.数字IC手撕代码-握手信号(READY-VALID)

13.数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)

14.数字IC手撕代码-泰凌微笔试真题

15.数字IC手撕代码-平头哥技术终面手撕真题

16.数字IC手撕代码-兆易创新笔试真题

17.数字IC手撕代码-乐鑫科技笔试真题(4倍频)

18.数字IC手撕代码-双端口RAM(dual-port-RAM)

        ...持续更新

 更多手撕代码题可以前往 数字IC手撕代码--题库文章来源地址https://www.toymoban.com/news/detail-430848.html


目录

流水握手

流水线

总代码

总testbench

波形


流水握手

        之前在这篇博客中讲过流程之间的握手操作。

        数字IC手撕代码-握手信号(READY-VALID)

        今天讲讲流水线技术,以及如何用握手操作解决流水线因上游断流及下游反压导致的可能的数据丢失及计算错误问题。

流水线

        所谓流水线,其实就是通过将一段具有长延时的组合逻辑,利用寄存器,切分成多段具有小延时的组合逻辑,从而达到提升整个模块时钟频率的目的。

        例如我们在两个寄存器之间有一段组合逻辑,实现4个8bit数的乘加计算,代码写为:

        assign result = a1*b1 + a2*b2 + a3*b3 + a4*b4;

        这条语句的组合逻辑延时很大,有几个原因,一个是四个乘法在一个组合逻辑实现,再考虑四个数的加法,这样组合逻辑的延时就会很大,可以利用流水线技术对这段组合逻辑进行切分,将乘法和加法分开执行且并行执行,例如:

module pipeline_handshake(
  input                 clk                     ,
  input                 rstn                    ,
  input         [7:0]   a1,a2,a3,a4,b1,b2,b3,b4 ,
  output  reg   [19:0]  result
);

reg [15:0] temp1,temp2,temp3,temp4;

//pipeline stage 1
always @(posedge clk)begin
  temp1 <= a1 * b1;
  temp2 <= a2 * b2;
  temp3 <= a3 * b3;
  temp4 <= a4 * b4;
end

//pipeline stage 2
always @(posedge clk)begin
  result <= temp1 + temp2 + temp3 + temp4;
end

endmodule

       如图所示,将一段长的乘加组合逻辑,切了两级流水,把乘法和加法分开来,好处是显而易见的,原来的组合逻辑延时是8bit乘法和3个16bit加法的延时,现在最大的组合逻辑延时是一个8bit乘法,或者3个16bit加法延时,该模块的最大时钟频率可以提高很多,这就是流水线技术。 

数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)

        运行结果如上,可以看到,数据因为做了两级流水,结果会比直接一条组合逻辑走到底的慢一个周期,但是流水线完全转起来之后,吞吐量都是一样的。


        前一文章讲完了握手,本文讲完了流水线,下面讲怎么把两个结合在一起,实现流水握手。上面只有两级流水,太短了,我们把战线拉长一点,在原来的基础上,令a1=c1+c2;b1=c3+c4; 

always @(posedge clk)begin
  a1 <= c1 + c2;
  b1 <= c3 + c4;
end

        即多加一级流水,分成如下3级。 

//pipeline stage 1
always @(posedge clk)begin
  a1 <= c1 + c2;
  b1 <= c3 + c4;
end

//pipeline stage 2
always @(posedge clk)begin
  temp1 <= a1 * b1;
  temp2 <= a2 * b2;
  temp3 <= a3 * b3;
  temp4 <= a4 * b4;
end

//pipeline stage 3
always @(posedge clk)begin
  result <= temp1 + temp2 + temp3 + temp4;
end

       确定好切分几级流水以及各个流水的工作内容后,就可以为各级流水线添加握手信号了。

        握手信号我们采用的是预取(pre-fetch)的方式,这种方式的握手可以解决数据卡在流水线中出不来的情况:对于本级来说,如果下一级准备好了,或者本级的输出为0(即本级流水肚子里没货),那么本级就可以接收数据。各级流水线运转的条件是:本级流水准备好(ready为高,且上级流水输出有效valid为高,即ready_本级 && valid_上级时,本级流水才开始工作,如果上一级数据不有效或者本级还没准备好,那么流水线就会一直停滞,直到数据有效和流水ready同时满足。

//pipeline stage 1
always @(posedge clk)begin
  if(!rstn)begin
    valid_r1 <= 1'b0;
  end
  else if(ready_o)begin    //如果本级准备好了,则将上一级的valid信号传递过来
    valid_r1 <= valid_i;
  end
end

always @(posedge clk)begin
  if(ready_o && valid_i)begin //输入数据ready_valid信号同时拉高时,数据有效并传入
    a1 <= c1 + c2;
    b1 <= c3 + c4;
    a2_r1 <= a2; a3_r1 <= a3; a4_r1 <= a4; //数据进来打一拍到第二级流水
    b2_r1 <= b2; b3_r1 <= b3; b4_r1 <= b4;
  end
end

        在第一级流水中,我们对a2~a4,b2~b4做了打一拍的操作,这里解释下为什么,因为我们输入的时候,a2~a4,b2~b4是跟c1~c4一起输入的,但是我们规划的流水线的第一级并没有对a2~a4,b2~b4进行操作而是在第二级对他们操作,所以如果我们不对输入的a2~a4,b2~b4有效数据进行一拍的缓存的话,若输入数据仅有效一个周期时,那么下个周期的时候,流水线使用的a2~a4,b2~b4就会出错,所以要在这里对第一级没有处理的数据进行打一拍,保证同时刻进来的数据走同一条流水

        同理可以写第二级和第三级的流水: 

//pipeline stage 2
assign ready_r1 = valid_r2 || ready_r2;
always @(posedge clk)begin
  if(!rstn)begin
    valid_r2 <= 1'b0
  end
  else if(ready_r1)begin   //如果本级准备好了,则将上一级的valid信号传递过来
    valid_r2 <= valid_r1;
  end
end
always @(posedge clk)begin
  temp1 <= a1    * b1;
  temp2 <= a2_r1 * b2_r1;
  temp3 <= a3_r1 * b3_r1;
  temp4 <= a4_r1 * b4_r1;
end

//pipeline stage 3
assign ready_r2 = ~valid_r3 || ready_i;
always @(posedge clk)begin
  if(!rstn)begin
    valid_r3 <= 1'b0;
  end
  else if(ready_r2)begin
    valid_r3 <= valid_r2;
  end
end

always @(posedge clk)begin
  if(ready_r2 && valid_r2)begin
    result <= temp1 + temp2 + temp3 + temp4;
  end
end

assign valid_o = valid_r3;

如上,就完成了对三级流水线添加握手信号的操作。下面我们来测试一下

initial begin
  clk   <= 1'b0;
  rstn  <= 1'b0;
  ready_i <= 1'b1;
  valid_i <= 1'b0;
  #20
  rstn <= 1'b1;
  #45
  ready_i <= 1; valid_i <= 1;   //流水线工作状态
  a2 <= 8'd2; a3 <= 8'd2; a4 <= 8'd2;
  b2 <= 8'd2; b3 <= 8'd2; b4 <= 8'd2;
  c1 <= 8'd2; c2 <= 8'd2; c3 <= 8'd2; c4 <= 8'd2;
  #10
  a2 <= 8'd3; a3 <= 8'd3; a4 <= 8'd3;
  b2 <= 8'd3; b3 <= 8'd3; b4 <= 8'd3;
  c1 <= 8'd3; c2 <= 8'd3; c3 <= 8'd3; c4 <= 8'd3;
  #10
  ready_i <= 1; valid_i <= 0;   //流水线上游断流
  a2 <= 8'd4; a3 <= 8'd4; a4 <= 8'd4;
  b2 <= 8'd4; b3 <= 8'd4; b4 <= 8'd4;
  c1 <= 8'd4; c2 <= 8'd4; c3 <= 8'd4; c4 <= 8'd4;
  #30
  a2 <= 8'd5; a3 <= 8'd5; a4 <= 8'd5;
  b2 <= 8'd5; b3 <= 8'd5; b4 <= 8'd5;
  c1 <= 8'd5; c2 <= 8'd5; c3 <= 8'd5; c4 <= 8'd5;
  #10
  ready_i <= 0; valid_i <= 1;   //流水线下游反压
  a2 <= 8'd6; a3 <= 8'd6; a4 <= 8'd6;
  b2 <= 8'd6; b3 <= 8'd6; b4 <= 8'd6;
  c1 <= 8'd6; c2 <= 8'd6; c3 <= 8'd6; c4 <= 8'd6;
  #30
  a2 <= 8'd7; a3 <= 8'd7; a4 <= 8'd7;
  b2 <= 8'd7; b3 <= 8'd7; b4 <= 8'd7;
  c1 <= 8'd7; c2 <= 8'd7; c3 <= 8'd7; c4 <= 8'd7;
  #10
  ready_i <= 1; valid_i <= 0;   //数据传输完毕
  a2 <= 8'd8; a3 <= 8'd8; a4 <= 8'd8;
  b2 <= 8'd8; b3 <= 8'd8; b4 <= 8'd8;
  c1 <= 8'd8; c2 <= 8'd8; c3 <= 8'd8; c4 <= 8'd8;
end

总代码

module pipeline_handshake(
  input                 clk                     ,
  input                 rstn                    ,
  input         [7:0]   a2,a3,a4,b2,b3,b4       ,
  input         [7:0]   c1,c2,c3,c4             ,
  output  reg   [19:0]  result                  ,

  input                 ready_i                 ,
  input                 valid_i                 ,
  output                ready_o                 ,
  output                valid_o                 
);

wire ready_r1,ready_r2;
reg valid_r1,valid_r2,valid_r3;
reg [7:0] a1,b1;
reg [7:0] a2_r1,a3_r1,a4_r1,b2_r1,b3_r1,b4_r1;

reg [15:0] temp1,temp2,temp3,temp4;

//pipeline stage 1
assign ready_o = ~valid_r1 || ready_r1;
always @(posedge clk)begin
  if(!rstn)begin
    valid_r1 <= 1'b0;
  end
  else if(ready_o)begin    //如果本级准备好了,则将上一级的valid信号传递过来
    valid_r1 <= valid_i;
  end
end

always @(posedge clk)begin
  if(ready_o && valid_i)begin //输入数据ready_valid信号同时拉高时,数据有效并传入
    a1 <= c1 + c2;
    b1 <= c3 + c4;
    a2_r1 <= a2; a3_r1 <= a3; a4_r1 <= a4; //数据进来打一拍到第二级流水
    b2_r1 <= b2; b3_r1 <= b3; b4_r1 <= b4;
  end
end


//pipeline stage 2
assign ready_r1 = ~valid_r2 || ready_r2;
always @(posedge clk)begin
  if(!rstn)begin
    valid_r2 <= 1'b0;
  end
  else if(ready_r1)begin   //如果本级准备好了,则将上一级的valid信号传递过来
    valid_r2 <= valid_r1;
  end
end
always @(posedge clk)begin
  if(ready_r1 && valid_r1)begin
    temp1 <= a1    * b1;
    temp2 <= a2_r1 * b2_r1;
    temp3 <= a3_r1 * b3_r1;
    temp4 <= a4_r1 * b4_r1;
  end
end

//pipeline stage 3
assign ready_r2 = ~valid_r3 || ready_i;
always @(posedge clk)begin
  if(!rstn)begin
    valid_r3 <= 1'b0;
  end
  else if(ready_r2)begin
    valid_r3 <= valid_r2;
  end
end

always @(posedge clk)begin
  if(ready_r2 && valid_r2)begin
    result <= temp1 + temp2 + temp3 + temp4;
  end
end

assign valid_o = valid_r3;

endmodule

总testbench

module pipeline_handshake_tb();

reg clk,rstn;

always #5 clk = ~clk;

reg valid_i,ready_i;
wire ready_o,valid_o;

reg [7:0] a2,a3,a4,b2,b3,b4,c1,c2,c3,c4;
wire [19:0] result;

initial begin
  clk   <= 1'b0;
  rstn  <= 1'b0;
  ready_i <= 1'b1;
  valid_i <= 1'b0;
  #20
  rstn <= 1'b1;
  #45
  ready_i <= 1; valid_i <= 1;   //流水线工作状态
  a2 <= 8'd2; a3 <= 8'd2; a4 <= 8'd2;
  b2 <= 8'd2; b3 <= 8'd2; b4 <= 8'd2;
  c1 <= 8'd2; c2 <= 8'd2; c3 <= 8'd2; c4 <= 8'd2;
  #10
  a2 <= 8'd3; a3 <= 8'd3; a4 <= 8'd3;
  b2 <= 8'd3; b3 <= 8'd3; b4 <= 8'd3;
  c1 <= 8'd3; c2 <= 8'd3; c3 <= 8'd3; c4 <= 8'd3;
  #10
  ready_i <= 1; valid_i <= 0;   //流水线上游断流
  a2 <= 8'd4; a3 <= 8'd4; a4 <= 8'd4;
  b2 <= 8'd4; b3 <= 8'd4; b4 <= 8'd4;
  c1 <= 8'd4; c2 <= 8'd4; c3 <= 8'd4; c4 <= 8'd4;
  #30
  a2 <= 8'd5; a3 <= 8'd5; a4 <= 8'd5;
  b2 <= 8'd5; b3 <= 8'd5; b4 <= 8'd5;
  c1 <= 8'd5; c2 <= 8'd5; c3 <= 8'd5; c4 <= 8'd5;
  #10
  ready_i <= 0; valid_i <= 1;   //流水线下游反压
  a2 <= 8'd6; a3 <= 8'd6; a4 <= 8'd6;
  b2 <= 8'd6; b3 <= 8'd6; b4 <= 8'd6;
  c1 <= 8'd6; c2 <= 8'd6; c3 <= 8'd6; c4 <= 8'd6;
  #30
  a2 <= 8'd7; a3 <= 8'd7; a4 <= 8'd7;
  b2 <= 8'd7; b3 <= 8'd7; b4 <= 8'd7;
  c1 <= 8'd7; c2 <= 8'd7; c3 <= 8'd7; c4 <= 8'd7;
  #10
  ready_i <= 1; valid_i <= 0;   //数据传输完毕
  a2 <= 8'd8; a3 <= 8'd8; a4 <= 8'd8;
  b2 <= 8'd8; b3 <= 8'd8; b4 <= 8'd8;
  c1 <= 8'd8; c2 <= 8'd8; c3 <= 8'd8; c4 <= 8'd8;
end

pipeline_handshake u_pipeline_handshake(
  .clk      (clk)     ,
  .rstn     (rstn)    ,
  .a2(a2),.a3(a3)     ,
  .a4(a4),.b2(b2)     ,
  .b3(b3),.b4(b4)     ,
  .c1(c1),.c2(c2)     ,
  .c3(c3),.c4(c4)     ,
  .result   (result)  ,
  .ready_i  (ready_i) ,
  .ready_o  (ready_o) ,
  .valid_i  (valid_i) ,
  .valid_o  (valid_o)

);

endmodule

波形

        这里关键就是这个波形,请一定要看好,流水握手掌握了之后,在我们写项目的过程中非常有用! 

数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)

        在tb中分别模拟流水线正常工作、上游断流、下游反压以及最后数据传输完毕之后的状态,看看在不同状态下,流水线是否会出现数据传输错误或者数据丢失的情况。 

        把整个模块包装成一个module后,我们这个就相当于一个流水处理一段长延时组合逻辑的IP。对于我们这个IP来说,valid_i和ready_o同时为高时,数据有效,即a2\b2=2,3,6时数据是有效的,一开始数据有效了两拍,分别是2和3,过了三拍后,数据输出valid_o拉高两拍,因为ready_i为1,所以下游可以接收数据,所以valid_o拉高两周期把输出的数据传完后就拉低了。

        输入数据有效两周期后,流水线断流,从而导致输入数据无效,所以流水线在输出两个结果后valid_o保持为低,直到上游又输入有效数据。

        此时进入下游反压状态,下游反压并不会立刻反馈到第一级的流水,而是由下至上逐级反馈,所以下游反压的时候,空的流水线不会立即停止,而是等数据把流水线灌满之后才停止。下游反压时,流水线进了三周期数据6,把流水线灌满之后,流水线就停滞了(表现为ready_o拉低,告诉上游我吃不进数据了)。

        数据停止传输时,又把ready_i拉高,即下游不反压了,但此刻上游数据已经传输完毕,所以流水线不再接受数据,而是把之前吃进来的三周期数据6,计算后再吐出来,可以看到确实输出时ready_i和valid_o同时保持三周期高,把三级流水线内部数据吐完后,valid_o拉低,流水线清空。

        这段流水握手的代码是我写的比较好看的一段代码,解决了我特别多的数据传输问题,大家感兴趣的可以借鉴。 


 更多手撕代码题可以前往 数字IC手撕代码--题库

到了这里,关于数字IC手撕代码-流水握手(利用握手解决流水线断流、反压问题)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数字IC设计中的握手与反压

    本文的主要目的是介绍清楚数字IC设计中握手和反压的原理和意义 如图所示,信号从输入端到A,经过模块A处理后,再送入到B模块进行处理。为了防止B错误读取A中的数据,A与B之间添加了信号Valid,只有当Valid信号为真时,A输出的数据才是有效数据,同时,为了防止B出现问题

    2024年02月16日
    浏览(45)
  • 流水线乘法器的原理及verilog代码

    二进制数乘法的显著特点就是可以将乘法转换为移位,乘2就是左移一位,乘2^n就是左移n位。而一个二进制数又可以看成是由若干个2的i次方的和。 设被乘数和乘数分别为M、N,且都是32位的二进制数,乘积结果为64位 的向量CO则 。 所以乘法可以由移位电路和加法器完成。计算

    2024年02月10日
    浏览(30)
  • 用diffuser代码方式打造Ai作画流水线(1)

    stablediffusion已经成为作画人的标配,通过sd的ps插件,或者通过SD webui就可以快速的构建出一套属于自己的Ai作画环境。这种可视化的界面确实方便也是更好艺术家个代码工程师协同合作的模式,可视化界面方便艺术家创造,需要更多组建、模型支持时候可以算法工程师来帮忙

    2024年02月11日
    浏览(32)
  • 【物联网】继续深入探索ADC模拟转数字的原理——Flash ADC&流水线ADC&逐次逼近型SAR ADC

    这篇文章主要弥补上一篇关于ADC的不足,更加深入了解ADC数模转换器的工作原理,举例常见的三种ADC,分别为Flash ADC流水线ADC逐次逼近型SAR ADC。 【物联网】深入了解AD/DA转换技术:模数转换和数模转换 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不

    2024年02月05日
    浏览(26)
  • Jenkins流水线整合k8s实现代码自动集成和部署

    1、安装好k8s集群 这里先要搭建好一个K8s集群,笔者这边就采用使用了一个一主一丛的k8s集群,k8s集群的版本使用1.19.5版本,服务器的配置:2核4G,操作系统: CentOS Linux release 7.9.2009 (Core) 主机名         ip k8smaster 192.168.19.8 k8sworker         192.168.19.9 具体的安装步骤可以

    2024年02月05日
    浏览(36)
  • 解决流水线瓶颈、提升编码效率的五个方法(上篇)

    不是吹牛,但我所管理的开发团队在软件开发速度上表现出色,能够高质量地编写代码,并在白噪声的陪伴下保持高效。 但就像所有的故事一样,一开始并不是这样的,甚至相去甚远。我们经历了时间、沟通、合作、失败、成功以及许多关于生产力的会议(有时很尴尬,但它

    2023年04月20日
    浏览(30)
  • 【jenkins】jenkins流水线构建打包jar,生成docker镜像,重启docker服务的过程,在jenkins上一键完成,实现提交代码自动构建的功能

     【jenkins】jenkins流水线构建打包jar,生成docker镜像,重启docker服务的过程,在jenkins上一键完成,实现提交代码自动构建,服务重启,服务发布的功能。一键实现。非常的舒服。 这是 shell脚本  脚本名称: startup.sh   本脚本的功能是在jenkins 构建完项目后,通过jar包生成dock

    2024年02月11日
    浏览(32)
  • 1.6流水线:流水线、流水线周期、流水线执行时间、流水线吞吐率、流水线加速比

    相关参数计算:流水线执行时间计算、流水线吞吐率、流水线加速比等。 流水线是指在程序执行时多条指令重叠进行操作的一种准并行处理实现技术。各种部件同时处理是针对不同指令而言的,它们可同时为多条指令的不同部分进行工作,以提高各部件的利用率和指令的平均

    2024年02月09日
    浏览(34)
  • 8位加法器的流水线设计(2级流水、四级流水)

    思考:流水线的设计是为了提高频率,在一个耗时比较长的组合逻辑中,加入寄存器, 可以将这个较长的组合逻辑分裂几份,从而提升主频,缺点是增加了寄存器的资源。 二级流水线的加法器的设计思想如下: 在第一个周期完成低四位的加法计算,使用一个四位加法器即可

    2024年02月11日
    浏览(30)
  • jenkins流水线

    1、 二、 三、 四、 五、 六、  

    2024年02月05日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包