csapp第四章

第四章

指令被编码为由一个或者多个字节序列组成的二进制格式,一个处理器支持的的指令和指令的字节级编码称为他的指令集体系结构(instruction-Set Architecture)ISA。不同的处理器家族有不同的ISA,一个程序编译成在一种机器上运行就不能在另外一种机器上运行。因此,ISA在编译器编写者和处理器设计人员之间提供了一个概念抽象层,编译器编写者只需要知道允许哪些指令,以及它们是如何编码的;而处理器设计者必须建造出执行这些指令的处理器。

HCL:硬件控制语言,一种描述硬件系统控制部分的简单语言

  • 学习过程
    • 学习一种顺序执行,功能正确但是不实用的Y86处理器。这个处理器每个时钟周期执行一条完整的Y86-64指令。所以它的时钟必须足够慢,以允许在一个周期内完成所有的动作。这样一个处理器是可以实现的,但是它的性能远远低于同样的硬件应该能达到的性能。

    • 改造成流水线化的处理器。这个处理器将每条指令的执行分解成五步,每个步骤由一个独立的硬件部分或阶段(stage)来处理。指令步经流水线的各个阶段,且每个时钟周期有一条新指令进人流水线。所以,处理器可以同时执行五条指令的不同阶段。为了使这个处理器保留Y86-64 ISA 的顺序行为,就要求处理很多冒险或冲突(hazard)情况,冒险就是一条指令的位置或操作数依赖于其他仍在流水线中的指令。

4.1 Y86-64指令集体系结构

定义一个指令集体系结构(例如Y86-64)包括定义各种状态单元、指令集和它们的编码、一组编程规范和异常事件处理。

4.1.1 程序员的可见状态
  • 可见状态是指每条指令都会读取或者修改处理器状态的某些部分
  • 15个寄存器,降低了编码的复杂度
  • %rsp栈指针
  • 简化了条件码寄存器
  • PC:当前正在执行指令的地址(注意:是"当前",和指令的地址,不是指令的内容)
  • 虚拟内存
4.1.2 4.1.3 Y86-64指令 指令编码
  • 不可以从一个内存地址传到另外一个内存地址。不允许将立即数传送到内存
  • 每条指令需要1-10个字节不等,每条指令的第一个字节表明指令的类型,这个字节分为两个部分,每部分4位,高四位是代码部分,第四位是功能部分
  • 整数操作指令,只对寄存器数据进行操作


  • 寄存器:程序寄存器存在CPU中的一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。在指令编码中以及在我们的硬件设计中,当需要指明不应访问任何寄存器时,就用ID值OxF来表示。
  • 有些指令需要一个附加的4字节常数字(constant word)。这个字能作为irmovq的立即数数据,rrumovq 和 mrmovq的地址指示符的偏移量,以及分支指令和调用指令的目的地址。注意,分支指令和调用指令的目的是一个绝对地址,使描述更加简洁。和IA32一样所有整数都采用小端法编码

  • 比较x86和y86指令编码

  • RISC和CISC指令集
    x86-64有时称为"复杂指令集计算机"(CISC,读作"sisk"),与"精简指令集计算机"(RISC,读作"risk")相对。从历史上看,先出现了CISC机器
4.1.4 Y86 异常
Y86-64程序

与X86的不同之处

  • Y86需要将常数加载到寄存器,因为不允许从一个内存地址传到另外一个内从地址,也不允许将立即数传送到内存
  • Y86的四个整数操作只能对寄存器数据进行操作,碰都立即数就将数字传到寄存器中保存在进行操作
  • 要实现从内从中读取一个数值并将其与一个寄存器相加,Y86需要将内存中的数据加载到寄存器中,然后进行寄存器之间的算术运算

伪指令.pos 0(第2行)告诉汇编器应该从地址0处开始产生代码。这个地址是所有Y86-64程序的起点。接下来的一条指令(第3行)初始化栈指针。我们可以看到程序结尾处(第40行)声明了标号stack,并且用一个.pos伪指令(第39行)指明地址0x200。因此栈会从这个地址开始,向低地址增长。我们必须保证栈不会增长得太大以至于覆盖了代码或者其他程序数据。

4.1.6 一些Y86指令详情
  1. pushq %rsp 有两种不同的约定
  • 压入%rsp原始值
  • 压入减去8的值
  • 用x86实验发现压入的是旧值
  1. popq %rsp
  • %rsp中存的是从内存中读出的那个值

