组成原理-lab1难点之流水线

流水线理解笔记

由于在做组成原理实验的时候,碰到了流水线这个概念,感觉什么都不懂,所以从头开始做做试验来理解一下这个概念,其中的Demo来自于重庆大学组成原理实验指导书Lab1,它的作者是

lvyufeng@cqu.edu.cn

我在作者提供的代码、描述的基础上进行了仿真实验,结合个人理解写下了这篇博客,这个博客以实验、代码为基础,根据行为仿真结果来进行讨论。

错误说明

  • 在最终的实现中,rst与流水线的刷新不应融合到一起,流水线的刷新应当用于流水线的部分刷新,而rst应该用于整体刷新。
  • vivado 有相应的免费版本,官网提供免费下载。

环境

顺便吐槽一下这个HEXO,我用typora编辑出来的代码,他给我隔一行隔一行的放,我用makrtext编辑的代码他直接给我搞成一行了,这咋办呢?

使用的环境是Vivado 2019.1 ,如果你也想用这个环境的话,你可以:

  • 去官网买正版(绝对支持)
  • 去网上搜索一波
  • 去淘宝花30买个

简单流水线

先从一个简单的、没有阻塞的流水线开始。

在开始之前,给大家几点建议(个人看法):

  • 装个文本编辑器吧:vivado自带的文本编辑器太难用了, 建议装一个VSC或者sublime。
  • 去复习一下VHDL的基础,熟悉一下什么叫阻塞赋值什么叫非阻塞赋值,以及一些类型之间的区别

下面先看代码:

`timescale 1ns / 1psmodule simplePipeline#(parameter WIDTH = 100)(input clk,input [WIDTH - 1 : 0] datain,output [WIDTH - 1: 0] dataout);reg [WIDTH - 1 : 0] piprData1;reg [WIDTH - 1 : 0] piprData2;reg [WIDTH - 1 : 0] piprData3;always @(posedge clk) beginpiprData1 <= datain;endalways @(posedge clk) beginpiprData2 <= piprData1;endalways @(posedge clk) beginpiprData3 <= piprData2;endassign dataout = piprData3;endmodule

仿真代码:

`timescale 1ns / 1psmodule simSimplePipeline();reg clk;reg [7 : 0]datain;wire [7 : 0]dataout;simplePipeline #(8)test1(.clk(clk),.datain(datain),.dataout(dataout));initial beginclk = 1'b0;datain = 7'd0;endalways #5 beginclk = ~clk;endalways @(posedge clk)begindatain = datain + 1'b1;endendmodule

仿真波形图:

代码

这个基础的流水线非常好理解,就是数据传入后,每次检测到时钟上升沿,就将数据进行一次平移,如下图所示:

可以看到在这个过程中,前三次都需要在检测到时钟上升沿的时候才能够进行非阻塞赋值,而最后一次是连续赋值。

要想理解这个过程,需要明晰一些概念:

  • 连续赋值:带有assign的是连续赋值,如上方代码中的assign dataout = piprData3;连续赋值中赋值符号左方的变量必须是线网类型,这步操作,从硬件的角度理解就是将赋值符号右边与左边进行连线,当右边发生变化的时候,左边立刻发生变化
  • 非阻塞赋值与阻塞赋值
    • 阻塞赋值: [变量] = [逻辑表达式];
    • 非阻塞赋值:[变量] <= [逻辑表达式];
    • 阻塞赋值和非阻塞赋值之间的区别主要在于阻塞,所谓阻塞,阻塞的是当前其他的赋值任务,其中:
      • 阻塞赋值,阻塞了其他的赋值任务,所以表现出的效果就和C中的赋值语句非常相似
      • 非阻塞赋值,没有阻塞其他的赋值任务,所有的赋值都是并行的,想要理解这个,你需要先明确一件事情:用Verilog写代码写出来的是个电路,而非软件

有阻塞的三级流水

代码:

`timescale 1ns / 1psmodule stallablePipeline#(parameter WIDTH = 100)(input                   clk,input                   rst,input                   validIn,input [WIDTH - 1 : 0]   dataIn,input                   outAllow,output wire                 validOut,output wire [WIDTH - 1 : 0]   dataOut);// pipe1Valid; 代表当前级是否存有有效数据reg                 pipe1Valid;reg [WIDTH - 1 : 0] pipe1Data;reg                 pipe2Valid;reg [WIDTH - 1 : 0] pipe2Data;reg                 pipe3Valid;reg [WIDTH - 1 : 0] pipe3Data;/*------------------------PIPE1 LOGIC------------------------*/// 表示pipe1能否被上一级刷新wire                pipe1AllowIn;// 表示pipe1是否可以用于刷新下一级wire                pipe1ReadyGo;// 表示pipe1能否进入pipe2wire                pipe1ToPipe2Valid;// 一旦pipe1的值有效,就可以传给下一个assign pipe1ReadyGo = 1'b1;// 如果pipe1中的值已经无效,或者这一轮一定会传给下一个,那么就可以进行接收assign pipe1AllowIn = ! pipe1Valid || pipe1ReadyGo && pipe2AllowIn;// 如果pipe1有效,并且pipe1可以进行传输,那么pipe1ToPipe2Valid可以进行。assign pipe1ToPipe2Valid = pipe1Valid && pipe1ReadyGo;always @(posedge clk)begin// 如果需要刷新,那么pipe1Valid 变为0,表示pipe1中的值不再有效if( rst ) beginpipe1Valid <= 1'b0;end// 不需要刷新,并且pipe1可以进行刷新// 如果输入端有输入,就代表下一个状态pipe1Valid的值有效// 如果无输入,就代表下一个状态无效else if(pipe1AllowIn)beginpipe1Valid <= validIn;end// 如果输入值有效,并且pipe1可以读入,那么就从输入端进行读入if(validIn && pipe1AllowIn)beginpipe1Data <= dataIn;endend/*------------------------PIPE2 LOGIC------------------------*/wire                pipe2AllowIn;wire                pipe2ReadyGo;wire                pipe2ToPipe3Valid;// 一样一样的assign pipe2ReadyGo = 1'b1;assign pipe2AllowIn = ! pipe2Valid || pipe2ReadyGo && pipe3AllowIn;assign pipe2ToPipe3Valid = pipe2Valid && pipe3ReadyGo;always @(posedge clk)beginif( rst ) beginpipe2Valid <= 1'b0;endelse if(pipe2AllowIn)beginpipe2Valid <= pipe1ToPipe2Valid;endif(pipe1ToPipe2Valid && pipe2AllowIn)beginpipe2Data <= pipe1Data;endend/*------------------------PIPE3 LOGIC------------------------*/// 一样一样的wire                pipe3AllowIn;wire                pipe3ReadyGo;// 一样一样的assign pipe3ReadyGo = 1'b1;assign pipe3AllowIn = ! pipe3Valid || pipe3ReadyGo && outAllow;always @(posedge clk)beginif( rst ) beginpipe3Valid <= 1'b0;endelse if(pipe3AllowIn)beginpipe3Valid <= pipe2ToPipe3Valid;endif(pipe2ToPipe3Valid && pipe3AllowIn)beginpipe3Data <= pipe2Data;endendassign validOut = pipe3Valid && pipe3ReadyGo;assign dataOut = pipe3Data;endmodulepipe1AllowIn

仿真

`timescale 1ns / 1psmodule simStallablePipeline();reg clk;reg rst;reg validIn, outAllow;reg [7 : 0]dataIn;wire [7 : 0]dataOut;wire validOut;stallablePipeline #(8)test2(.clk(clk),.rst(rst),.validIn(validIn),.dataIn(dataIn),.outAllow(outAllow),.validOut(validOut),.dataOut(dataOut));initial begin// 暂且不刷新rst = 1'b0;// 永远可进validIn = 1'b1;// 永远可出outAllow = 1'b1;clk = 1'b0;dataIn = 7'd0;),endalways #5 beginclk = ~clk;endalways @(posedge clk)begindataIn = dataIn + 1'b1;endendmodule