4.2 逻辑设计的硬件控制语言HCL(hardware control language)

实现一个数字系统需要三个主要的组成部分:计算对位进行操作的函数组合逻辑,存储位的存储单元,以及控制存储单元更新的时钟信号。

  • 现在大多数设计都是用硬件描述语言(HDL,hardware description language)来表达。他是一种文本表示,
  • VHDL 也是用来描述的
4.2.1 逻辑门

逻辑门下面是对应的HCL表达式:AND 用&.&表示,OR用||表示,而NOT用!表示。我们用这些符号而不用C语言中的位运算符&、│和~,这是因为逻辑门只对单个位的数进行操作,而不是整个字。

4.2.2 组合电路和HCL布尔表达式


bool eq = (a && b)|| (!a && !b);当ab都是1或者都是0时输出1


bool out = (s && a) || (!s && b);

  • C的逻辑控制表达式可以被部分求值,而组合逻辑没有部分求值的规则
  • C0代表false其他代表true,逻辑门只有01
4.2.3 字级的组合电路和HCL整数表达式

在HCL中,我们将所有的字级的信号都声明为int,不指定字的大小

字级相等
bool Eq = (A == B);参数AB是int类型的

这个表达式包含一系列的情况,每种情况à都有一个布尔表达式select、和一个整数表达式ex pr,,前者表明什么时候该选择这种情况,后者指明的是得到的值。

在这段代码中,第二个选择表达式就是1,表明如果前面没有情况被选中,那就选择这种情况。这是HCL中一种指定默认情况的方法。几乎所有的情况表达式都是以此结尾的。

  • 最小值

    ALU的减法也是输入B减去输入A,和subq保持一致
集合关系
4.2.5 存储器和时钟

组合电路不存储任何信息。

  • 时序电路,就是有状态并且在这个状态下进行计算的系统,必须引入按位存储信息的设备
  • 两类存储器设备
  1. 时钟寄存器(寄存器):存储单个位或字
  2. 随机访问存储器(内存):多个字。(处理器的虚拟内存系统,寄存器文件)
  • 硬件寄存器,程序寄存器
  • 寄存器文件和随机访问存储器都是用时钟信号来控制的

4.3 Y86的顺序实现(SEQ)

这种顺序的额实现需要很长的时钟周期。

4.3.1 奖处理组织成阶段
  1. 取指:根据指令的icode来将指令的长度得出。在这个阶段valP就被设置为PC加上指令长度
  2. 译码:通常是在寄存器文件中读取两个操作数,valA和valB。通常是读指定的两个寄存器文件,但是有时读寄存器%rsp的值,这在指定的rarb是不指定的,但是他很具icode的值来决定读不读%rsp
  3. 执行:主角:ALU算术逻辑单元。计算内存引用的有效地址,或者栈指针的增加减少,或者只是简单的加一个0将一个输入传递到输出。得到valE,同时设置条件码。对于条件传送指令和条件跳转指令的控制。
  4. 访存:读或者写内存中的数据,读出的值是valM。指令和数据内存访问的是相同的内存位置,但是用在不同的目的。
  5. 写回:可以将valE或者valM写回,最多可以写两个结果。寄存器文件有两个写端口,端口E用来写ALU计算出来的值,端口M用来写从数据内存中读出的值
  6. 更新PC:将PC设置下一条指定的地址。地址来源:valP、valC(调用指令或者跳转指令),valM(从内存读取的返回地址)
    (根据icode,cnd,来决定新PC是valc【跳转指令,条件传送】、valM【call】、valP【顺序执行】)

  • 在顺序实现中的 计算
  • 为什么pop指令要让valA和valB都存放栈指针的值?
  1. 使跟其他的指令更相似,增强设计的整体一致性。
  • 在写回阶段要用+8的栈指针更新栈指针寄存器,还要将rA更新为从内存总读出的值。用没加过8的值作为内存读地址,保持了Y86的惯例,popq应该首先读内存,然后再增加栈指针

  • 只有条件跳转指令不用valE。

  • ret和popq都将%rsp存如两个变量

  • irmovq 和rrmovq在执行阶段都会0+valA,要注意那个0

4.3.2 SEQ硬件结构
  • 程序寄存器PC是SEQ中唯一的始终寄存器
  • srcA,valA的源;srcB,valB的源;dstE,写入valE的寄存器;desM,写入valM的寄存器

图中,右边两栏给出的是指令oPq和mrmovq的计算,来说明要计算的值。要将这些计算映射到硬件上,我们要实现控制逻辑,它能在不同硬件单元之间传送数据,以及操作这些单元,使得对每个不同的指令执行指定的运算。这就是控制逻辑块的目标,控制逻辑块在图4-23中用灰色圆角方框表示。我们的任务就是依次经过每个阶段,创建这些块的详细设计。

4.3.3 SEQ的时序

原则:处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。

这个原则保证了硬件的执行获得了像之前的表中赋值顺序执行一样的效果。即使硬件所有的状态更新实际上同时发生。

  • SEQ的实现包括组合逻辑和两种存储器设备(时钟寄存器:程序计数器和条件码寄存器。随机访问存储器:寄存器文件,指令内存和数据内存)
  • 组合逻辑不需要任何的时序或控制------只要输入变化了,值就通过逻辑门网络传播。
  • 对时序进行明确的控制
  1. 指令内存:指令内存是只读的,所以可以将这个单元看成是组合逻辑
  2. 另外四个(程序计数器、条件码寄存器、数据内存和寄存器文件)通过一个时钟信号来控制。这些单元通过一个时钟信号来控制,它触发将新值装载到寄存器以及将值写到随机访问存储器。每个时钟周期,程序计数器都会装载新的指令地址。
    只有在执行整数运算指令时,才会装载条件码寄存器。只有在执行rmmovq、pushq或call指令时,才会写数据内存。

寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器,不过我们可以用特殊的寄存器ID 0xF(为什么用15个寄存器的原因)作为端口地址,来表明在此端口不应该执行写操作。

  • 写操作是由时钟控制的。当时钟上升时,会更新程序计数器,寄存器文件,和条件码寄存器。在这个周期内会执行取出的指令
4.3.4 SEQ阶段的实现

设计实现SEQ所需要的控制逻辑块的HCL描述

  • nop指令只是简单的经过各个阶段,除了要将PC+1,不进行任何处理
  • halt指令使得处理器状态被设置成HLT,导致处理器停止运行
  • 只有popa指令同时用到寄存器文件的两个写端口
  • aluA的值可以是valA、valC、+8、-8
  • 只有OPq指令设置条件码
  • 控制都能用HCL编码编写出来
  • 信号Cnd,用于设置条件传送的dstE,也可以用在条件分支的下一个PC逻辑中。对于其他指令,取决于指令的功能码和条件码的设置,Cnd信号可以被设为1或者0(opQ是否溢出)。

4.4 流水线通用原理

流水线化的重要特性是提高了系统的吞吐量,即单位时间处理的指令条数,不过他也会轻微的增加延迟

4.4.1 计算流水线

G=10的九次方。1ns=1000ps

延迟等于吞吐量的倒数

  • 流水线:增加流水线寄存器,缩减失踪周期,吞吐量提高几倍,延迟只有少量的提升,原因是流水线寄存器的时间开销。这里的吞吐量大约是8.33GIPS
4.4.2 流水线操作的详细说明

流水线阶段之间的转移是由时钟信号控制的

4.4.3 流水线的局限性
  1. 不一致的划分
  1. 流水线过深,收益反而下降

我们将流水线的阶数加倍,但是性能提高了不到二倍

4.4.4 带反馈的流水线系统
  • 数据相关
  • 控制相关
4.5 Y86的流水线实现
4.5.1 SEQ+:重新安排计算阶段
  • 电路重定时:通常用它来平衡一个流水线系统中各个阶段之间的延迟
4.5.2 插入流水线寄存器
4.5.3对信号进行重新排列和标号

m_stat和M_stat的区别

  • m_stat表示的是在访存阶段中由控制逻辑块产生出来的状态信号
  • M_stat表示的是流水线寄存器M的状态码字段
    为什么detE和detM在PIPE-中要一直携带信号穿过执行和访存阶段,直到访存阶段才送到寄存器文件中?
  • 为了确保写端口的地址和数据输入是来自同一条指令。否则,会将处于写回阶段的指令写入,而寄存器ID却来自处于译码阶段的指令。
  • 优点是,减少了要携带给流水线寄存器E和 M的状态数量
    所有的指令中,只有call指令在访存阶段需要valP,只有跳转指令在执行阶段需要valP的值,而这两指令都不需要从寄存器中读数据,
  • 每条指令的状态码和指令一起通过流水线