波形图

这个代码看似非常复杂,实际上还是很简实际上还是很简单的,每一层的逻辑都是相似的,每一层与上一层的数据转移是有三个变量进行控制的:

  • pipeXReadyGo:表示当前层的数据能否进入下一层
  • pipeXAllowIn:表示当前层能否接受上一层的数据
  • pipeXToPipeX+1Valid:表示在本时钟周期内,是否会进行传输

我们设计的逻辑如下:

  • 当前层能否进入下一层:始终可以
  • 当前层能否接受:当前层值无效或当前层可以进入下一层,并且下一层可以接收
  • 本次能否转移:本层值有效,并且可以传入下一层

由于我们这样的设计思路,并且allowIn,validOut始终为1,所以我们达到的效果与第一次相同。

简单流水线加法器(8bits 2级流水)

`timescale 1ns / 1psmodule adder8Bits2Steps(input  [7 : 0] num1,input  [7 : 0] num2,// 上一位的进位input           cin,input           clk,output reg         count,output reg [7 : 0] sum);reg         countTemp;reg [3 : 0] sumTemp;always @(posedge clk) begin{countTemp, sumTemp} <= num1[3:0] + num2[3:0] + cin;endalways @(posedge clk) begin{count, sum} <= {{1'b0, num1[7 : 4]} + {1'b0, num2[7 : 4]} + countTemp, sumTemp};endendmodule

这个比较好懂,我就不做过多说明了,但是我想着重讲的是:为什么流水线比普通的看?我们从RTL电路上来看:

上面的是普通的加法器,下面的是流水线加法器,可以看到,对比两者,流水线加法器所使用的寄存器较多。

再看运算速度,对于上面的普通加法器,完成sum1需要等待8个周期,完成sum1后再进行sum0,又需要八个周期,这样就需要16个周期。而对于下面的流水线来说:前四位和后四位的加法同时进行,消耗四个时钟周期,完成后,进行进位,消耗4个周期。所以说,使用流水线可以加快运算。

8Bit 4级流水——无阻塞简单版

一共有四层,所以叫四级流水:

`timescale 1ns / 1psmodule adder8Bits4Steps(input  [7 : 0]    num1,input  [7 : 0]    num2,// 上一位的进位input              cin,input              clk,output reg         count,output reg [7 : 0] sum);reg count1, count2, count3;reg [1:0] sum1;// 第一级流水always @ (posedge clk) begin{count1, sum1} <= {1'b0, num1[1:0]} + {1'b0, num2[1:0]} + cin;end// 第二级流水reg [3:0] sum2;always @ (posedge clk) begin{count2, sum2} <= {{1'b0, num1[3:2]} + {1'b0, num2[3:2]} + count1, sum1};end// 第三级流水reg [5:0] sum3;always @ (posedge clk) begin{count3, sum3} <= {{1'b0, num1[5:4]} + {1'b0, num2[5:4]} + count2, sum2};end// 第四级流水always @ (posedge clk) begin{count, sum} <= {{1'b0, num1[7:6]} + {1'b0, num2[7:6]} + count3, sum3};endendmodule

生成的RTL电路图如下:


仿真:

`timescale 1ns / 1psmodule simAdder8Bits4Steps();reg  [7 : 0]     num1;reg  [7 : 0]     num2;// 上一位的进位reg              cin;reg              clk;wire              count;wire      [7 : 0] sum;adder8Bits4Steps test1(.num1(num1),.num2(num2),.cin(cin),.clk(clk),.count(count),.sum(sum));initial beginnum2 = 8'd1;num1 = 8'd13;clk  = 1'b0;cin  = 1'b0;endalways #5 clk = ~clk;endmodule

波形图:

只用了四个周期就算出来了。

8Bit 4级流水——带有暂停刷新功能

在做它之前,我们先来讨论一个问题:为什么需要暂停刷新功能?这是因为不同的指令需要不同的周期,在执行多周期指令的时候,如果cpu不支持动态指令调度和多发射,那么必须停顿

`timescale 1ns / 1ps// 带有暂停与刷新功能的八位四级流水线,散装英语module adder8Bits4StepsWithStopAndClear(input  [7 : 0]    num1,input  [7 : 0]    num2,// 上一位的进位input              cin,// 输入是否有效input              validIn,// 刷新input              rst,// 暂停input              stop,input              clk,input              outAllow,// 判断输出是否有效output wire         validOut,output wire         count,output wire [7 : 0] sum);reg count1, count2, count3;// 记录每一层是否有效reg pipe1Valid;reg pipe2Valid;reg pipe3Valid;reg pipe4Valid;// 第一级流水reg [1:0] sum1;// 控制变量:和之前的一样// 表示pipe1能否被上一级刷新wire   pipe1AllowIn;// 表示pipe1是否可以用于刷新下一级wire   pipe1ReadyGo;// 表示pipe1能否进入pipe2wire   pipe1ToPipe2Valid;// 如果没有暂停,那么就可以Goassign pipe1ReadyGo = !stop;// 如果pipe1中的值已经无效,或者这一轮一定会传给下一个,那么就可以进行接收assign pipe1AllowIn = ! pipe1Valid || pipe1ReadyGo && pipe2AllowIn;// 如果pipe1有效,并且pipe1可以进行传输,那么pipe1ToPipe2Valid可以进行。assign pipe1ToPipe2Valid = pipe1Valid && pipe1ReadyGo;// 是否有效always @ (posedge clk) begin// 如果需要刷新,那么pipe1Valid 变为0,表示pipe1中的值不再有效if( rst ) beginpipe1Valid <= 1'b0;end// 不需要刷新,并且pipe1可以进行刷新// 如果输入端有输入,就代表下一个状态pipe1Valid的值有效// 如果无输入,就代表下一个状态无效else if(pipe1AllowIn)beginpipe1Valid <= validIn;end// 如果输入值有效,并且pipe1可以读入,那么就从输入端进行读入if(validIn && pipe1AllowIn)begin{count1, sum1} <= {1'b0, num1[1:0]} + {1'b0, num2[1:0]} + cin;endend// 第二级流水reg [3:0] sum2;wire   pipe2AllowIn;wire   pipe2ReadyGo;wire   pipe2ToPipe3Valid;assign pipe2ReadyGo = !stop;assign pipe2AllowIn = ! pipe2Valid || pipe2ReadyGo && pipe3AllowIn;assign pipe2ToPipe3Valid = pipe2Valid && pipe2ReadyGo;always @ (posedge clk) beginif( rst ) beginpipe2Valid <= 1'b0;endelse if(pipe2AllowIn)beginpipe2Valid <= pipe1ToPipe2Valid;endif(pipe1ToPipe2Valid && pipe2AllowIn)begin{count2, sum2} <= {{1'b0, num1[3:2]} + {1前'b0, num2[3:2]} + count1, sum1};endend// 第三级流水reg [5:0] sum3;wire   pipe3AllowIn;wire   pipe3ReadyGo;wire   pipe3ToPipe4Valid;assign pipe3ReadyGo = !stop;assign pipe3AllowIn = ! pipe3Valid || pipe3ReadyGo && pipe4AllowIn;assign pipe3ToPipe4Valid = pipe3Valid && pipe3ReadyGo;always @ (posedge clk) beginif( rst ) beginpipe3Valid <= 1'b0;endelse if(pipe2AllowIn)beginpipe3Valid <= pipe2ToPipe3Valid;endif(pipe2ToPipe3Valid && pipe3AllowIn)begin{count3, sum3} <= {{1'b0, num1[5:4]} + {1'b0, num2[5:4]} + count2, sum2};endend// 第四级流水wire pipe4AllowIn;wire pipe4ReadyGo;reg [7 : 0]sum4;reg        count4;// 一样一样的assign pipe4ReadyGo = !stop;assign pipe4AllowIn = ! pipe4Valid || pipe4ReadyGo && outAllow;always @(posedge clk)beginif( rst ) beginpipe4Valid <= 1'b0;endelse if(pipe3AllowIn)beginpipe4Valid <= pipe3ToPipe4Valid;endif(pipe3ToPipe4Valid && pipe4AllowIn)begin{count4, sum4} <= {{1'b0, num1[7:6]} + {1'b0, num2[7:6]} + count3, sum3};endendassign validOut = pipe4Valid && pipe4ReadyGo;assign sum = sum4;assign count = count4;endmodule

仿真

`timescale 1ns / 1ps// 测试最后一个模型module finalSim();reg  [7 : 0]    num1;reg  [7 : 0]    num2;// 上一位的进位reg              cin;// 输入是否有效reg              validIn;// 刷新reg              rst;// 暂停reg              stop;reg              clk;reg              outAllow;// 判断输出是否有效wire         validOut;wire         count;wire [7 : 0] sum;adder8Bits4StepsWithStopAndClear test1(num1, num2, cin, validIn, rst, stop, clk, outAllow, validOut, count, sum);initial beginnum1 = 8'd1;num2 = 8'd1;cin  = 1'd0;validIn = 1'd1;rst = 1'd0;stop = 1'd0;clk = 1'd0;outAllow = 1'd1;# 30 rst = 1'b1;# 30 rst = 1'b0;endalways # 5 beginclk = ~ clk;endalways # 10 beginstop = ~ stop;endendmodule

仿真波形图:

30ns时,rst = 1,60ns时,重新将rst置为0,并且,每10ns翻转一次stop

[在90ns的时候将rst置为1,并在120ns时,将其置为0,则仿真波形图如下:

validOut正确,如果大家觉得这段结果难以理解,那不是因为你们菜,而是因为我这个测试用例设计的太狗屎了。

下面取消对rst的更改,全程不刷新,只进行周期性stop:

最后,不stop,也不刷新:

算了,再加最后一组,只刷新,不stop:

在最后一组中,可以看出:当刷新被重置为0时,开始重新计算,4个时钟周期后,计算完成,validOut被重新置为1。如果大家还是看不懂,可以copy下来代码,自己跑跑试试,我这个用例造的着实垃圾。

组成原理-lab1难点之流水线相关推荐

  1. 超流水线计算机原理,6计算机组成原理第6章流水线原理.ppt

    文档介绍: 第6章流水线原理及其§1重叠方式通常提高指令执行速度的途径有如下三种:提高处理机的工作主频.采用更好的算法和设计更好的功能部件.多条指令并行执行,称为指令级并行技术.狼畜痔隶嘿拾冈候裸腺绘 ...

  2. 计算机组成原理中流水线的极,6计算机组成原理第6章流水线原理.ppt

    文档介绍: 第6章流水线原理及其§1重叠方式通常提高指令执行速度的途径有如下三种:提高处理机的工作主频.采用更好的算法和设计更好的功能部件.多条指令并行执行,称为指令级并行技术.辛腆拴泵蛇势哗耀投搏却 ...

  3. 计算机组成原理解释流水线,6计算机组成原理第6章流水线原理.ppt

    文档介绍: 第6章流水线原理及其§1重叠方式通常提高指令执行速度的途径有如下三种:提高处理机的工作主频.采用更好的算法和设计更好的功能部件.多条指令并行执行,称为指令级并行技术.信蚊臃箭酵竖鼎鲤砌下肘 ...

  4. 计算机组成原理笔记--简单五级流水线设计与性能

    文章目录 前言 五级流水线 冒险/冲突(Hazards/Conflict) 控制单元 模块调度 系列目录 前言 上一章 单周期设计与多周期设计 一章中提到了两种微架构模型.这一章主要会讲解基本流水线的 ...

  5. Verilog-移位操作(算术右移与逻辑右移)

    Verilog-移位操作(算术右移与逻辑右移) 写在前面 MIPS文档中的指令介绍 算术右移与逻辑右移及其Verilog语言区别 算术左移与逻辑左移 写在前面 在计算机组成原理课程设计-Verilog ...

  6. 计算机硬件方面课程,计算机硬件课程教学改革分析

    摘要:针对目前国内高校计算机专业开展的硬件类课程教学,分析了理论教学和实践教学中存在的主要问题.结合目前应用型本科院校的开展和教学经验,从理论教学体系和实验教学体系两个方面进行探索,旨在提高硬件的教学 ...

  7. 微机原理和计算机组成原理一样吗_计算机/软工408考研---组成原理+OS重难点

    0. 前言 考虑到408中计算机组成原理与操作系统联系比较紧密,所以本篇文章将组成原理和操作系统放在一起进行总结出重难点,同时将组成原理和操作系统中知识交融关联性比较大的部分进行整合. 本篇文章旨在分 ...

  8. 计算机组成原理流水线ppt,[计算机组成原理第讲流水线.ppt

    [计算机组成原理第讲流水线 第6章 中央处理器 Homework 3.中央控制和局部控制相结合 大多数指令的执行过程中包含的微操作个数及指令的执行时间比较接近或相等,可以将这样的指令作为标准建立统一的 ...

  9. 计算机系统流水线方面的基础知识,软件设计师重点难点——流水线.doc

    文档介绍: 流水线这个知识点在软件设计师考试中是个重点也是个难点,考查的频率比较高.之所以说流水线是个难点,有两方面的原因:一方面是需要理解流水线的理论,了解其工作原理,计算方式;另一方面是在软考当中 ...

最新文章

  1. 新闻网大数据实时分析可视化系统项目——5、Hadoop2.X HA架构与部署
  2. 机器推理文本+视觉,跨模态预训练新进展
  3. 解决Xcode The selected destination does not support the architecture 错误错误
  4. mysql函数GROUP_CONCAT,Cast,convert
  5. Redis 配置连接池,redisTemplate 操作多个db数据库,切换多个db,解决JedisConnectionFactory的设置连接方法过时问题。(转)
  6. PspCidTable 完全解读
  7. mysql如何删除列中的约束_我们如何从现有MySQL表的列中删除NOT NULL约束?
  8. Android进阶知识:绘制流程(上)
  9. weh shell高大上?一文教你实现
  10. 2020牛客多校第1场H-Minimum-cost Flow-最小费用流
  11. HEVC/H265 HM10.0 分析(一)NALread.cpp
  12. css 查看更多_在Scrapy中如何利用CSS选择器从网页中采集目标数据——详细教程(上篇)...
  13. 普通用户通过Putty密钥方式登录
  14. mysql数据类型详解系列
  15. 钉钉、微信产品大PK,基因已经决定了结果
  16. 【Linux】文件系统及软硬连接
  17. MongoDB 数据库简介、安装及使用
  18. Premiere 常用视频概念
  19. vue2 - 基于Export2Excel.js导出Excel案例(js-xlsx插件二次封装使用)
  20. opencv图像形态学运算

热门文章

  1. 疫情期间远程办公,我这么计划
  2. python大神的成长之路普通话三分钟_三分钟普通话说话30篇-我的成长之路 - 希赛网...
  3. 走近棒球运动·底特律老虎队·MLB棒球创造营
  4. 神经网络在控制中的应用,神经元网络控制的作用
  5. 数学学习与研究杂志数学学习与研究杂志社数学学习与研究编辑部2023年第3期目录
  6. 英语b计算机考试成绩查询成绩查询,大学英语b成绩查询
  7. Linux+开发+运维-推荐书籍与学习路线
  8. 苹果CMS V10仿韩剧TV主题模板源码 | 苹果CMS主题
  9. 都知道vue3响应式是Proxy实现的,进来把proxy与Reflect吃透
  10. 简易的C语言判断输入的年份为闰年还是平年