4.5.4 预测下一个PC

除了条件转移和ret以外,根据取指阶段中计算出的信息,就能确定下一条指令的地址。

  • 分支预测:y86只是简单的预测选择分支,预测的pc为valc
  • 预测错误会极大的降低程序的性能,所以促使我们在可能的时候,使用条件数据传送而不是条件控制转移
  • ret指令不做预测
4.5.5 流水线冒险
  • 数据冒险:书上的例子说明了,如果一条指令的操作数被他前面三条指令中任意一条改变的话,都会出现数据冒险。
  • 程序寄存器
  • 程序计数器
  • 内存
  • 条件码寄存器
  • 状态寄存器
  1. 用暂停来避免数据冒险

气泡就像一个自动产生的nop指令,------他不会改变寄存器、内存、条件码、或程序状态。就是将本来要执行的指令替换成空的,往下执行。

  1. 用转发来避免数据冒险

概括来说就是,不必等到写回阶段将值写回寄存器才能给其他指令用,可以在alu算完,到时钟周期上升沿,存放在流水线寄存器中,然后其他指令要用到相应的值,就直接用数据转发将流水线寄存器中的值交给后面的指令用。这样就不用插入气泡,降低吞吐量了。

  1. 加载/使用数据冒险(不能单纯靠转发来解决)

将暂停和转发结合起来

  1. 避免控制冒险

控制冒险只发生在ret指令和跳转指令(只有在预测错误才会发生麻烦)

  • ret指令解决控制冒险
    取出这条指令之后下一条指令就自动加入气泡,不是

程序员可见状态:条件码寄存器,数据内存,程序寄存器,程序状态,PC

在异常处理这不允许修改条件码寄存器,数据内存。(不用管寄存器文件,因为没有回写),第一条遇到异常的指令一定会到达写回阶段。

  • 如果取出来某条指令,过后又取消了,那么关于所有这条指令的异常状态信息也会被取消,
4.5.7 PIPE各阶段的实现

PIPE是我们使用了转发技术的流水线化的Y86处理器

4.5.8 流水线控制逻辑
  1. 加载/使用冒险
    在一条从内存中读出一个值的指令和要使用该值的指令之间必须暂停一个周期
  2. 处理ret
    流水线必须暂停直到ret指令到达写回阶段
  3. 预知错误分支
    在分支逻辑发现不应该选择选择分支之前,分支目标处的几条指令已经进入流水线了。必须取消这些指令,并从跳转指令后面的那条指令开始取指
  4. 异常
    当一条指令导致异常,我们想要禁止后面的指令更新程序员可见状态,并且在异常只指令到达写回阶段时,停止执行。
相关推荐
wingaso24 分钟前
[经验总结]删除gitlab仓库分支报错:错误:无法推送一些引用到“http:”
linux·数据仓库·git
独行soc28 分钟前
2025年渗透测试面试题总结-阿里云[实习]阿里云安全-安全工程师(题目+回答)
linux·经验分享·安全·阿里云·面试·职场和发展·云计算
勤不了一点39 分钟前
小白上手RPM包制作
linux·运维·服务器·软件工程
麦a~M了M2 小时前
ansible
linux·运维·ansible
QQ_4376643143 小时前
Linux下可执行程序的生成和运行详解(编译链接汇编图解)
linux·运维·c语言·汇编·caffe
窦再兴4 小时前
来一个复古的技术FTP
linux·运维·服务器
xiaobin889994 小时前
【2025最新版】VMware虚拟机下载安装教程 保姆级图文详解(附安装包+常用镜像Linux,win11,ubuntu,centos)
linux·其他·ubuntu·centos
ALex_zry5 小时前
Ubuntu 20.04 C++开发环境搭建指南(2025版)
linux·c++·ubuntu
疯狂的挖掘机5 小时前
记一次从windows连接远程Linux系统来控制设备采集数据方法
linux·运维·windows
sz66cm6 小时前
Linux基础 -- 用户态Generic Netlink库高性能接收与回调框架
linux