Amiya · 2023年07月24日 · 北京市

剖析虚幻渲染体系(19)- 计算机硬件体系(1)

19.1 计算机概述

之前的很多篇文章已经大量涉及了各种各样的硬件和技术,本篇将更加全面、系统、深入地阐述计算机的硬件组成和体系架构,从而形成自上而下的计算机体知识体系。本篇主要阐述以下内容:

  • 计算机基础。
  • 电子电路基础。
  • 计算机硬件。
  • 计算机架构和组织。
  • 计算机硬件运行机制。
  • 计算机硬件的UE封装和实现。

19.1.1 计算机是什么

计算机(Computer)是什么?计算机是一种通用设备,可以编程处理信息,并产生有意义的结果。它是一种被广泛使用且使我们的工作变得轻松的设备,是被动型机器,需要我们输入指令或任务来执行,从而获得我们需要的结果。

image.png

一个基础的计算机。

简单的单处理器计算机如下,提供了传统单处理器计算机内部结构的分层视图。有四个主要结构部件:CPU、主内存、I/O、系统链接。

image.png

如下图所示,向用户提供应用程序时使用的硬件和软件可以以分层或分层的方式查看。这些应用程序的用户,即最终用户,通常不关心计算机的架构,因此终端用户根据应用来查看计算机系统。该应用程序可以用编程语言表示,并由应用程序程序员开发,将应用程序开发为一组完全负责控制计算机硬件的处理器指令将是一项极其复杂的任务。为了简化此任务,提供了一组系统程序。

image.png

其中一些程序被称为实用程序。这些实现了常用的功能,有助于程序创建、文件管理和I/O设备控制。程序员在开发应用程序时使用这些工具,应用程序在运行时调用实用程序来执行某些功能。最重要的系统程序是操作系统,操作系统向程序员隐藏了硬件的细节,并为程序员提供了使用系统的方便界面。它充当中介,使程序员和应用程序更容易访问和使用这些设施和服务。

硬件之中,最重要的部件是芯片,其制造工艺如下图。从硅锭切片后,将空白晶片经过20至40个步骤,以形成图案化晶片。然后用晶片测试仪对这些图案化的晶片进行测试,并绘制出良好零件的地图。然后将晶片切成小片,在该图中,一个晶片生产了20个管芯,其中17个管芯通过了测试(X表示模具不良)。在这种情况下,好模具的产量为17/20,即85%。然后将这些好的模具粘结到包装中,并在将包装好的零件运送给客户之前再次进行测试。最终测试中发现一个包装不良的零件。

image.png

19.1.2 计算机结构

典型的台式计算机有三个主要物理部件——CPU(中央处理器)、主存储器和硬盘。CPU通常也被称为处理器或简单的机器,是计算机的大脑,是计算机的主要部分,将程序作为输入并执行。主存储器用于存储程序在执行过程中可能需要的数据(信息存储),处理器本身的存储空间有限。当电源关闭时,处理器和主存储器会丢失所有数据,但硬盘代表永久存储,程序、数据、照片、视频和文档等数据都存储在硬盘中。

image.png

下图显示了三个组件的简化框图。除了这些主要组件之外,还有一系列与计算机相连的外围组件,例如键盘和鼠标连接到计算机。它们从用户处获取输入,并将其传递给处理器上运行的程序。

image.png

类似地,为了显示程序的输出,处理器通常将输出数据发送到监视器,监视器可以以图形方式显示结果,也可以使用打印机打印结果。最后,计算机可以通过网络连接到其他计算机。所有外围设备的方框图如下所示。

image.png

19.1.3 计算机运行机制

不管底层技术如何,我们需要理解的一个基本概念是,计算机从根本上来说是一台愚蠢的机器。与我们的大脑不同,它没有被赋予抽象的思想、理性和良知。至少在目前,计算机不能自己做出非常复杂的决定,能做的就是执行一个程序。尽管如此,计算机之所以如此强大,是因为它们非常擅长执行程序,每秒可以执行数十亿条基本指令。计算机与人脑的比较如下表所示。

image.png

计算机无法理解人类语言,只能理解二进制数据,也就是0和1、True和False、On和Off,这些状态是通过晶体管(transistor)实现的。晶体管是用于存储2个值(1和0或开和关)的微型设备,如果晶体管打开,它的值为 1,如果它关闭,则值为 0。

例如,一个存储芯片包含数亿甚至数十亿个晶体管,每个晶体管都可以单独打开或关闭。当极少量的电流通过晶体管时,它保持状态1,当没有电流时,晶体管的状态为0。具体示例:

1 : 1
2 : 10
3 : 11
a : 01100001
A : 01000001
U : 01010101 

Hello        : 01001000 01100101 01101100 01101100 01101111 
Hello World! : 01001000 01100101 01101100 01101100 01101111 00100000 01010111 01101111 01110010 01101100 01100100 00100001

问题在于,以上二进制代码对于人类而言,太难以理解了,此时需要各类计算机软件做翻译的桥梁作用。软件是一组指令,告诉计算机要做什么、什么时候做以及如何做。下图显示了操作流程。第一步是用高级语言(C或C++)编写程序,第二步涉及编译它,编译器将高级程序作为输入,并生成包含机器指令的程序,该程序通常称为可执行文件或二进制文件。注意,编译器本身是一个由基本机器指令组成的程序。

image.png

假设要执行2+2的指令,那么我们必须给计算机指令:

  • 第1步:取2个值。
  • 第2步:存储该2值。
  • 第3步:使用 + 运算符对它们进行相加。
  • 第4步:存储结果。

为 + 运算符提供了单独的说明,以便计算机在遇到 + 符号时知道如何进行加法。那么谁来转换这段代码呢?答案是解释器(interpreter),它把我们的语言代码转换成计算机可以理解的机器语言。同理,输入和输出数据也需要依赖特定的软件和解释器。

就像任何语言都有有限的单词一样,处理器可以支持的基本指令/基本命令的数量也必须是有限的,这组指令通常称为指令集(instruction set),基本指令的一些示例是加法、减法、乘法、逻辑或和逻辑非。请注意,每条指令需要处理一组变量和常量,最后将结果保存在变量中。这些变量不是程序员定义的变量,是计算机内的内部位置。

19.1.4 计算机设计准则

计算机设计是组件相互关联的结构。设计者一次处理特定级别的系统,并且在不同级别存在不同类型的问题。在每一层,设计者都关心结构和功能,结构是相互关联的用于通信的各个组件的骨架,功能是系统中涉及的活动。 以下是计算机设计中的问题:

  • 无限速度的假设: 不能假设计算机的无限速度,因为假设无限速度是不切实际的,也给设计者的思维带来了问题。
  • 内存无限的假设: 就像计算机的速度一样,内存也不能假设为无限的,总是有限的。
  • 内存和处理器之间的速度不匹配: 有时内存和处理器的速度可能不匹配,可能是内存速度更快或处理器速度更快。内存和处理器之间的不匹配会导致设计中出现问题。
  • 处理错误和错误: 处理缺陷和错误是任何计算机设计者的巨大责任,缺陷和错误会导致计算机系统出现故障,有时这些错误可能更危险。
  • 多处理器: 设计具有多个处理器的计算机系统会导致管理和编程的巨大任务,是计算机设计中的一大问题。
  • 多线程: 具有多线程的计算机系统总是对设计者构成威胁,具有多个线程的计算机应该能够进行多任务和多处理。
  • 共享内存: 如果一次要执行多个进程,则所有进程共享相同的内存空间。应该以特定的方式对其进行管理,以免发生冲突。
  • 磁盘访问: 磁盘管理是计算机设计的关键,磁盘访问存在几个问题,系统可能不支持多磁盘访问。
  • 更好的性能: 始终是一个问题,设计者总是试图简化系统以获得更好的性能来降低功耗和降低成本。

19.1.5 实用机器架构

下面阐述一下不同类型的实用机器的设计及架构。

  • 哈佛架构

哈佛体系架构下图所示,有单独的结构来维护指令表和内存。指令表也被称为指令存储器,因为可以把它看作是专门为只保存指令而设计的专用存储器。内存保存程序所需的数据值,因此被称为数据存储器。处理指令的引擎分为两部分:控制和ALU,控制单元的工作是获取指令、处理指令并协调指令的执行。ALU代表算术逻辑单元,有专门的电路,可以计算算术表达式或逻辑表达式(and/or/NOT等)。

image.png

请注意,每台计算机都需要从用户/编程器处获取输入,并最终将结果传回编程器,可通过多种方法实现,例如我们如今使用的键盘和显示器。早期的计算机使用一组开关,最终结果打印在一张纸上。

  • 冯诺依曼架构

约翰·冯·诺依曼提出了通用图灵完全计算机的冯·诺伊曼体系结构,实际上,Eckert和Mauchly于1946年基于该架构设计了第一台通用图灵完全计算机(有个小限制),称为ENIAC(电子数字积分器和计算器),该计算机用于计算美国陆军弹道研究实验室的火炮环表,后来在1949年被EDVAC计算机取代,该计算机也被美国陆军的弹道研究实验室使用。

作为ENIAC和EDVAC基础的基本冯·诺依曼架构如下图所示。指令表保存在内存中,图灵机的处理引擎被称为CPU(中央处理单元),包含程序计数器,其工作是获取新指令并执行它们。它有专用的功能单元来计算算术函数的结果,在内存位置加载和存储值,以及计算分支指令的结果。最后,与哈佛体系结构一样,CPU连接到I/O子系统。

image.png

这台机器的创新之处在于指令表存储在内存中,使用通常存储在内存中的同一组符号对每条指令进行编码。例如,如果内存存储十进制值,则每条指令都需要编码为十进制数字串。冯·诺依曼CPU需要解码每条指令,这个想法的核心是,指令被视为常规数据(内存值)。这个简单的想法实际上是设计优雅计算系统的一个非常强大的工具,被称为存储程序概念。

存储程序概念(stored program concept):程序存储在内存中,指令被视为常规内存值。

存储程序概念极大地简化了计算机的设计。由于内存数据和指令在概念上是以相同的方式处理的,所以我们可以有一个统一的处理系统和一个以相同方式处理指令和数据的内存系统。从CPU的角度来看,程序计数器指向一个通用内存位置,其内容将被解释为编码指令的内容,很容易存储、修改和传输程序,程序还可以在运行时通过修改自身甚至其他程序来动态更改其行为。这构成了当今复杂编译器的基础,这些编译器将高级C程序转换为机器指令。此外,许多现代系统(如Java虚拟机)动态地修改它们的指令以实现效率。

冯·诺伊曼机器或哈佛机器不像图灵机器那样拥有无限的内存,严格地说,它们并不完全等同于图灵机,对于所有实用的机器都是如此,它们需要足够的资源。然而,科学界已经学会接受这种近似。

19.2 计算机硬件基础

19.2.1 汇编语言

汇编语言可以广泛地定义为机器指令的文本表示。在构建处理器之前,我们需要了解不同机器指令的语义,在这方面,对汇编语言的严格研究将是有益的。汇编语言专用于ISA和编译器框架,因此,汇编语言有许多优点。本节将描述不同汇编语言变体的基本原理,一些通用概念和术语。随后,将描述针对基于ARM的处理器的ARM汇编语言,描述针对Intel/AMD处理器的x86汇编语言。

19.2.1.1 为什么用汇编语言?

先从软件开发者的视角阐述之。

人类懂得自然语言,如中文、英语和西班牙语。通过一些额外的训练,人类还可以理解计算机编程语言,如C或Java。然而,如前面所述,计算机是一台愚蠢的机器,不够聪明,无法理解人类语言(如英语)或编程语言(如C)中的命令,它只能理解零和一。因此,要给计算机编程,必须给它一个0和1的序列。事实上,一些早期的程序员曾经通过打开或关闭一组开关来编程计算机,打开一个开关对应于1,打开它意味着0。对于今天的大规模数百万行程序来说,不是一个可行的解决方案,需要另寻更好的方法。

因此,我们需要一个自动转换器,它可以将用C或Java等高级语言编写的程序转换为一系列0和1,称为机器代码(machine code)。机器代码包含一组称为机器指令的指令,每个机器指令都是由零和一组成的序列,并指示处理器执行特定的操作。可以将用高级语言编写的程序转换为机器代码的程序称为编译器(见下图)。

image.png
编译过程。

请注意,编译器是一个可执行程序,通常在应该为其生成机器代码的机器上运行。可能出现的一个自然问题是——谁编写了第一个编译器?

第一,鉴于编译器的普遍存在,几乎所有的程序都是用高级语言编写的,编译器用来将它们转换为机器代码,但这一规则也有重要的例外。请注意,编译器的作用有两方面:首先,它需要正确地将高级语言的程序翻译成机器指令;其次,它需要生成不占用大量空间且速度快的高效机器代码。因此,多年来编译器中的算法变得越来越复杂,但并不总是能够满足这些要求,例如在某些情况下,编译器可能无法生成足够快的代码,或者无法提供程序员期望的某种功能:

  • 首先,编译器中的算法受到它们对程序执行的分析量的限制。例如,我们不希望编译过程极其缓慢,编译器领域的许多问题在计算上很难解决,因此很耗时。
  • 其次,编译器不知道代码中的广泛模式。例如,某个变量可能只取一组有限的值,在此基础上,可能进一步优化机器代码,编译器很难理解这一点。聪明的程序员有时可以生成比编译器更优化的机器代码,因为他们知道一些广泛的执行模式,可以胜过编译器。
  • 处理器供应商也可能在ISA中添加新指令。在这种情况下,用于旧版本处理器的编译器可能无法利用新指令,需要在程序中手动添加它们。流行的编译器(如gcc,GNU编译器集合)是相当通用的,它们不使用处理器在生成机器代码时提供的所有可能的机器指令。通常,操作系统和设备驱动程序(与打印机和扫描仪等设备接口的程序)需要大量遗漏的指令,因为它们需要对硬件的低级别访问,因此系统程序员有强烈的动机偶尔绕过编译器。

在所有这些情况下,程序员都有必要在程序中手动嵌入一系列机器指令。如上所述,这样做的两个主要原因是效率和额外的功能。因此,从系统软件开发人员的角度来看,有必要了解机器指令,以便他们在工作中更有效率。

现在,我们的目标是让现代程序员远离0和1的复杂细节。理想情况下,我们不希望程序员像50年前那样通过手动打开和关闭开关来编程,由此开发了一种称为汇编语言的低级语言。汇编语言是机器代码的一种人类可读形式,每个汇编语言语句通常对应于一条机器指令。此外,它通过不强迫程序员记住编码指令所需的0/1的确切序列,大大减轻了程序员的负担。

  • 低级编程语言(low level programming language)使用通常只对应于一条机器指令的简单语句,这些语言是ISA特有的。
  • 汇编语言(assembly language)是指一系列特定于每个ISA的低级编程语言,具有由一系列汇编语句组成的泛型结构。通常,每个汇编语句有两部分:
  • 一个指令代码,是基本机器指令的助记符。
  • 一个操作数列表。

从实际角度来看,可以编写独立的汇编程序,并使用称为汇编器的程序将其转换为可执行程序,也可以在高级语言(如C或C++)中嵌入汇编代码片段,后者更为常见。

汇编器(assembler)是将汇编程序转换为机器代码的可执行程序。

编译器确保能够将组合程序编译为机器代码。汇编语言的好处是多方面的:

  • 可读性。因为汇编代码中的每一行对应于一条机器指令,所以它和机器代码一样具有表达力。
  • 高效性。由于这种一对一的映射,我们不必通过在汇编中编写程序来提高效率。
  • 简化性。它是一种可读的、优雅的文本表示机器代码的形式,大大简化了使用它编写程序的过程,也可以在用C等高级语言编写的软件中清晰地嵌入汇编代码片段,它定义了一个高于实际机器代码的抽象级别。两个处理器可能与同一种汇编语言兼容,但实际上对同一条指令有不同的机器编码。在这种情况下,汇编程序将在这两个处理器上兼容。

再从硬件设计者的视角阐述之。

硬件设计师的职责是设计能够实现ISA中所有指令的处理器。他们的主要目标是设计一个在面积、功率效率和设计复杂性方面最佳的高效处理器,从他们的角度来看,ISA是软件和硬件之间的关键纽带。这回答了他们的基本问题——构建什么?因此,对他们来说,理解不同指令集的精确语义是非常重要的,这样他们就可以为它们设计处理器。将指令仅仅看作一个0和1的序列是很麻烦的,通过查看机器指令的文本表示,他们可以获得很多好处,很清晰地知道是一条怎样的汇编指令。

汇编语言专用于指令集和汇编器。本节使用流行的GNU汇编器的汇编语言格式来解释典型汇编语言文件的语法,请注意,其他系统具有类似的格式,并且概念大致相同。

** 19.2.1.2 汇编语言基础

机器模型

汇编语言不将指令存储器和数据内存视为不同的实体,假设一个抽象的冯·诺依曼机器增加了寄存器。

有关机器模型的图示,请参见下图。程序存储在主内存的一部分中,中央处理单元(CPU)逐条指令读出程序指令,并适当地执行指令,程序计数器(PC)跟踪CPU正在执行的指令的内存地址,大多数指令都希望从寄存器中获取其输入操作数。回想一下,每个CPU都有固定数量的寄存器(通常<64),然而大量指令也可以直接从内存中获取操作数。CPU的工作是协调主存和寄存器之间的传输,CPU还需要执行所有算术/逻辑计算,并与外部输入/输出设备保持联系。

image.png

大多数类型的汇编语言在大多数语句中都采用这种抽象机器模型。但由于使用汇编语言的另一个目的是对硬件进行更细粒度和侵入性的控制,因此有相当多的汇编指令可以识别处理器的内部。

这些指令通常通过改变一些关键内部算法的行为来修改处理器的行为,它们修改内置参数,如电源管理设置,或读/写一些内部数据。最后请注意,汇编语言不区分机器无关指令和机器相关指令。

每台机器都有一组寄存器,这些寄存器对汇编程序员是可见的。ARM有16个寄存器,x86(32位)有8个寄存器,而x86_64(64位)有16个。寄存器有名称,ARM将它们命名为r0、r1、...、r14、r15,x86将它们命名成eax、ebx、ecx、edx、esi、edi、ebp和esp,可以使用这些名称访问寄存器。

在大多数ISA中,返回地址寄存器用于函数调用。让我们假设一个程序开始执行一个函数,它需要记住执行函数后需要返回的内存地址,此地址称为返回地址。在跳转到函数的起始地址之前,我们可以将返回地址的值保存在这个寄存器中。通过将保存在返回地址寄存器中的值复制到PC上,可以简单地实现返回语句。在ARM和MIPS等汇编语言中,程序员可以看到返回地址寄存器,然而x86不使用返回地址寄存器,使用堆栈。

在ARM处理器中,程序员可以看到PC,它是最后一个寄存器(r15)。可以读取PC的值,也可以设置其值,设置PC的值意味着我们希望分支到程序中的新位置。然而x86的PC是隐式的,程序员不可见。

内存视图

内存可以看作是一个大的字节数组,每个字节都有一个唯一的地址,基本上就是它在数组中的位置。第一字节的地址是0,第二字节的地址为1,以此类推。我们没有一种方法来唯一地寻址给定的位,地址在32位机器中是32位无符号整数,在64位机器中则是64位无符号。

在冯·诺依曼机器中,我们假设程序作为字节序列存储在内存中,程序计数器指向将要执行的下一条指令。

假设内存是一个大的字节数组,如果我们所有的数据项都只有一个字节长,那么就可以了,像C和Java这样的语言有不同大小的数据类型:char(1字节)、short(2字节)、integer(4字节)和long integer(8字节)。对于多字节数据类型,必须在内存中为其创建一个表示,在内存中表示多字节数据类型有两种可能的方式——小端和大端。其次,我们还需要找到表示内存中数据数组或列表的方法。

  • 小端和大端表示

让我们考虑在位置0-3存储整数的问题。让整数为0x87654321,它可以分为四个字节:87、65、43和21。一个选项是将最重要的字节87存储在最低的内存地址0中,下一个位置可以存储65、43、21,这被称为大端(big endian)表示,因为我们从最大字节的位置开始。相比之下,我们可以先将最小的字节保存在位置0,然后继续将最大的字节存储在位置3,这种表示方式称为小端(big endian)。下图显示了差异。

image.png
大端和小端表示。

因此,没有理由选择一种代表而不是另一种代表,例如,x86处理器使用little-endian格式。早期版本的ARM处理器曾经是小端的,然而,现在它们是双端的,意味着ARM处理器可以根据用户设置同时作为小端和大端机器工作。传统上,IBM POWER处理器和Sun SPARC处理器都是大端的。

  • 表示数组

数组是一组线性有序的对象,其中对象可以是简单的数据类型(如整数或字符),也可以是复杂的数据类型。

int a[100];
char c[100];

让我们考虑一个简单的整数数组a。如果数组有100个条目,那么内存中数组的总大小等于100 4=400字节。如果数组的起始内存位置为loc,然后将[0]存储在位置(loc + 0)、(loc + 1)、(loc + 2)、(loc + 3)中。请注意,有大端和小端两种方法保存数据。下一个数组条目a[1]保存在位置(loc + 4) ... (loc + 7)中,依此类推,我们注意到条目a[i]保存在(loc + 4 x i) ... (loc + 4 x i + 3)中。

大多数编程语言都定义以下形式的多维数组:

int a[100][100];
char c[100][100];

它们通常在内存中表示为规则的一维数组,多维数组中的位置与等效的一维数组之间存在映射函数,我们可以扩展该方案以考虑维度大于2的多维数组。

我们观察到,通过以行优先(row major)方式保存二维数组,可以将其保存为一维数组,意味着数据按行保存。我们保存第一行,然后保存第二行,以此类推。同样,也可以以列优先(column major)的方式保存多维数组,其中保存第一列,然后再保存第二列,依此类推。

行优先(row major):数组按行保存在内存中。 列优先(column major):数组按列保存在内存中。

19.2.1.3 汇编语言语法

汇编文件的确切语法取决于汇编程序,不同的汇编程序可以使用不同的语法,尽管它们可能在基本指令及其操作数格式上达成一致。本节将解释GNU系列汇编语言的语法,它们是为GNU汇编程序设计的,是GNU编译器集合(gcc)的一部分。与所有GNU软件一样,该汇编程序和相关编译器可免费用于大多数平台,汇编程序可在gnu.org 上找到。请注意,其他汇编程序(如NASM、MASM)都有自己的格式,但总体结构在概念上与本节描述的没有太大区别。

汇编文件结构

程序集文件是一个常规文本文件,后缀是.s。如果安装了GNU编译器gcc,则可以通过发出以下命令快速生成C程序的汇编文件,当然也可以使用在线GCC

gcc -S test.c

生成的程序集文件将命名为test.s。GNU程序集的结构非常简单,如下图所示。不同部分的示例包括文本(实际程序)、数据(具有初始化值的数据)和bss(初始化为0的通用数据)。每个段(section)以节标题开头,这是以“.”开头的节的名称符号,例如,文本部分以“.text”行开头。接着是汇编语言语句列表,每条语句通常以换行符结尾,同样,数据部分包含数据值列表。

汇编文件以包含格式为“.file <文件名>”的行的文件段开头。当我们使用gcc编译器从C程序生成程序集文件时,.file部分中的文件名通常与我们的原始C程序(test.C)相同。文本段是必填的,其余段是可选的,可能有一个或多个数据段,也可以使用.section指令定义新的节。本节我们主要关注文本部分,因为对学习指令集的本质感兴趣。

image.png
汇编语言文件结构。

汇编基本语句

一条基本的汇编语言语句指定了一条汇编指令,有两部分:指令及其操作数列表,如下图所示。该指令是实际机器指令的文本标识符,操作数列表包含每个操作数的值或位置。操作数的值是一个数值常量,也被称为立即值(immediate value),操作数位置可以是寄存器位置或内存位置。

image.png
汇编语言语句。

在计算机架构中,指令中指定的常数值也称为立即数(immediate)

假设有以下语句:

add r3, r1, r2

在这个ARM汇编语句中,add指令指定了我们希望将两个数字相加并将结果保存在某个预先指定的位置的事实。在这种情况下,加法指令的格式如下:

<指令><目标寄存器><操作数寄存器1><操作数寄存器2>

。指令的名称为add,目标寄存器为r3,操作数寄存器为r1和r2。指令的详细步骤如下:

1.读取寄存器r1的值。让我们将该值称为v1。

2.读取寄存器r2的值。让我们将该值称为v2。

3.计算v3=v1+v2。

4.将v3保存在寄存器r3中。

现在让我们再举一个以类似方式工作的两条指令的示例:

sub r3, r1, r2
mul r3, r1, 3

sub指令减去存储在寄存器中的两个数,mul指令将存储在寄存器r1中的一个数乘以数值常数3,这两条指令都将结果保存在寄存器r3中,它们的操作模式与加法指令类似。此外,算术指令(如add、sub和mul)也称为数据处理指令。还有其他几类指令,例如从内存加载或存储值的数据传输指令,以及实现分支的控制指令。

通用语句结构

汇编语句的一般结构如下图所示,它由三个字段组成:标签(指令的标识符)、键(汇编指令或汇编程序指令)和注释。这三个字段都是可选的,但是,任何汇编语句都需要至少具有其中一个。

image.png
汇编语句的通用结构。

语句可以选择以标签开头,标签是语句的文本标识符,换句话说,标签在汇编中唯一地标识汇编语句。请注意,我们不允许在同一汇编文件中重复标签,标签在执行分支指令时非常有用。

下面的示例代码中显示了一个标签的示例,这里标签的名称是“label1”,后面是冒号。在标签之后,我们编写了一条汇编指令,并给它一个操作数列表。标签可以由有效的字母数字字符[a-z] [A-Z] [0-9] 以及符号“.”、“_”和“$”。通常,我们不能以数字作为标签的开头。在指定标签之后,我们可以将该行保持为空,也可以指定键(汇编语句的一部分)。如果键以“.”开头,那么它是一个汇编程序指令,对所有计算机都有效,它指示汇编程序执行某个操作,此操作可以包括启动新节或声明常量。该指令还可以采用参数列表,如果键以字母开头,则它是一条常规的汇编指令。

label1: add r1, r2, r3

在标签、汇编指令和操作数列表之后,可以选择插入注释。GNU汇编程序支持两种类型的注释,我们可以在插入类似C或Java风格的注释。在ARM汇编中,通过在注释前面加上“@”字符,也可以有一个小的单行注释。

label1: add r1, r2, r3 @ Add the values in r2 and r3
label2: add r3, r4, r5 @ Add the values in r4 and r5
add r5, r6, r7 /* Add the values in r6 and r7 */

汇编语句可能只包含标签,而不包含键。在这种情况下,标签本质上指向一个空语句,不是很有用。因此,汇编程序假定在这种情况下,标签指向最近的包含键的后续汇编语句。

19.2.1.4 指令类型

按功能分类

按功能,可分为四种主要类型,说明如下:

  • 数据处理指令:数据处理指令通常是算术指令,如加法、减法和乘法,或按位或、异或计算的逻辑指令,比较指令也属于这个类型。
  • 数据传输指令:这些指令在两个位置之间传输值,位置可以是寄存器或内存地址。
  • 分支指令:分支指令帮助处理器的控制单元根据操作数的值跳转到程序的不同部分,在实现for循环和if-then-else语句时很有用。
  • 异常生成指令:这些专用指令有助于将控制权从用户级程序转移到操作系统。

我们将介绍数据处理、数据传输和控制指令。

按操作数分类

GNU汇编程序中的所有汇编语言语句都具有相同的结构,它们以指令的名称开头,后面是操作数列表。我们可以根据指令所需的操作数对其进行分类,如果一条指令需要n个操作数,那么通常称它是n地址格式,例如,不需要任何操作数的指令是0地址格式指令,如果它需要3个操作数,则它是3地址格式指令。

如果一条指令需要n个操作数(包括源和目标),那么我们称其为n地址格式指令。

在ARM中,大多数数据处理指令采用3地址格式,数据传输指令采用2地址格式。然而,在x86中,大多数指令都是2地址格式。我们想到的第一个问题是,3地址格式指令与2地址格式指令的逻辑是什么?这里一定有一些权衡。

让我们阐述一些一般的经验法则。如果一条指令有更多的操作数,那么它将需要更多的位来表示该指令,因此需要更多的资源来存储和处理指令。然而,这一论点有另一面,拥有更多的操作数也会使指令更加通用和灵活,将使编译器编写者和汇编程序员的生活变得更加轻松,因为使用更多操作数的指令可以做更多的事情。反向逻辑适用于占用较少操作数的指令,占用更少的存储空间,也不那么灵活。

让我们考虑一个例子。假设我们试图将两个数字3和5相加,得到结果8。用于添加的ARM指令如下所示:

add r3, r1, r2

此指令将寄存器r1(3)和r2(5)的内容相加,并将其保存在r3(8)中。然而,x86指令如下所示:

add edx, eax

此处假设edx包含3,eax包含5,执行加法,结果8存储回edx。因此,在这种情况下,x86指令采用2地址格式,因为目标寄存器与第一源寄存器相同。

19.2.1.5 操作数类型

现在让我们看看不同类型的操作数,在汇编语句中指定和访问操作数的方法称为**寻址模式(addressing mode)。

在汇编语句中指定和访问操作数的方法称为寻址模式(addressing mode)。
指定操作数的最简单方法是将其值嵌入指令中,大多数汇编语言允许用户将整数常量的值指定为操作数,这种寻址模式被称为立即寻址模式(immediate addressing mode),此方法对于初始化寄存器或内存位置或执行算术运算非常有用。

一旦必需的常数集被加载到寄存器和内存位置,程序就需要通过对寄存器和内存进行操作来继续,这个空间有几种寻址模式。在介绍它们之前,让我们以寄存器转移符号(register transfer notation)的形式介绍一些额外的术语。

寄存器转移符号

image.png

操作数的通用寻址模式

image.png

让我们通过考虑基偏移寻址模式来引入一个称为有效内存地址(effective memory address)的新术语。内存地址等于基址寄存器的内容加上偏移量,计算出的内存地址称为有效内存地址。在内存操作数的情况下,我们可以类似地为其他寻址模式定义有效地址。

image.png
多种寻址模式。

image.png
x86寻址模式计算。

19.2.1.6 常见指令说明

本小节将以ARM为基准,阐述常见的RISC指令用法。

数据传输指令

数据传输指令包含以下几类:

  • LDR和STR

可加载寄存器和存储寄存器,包含32位字、8位无符号字节、半字、无符号字节、双字等。

LDR和STR都有四种可能的形式:零偏移量、预索引偏移、程序相关、后索引偏移。四种形式的语法顺序相同,分别为:

op{cond}{B}{T} Rd, [Rn]
op{cond}{B} Rd, [Rn, FlexOffset]{!}
op{cond}{B} Rd, label
op{cond}{B}{T} Rd, [Rn], FlexOffset

以上是针对32位字或8位无符号字节,如果需要双字则B改成D:

op{cond}D Rd, [Rn]
op{cond}D Rd, [Rn, Offset]{!}
op{cond}D Rd, label
op{cond}D Rd, [Rn], Offset

半字、有符号字节语法如下:

op{cond}        type Rd, [Rn]
op{cond}        type Rd, [Rn, Offset]{!}
op{cond}        type Rd, label
op{cond}        type Rd, [Rn], Offset

示例:

; 示例1
SUB R1, PC, #4 ; R1 = address of following STR instruction
STR PC, [R0]   ; Store address of STR instruction + offset,
LDR R0, [R0]   ; then reload it
SUB R0, R0, R1 ; Calculate the offset as the difference

; 示例2
LDRD    r6,[r11]
LDRMID  r4,[r7],r2
STRD    r4,[r9,#24]
STRD    r0,[r9,-r2]!
LDREQD  r8,abc4

; 示例3
LDREQSH r11,[r6]        ; (conditionally) loads r11 with a 16-bit halfword from the address in r6. Sign extends to 32 bits.
LDRH    r1,[r0,#22]     ; load r1 with a 16 bit halfword from 22 bytes above the address in r0. Zero extend to 32 bits.
STRH    r4,[r0,r1]!     ; store the least significant halfword from r4 to two bytes at an address equal to contents(r0) plus contents(r1). Write address back into r0.
LDRSB   r6,constf       ; load a byte located at label constf. Sign extend.
LDM和STM
LDM、STM加载和存储多个寄存器,寄存器r0到r15的任何组合都可以被传送。语法如下:

op{cond}mode Rn{!}, reglist{^}
示例:

LDMIA   r8,{r0,r2,r9}
STMDB   r1!,{r3-r6,r11,r12}
STMFD   r13!,{r0,r4-r7,LR}  ; Push registers including the stack pointer
LDMFD   r13!,{r0,r4-r7,PC}  ; Pop the same registers and return from subroutine
  • PLD

PLD缓存预加载。语法:

PLD [Rn{, FlexOffset}]

示例:

PLD [r2]
PLD [r15,#280]
PLD [r9,#-2481]
PLD [r0,#av*4]  ; av * 4 must evaluate, at assembly time, to an integer in the range -4095 to +4095
PLD [r0,r2]
PLD [r5,r8,LSL 2]
  • SWP

在寄存器和存储器之间交换数据,使用SWP实现信号量。语法:

SWP{cond}{B} Rd, Rm, [Rn]

通用数据处理指令

此类指令又包含以下几种:

  • 灵活第二操作数

大多数ARM通用数据处理指令都有一个灵活的第二操作数,在每条指令的语法描述中显示为Operand2。语法有两种形式:

#immed_8r
Rm{, shift}

示例:

ADD     r3,r7,#1020         ; immed_8r. 1020 is 0xFF rotated right by 30 bits.
AND     r0,r5,r2            ; r2 contains the data for Operand2.
SUB     r11,r12,r3,ASR #5   ; Operand2 is the contents of r3 divided by 32.
MOVS    r4,r4, LSR #32      ; Updates the C flag to r4 bit 31. Clears r4 to 0.
ADD、SUB、RSB、ADC、SBC和RSC

此类指令的语法如下:

op{cond}{S} Rd, Rn, Operand2

示例:

ADD     r2,r1,r3
SUBS    r8,r6,#240      ; sets the flags on the result
RSB     r4,r4,#1280     ; subtracts contents of r4 from 1280
ADCHI   r11,r0,r3       ; only executed if C flag set and Z flag clear
RSCLES  r0,r5,r0,LSL r4 ; conditional, flags set
  • AND、ORR、EOR和BIC

逻辑操作,语法如下:

op{cond}{S} Rd, Rn, Operand2

示例:

AND     r9,r2,#0xFF00
ORREQ   r2,r0,r5
EORS    r0,r0,r3,ROR r6
BICNES  r8,r10,r0,RRX
  • MOV、MVN、CMP、CMN、TST、TEQ、CLZ

移动、对比、测试、计数前导零指令,语法如下:

MOV{cond}{S} Rd, Operand2
MVN{cond}{S} Rd, Operand2

CMP{cond} Rn, Operand2
CMN{cond} Rn, Operand2

TST{cond} Rn, Operand2
TEQ{cond} Rn, Operand2

CLZ{cond} Rd, Rm

示例:

MOV     r5,r2
MVNNE   r11,#0xF000000B
MOVS    r0,r0,ASR r3

CMP     r2,r9
CMN     r0,#6400
CMPGT   r13,r7,LSL #2

TST     r0,#0x3F8
TEQEQ   r10,r9
TSTNE   r1,r5,ASR r1

CLZ     r4,r9
CLZNE   r2,r3

算术指令

算术指令包含大量的乘法指令,乘法的指令较多较复杂。常见算术指令如下表所示:

image.png

示例:

MUL     r10,r2,r5
MLA     r10,r2,r1,r5
MULS    r0,r2,r2
MULLT   r2,r3,r2
MLAVCS  r8,r6,r3,r8

UMULL       r0,r4,r5,r6
UMLALS      r4,r5,r3,r8
SMLALLES    r8,r9,r7,r6
SMULLNE     r0,r1,r9,r0 ; Rs can be the same as other registers

SMLAWB      r2,r4,r7,r1
SMLAWTVS    r0,r0,r9,r2

MIA     acc0,r5,r0
MIALE   acc0,r1,r9
MIAPH   acc0,r0,r7
MIAPHNE acc0,r11,r10
MIABB   acc0,r8,r9
MIABT   acc0,r8,r8
MIATB   acc0,r5,r3
MIATT   acc0,r0,r6
MIABTGT acc0,r2,r5

分支指令

分支语句的描述如下表:

image.png

示例:

B       loopA
BLE     ng+8
BL      subC
BLLT    rtX

BX      r7
BXVS    r0

BLX     r2
BLXNE   r0
BLX     thumbsub

条件执行

几乎所有ARM指令都可以包含可选条件代码。这在语法描述中显示为{cond}。只有当CPSR中的条件代码标志满足指定条件时,才执行带有条件代码的指令。可以使用的条件代码如下表所示(部分)。

后缀    标记    含义
EQ    Z设置    =
NE    Z清除    !=
CS、HS    C设置    >=(无符号)
CC、LO    C清除    =(无符号)
MI    N设置    负数
PL    N清除    非负数
VS    V设置    溢位
VC    V清除    无溢位
HI    C设置且Z清除    >(无符号)
LS    C清除或Z设置    <=(无符号)
GE    N和V一样    >=(有符号)
LT    N和V不一样    <(有符号)
GT    Z清除且N和V一样    >(有符号)
LE    Z设置或N和V不一样    <=(有符号)
AL    任意    总是(通常省略)

几乎所有ARM数据处理指令都可以根据结果选择性地更新条件代码标志。要使指令更新标志,请包含S后缀,如指令的语法描述所示。 有些指令(CMP、CMN、TST和TEQ)不需要S后缀,它们的唯一功能是更新标志,总是更新标志。

标志将保留到更新,未执行的条件指令对标志没有影响,一些指令更新标志的子集,其他标志不受这些指令的影响。详细信息在说明说明中指定。可以根据另一条指令中设置的标志,有条件地执行指令,或者:

  • 紧接在更新标志的指令之后。
  • 在没有更新标志的任何数量的介入指令之后。

示例:

ADD     r0, r1, r2    ; r0 = r1 + r2, don't update flags
ADDS    r0, r1, r2    ; r0 = r1 + r2, and update flags
ADDCSS  r0, r1, r2    ; If C flag set then r0 = r1 + r2, and update flags
CMP     r0, r1        ; update flags based on r0-r1.

其它指令

ARM还有协调处理器、伪指令、杂项指令等其它指令,本文限于篇幅就不接受了。更多详情参见:
ARM:Introduction to Assembly Language
ARM Instruction Reference
ARM and Thumb instruction summary

19.2.2 二进制位

计算机不像人类那样理解单词或句子,只理解0和1的序列,存储、检索和处理数十亿个0和1非常容易。其次,使用硅晶体管(silicon transistor)实现计算机的现有技术与处理0和1的概念非常兼容。基本硅晶体管是一种开关,可以根据输入将输出设置为逻辑0或1,硅晶体管是我们今天拥有的所有电子计算机的基础,从手机的处理器到超级计算机的处理器。十九世纪末制造的一些早期计算机处理十进制数字,本质上大多是机械的。首先让我们明确定义一些简单的术语:

  • 位(bit):可以有两个值的变量:0或1。
  • 字节(byte):8个位的序列。

19.2.2.1 逻辑操作

二进制变量(0或1)最早由乔治·布尔(George Boole)于1854年描述,他使用这些变量及其相关运算来描述数学意义上的逻辑,他设计了一个完整的代数,由简单的二元变量、一组新的运算符和基本运算组成。为了纪念乔治·布尔,二进制变量也称为布尔变量(Boolean variable),布尔变量的代数系统称为布尔代数(Boolean algebra)。逻辑位操作如下:

  • NOT

逻辑补码(logical complement)称为NOT运算符,任何布尔运算符都可以通过真值表来表示,真值表列出了所有可能的输入组合的运算符输出,NOT运算符的真值表如下表所示。

image.png

  • OR

OR运算符表示任一操作数等于1的事实。例如,如果A=1或B=1,则A或B等于1。OR运算符的真值表如下所示。

image.png

  • AND

AND运算符的操作是所有操作数为1,则结果才为1,其它则为0。例如,当A和B都为1时,A和B等于1。AND运算符的真值表如下所示。

image.png

  • NAND、NOR

另外的两个简单的运算符,即NAND和NOR非常有用。NAND是AND的逻辑补码,NOR是OR的逻辑补。它们的真值表如下所示。

image.png

NAND和NOR是非常重要的运算符,因为它们被称为通用运算符,我们可以只使用它们来构造任何其他运算符。

  • XOR

XOR是异或运算符,当A和B相等时,值为0,否则为1。真值表如下所示。

image.png

19.2.2.2 布尔代数

image.png
image.png
image.png

19.2.3 晶体管

硅是周期表中的第14种元素,有四个价电子,虽然与碳和锗属于同一组,但其化学反应性不如后两者。

90%以上的地壳由硅基矿物组成,二氧化硅是沙子和石英的主要成分,它供应充足,而且制造起来相当便宜。硅具有一些有趣的特性,使其成为设计电路和处理器的理想衬底。让我们考虑一下硅的分子结构,它有一个致密的结构,每个硅原子都与其他四个硅原子相连,紧密相连的一组硅原子结合在一起形成一个强晶格,其他材料(尤其是金刚石)具有类似的晶体结构。因此,硅原子比大多数金属更紧密。

由于缺乏自由电子,硅没有很好的导电性能,介于良导体和绝缘体之间,因此被称为半导体(semiconductor)。通过可控的方式添加一些杂质,可以稍微改变其性质,这个过程被称为掺杂(doping)

19.2.3.1 掺杂

通常,向硅中添加两种杂质以改变其特性:n型和p型。n型杂质通常由周期表中的V族元素组成,磷是最常见的n型掺杂剂,偶尔也会使用砷。添加具有价电子的V族掺杂剂的效果是,额外的电子从晶格中分离出来,并可用于传导电流。这种掺杂过程有效地提高了硅的导电性。

同样,可以向硅中添加III族元素,如硼或镓,以产生p型掺杂硅,会产生相反的效果,会在晶格中创建一个空隙,此空隙也称为孔(hole),孔表示没有电子。像电子一样,孔可以自由移动,也有助于传导电流。电子带负电荷,孔在概念上与正电荷相关。

现在我们已经制作了两种半导体材料:n型和p型,下面看看如果连接它们形成p-n结会发生什么。

19.2.3.2 P-N结

让我们考虑一个p-n结,如下图所示。p型区有过量的孔,n型区有过剩的电子。在结处,一些孔交叉并移动到n区,因为它们被电子吸引。类似地,一些电子越过并聚集在p区一侧。孔和电子的这种迁移称为扩散,见证这种迁移的交界处周围的区域被称为耗尽区。然而,由于电子和孔的迁移,在耗尽区中产生了与迁移方向相反的电场,这个电场感应出一种称为漂移电流的电流。在稳态下,漂移电流和扩散电流相互平衡,因此实际上没有电流流过结。

image.png

如果将p侧连接到正端子,将n侧连接到负端子,则这种配置称为正向偏置。在这种情况下,孔从结的p侧流向n侧,电子则反向流动。因此,该结传导电流。

如果我们将p侧连接到负端子,将n侧连接到正端子,则这种配置称为反向偏置。在这种情况下,孔和电子被拉离结。因此,没有电流流过结,并且在这种情况下p-n结不导电。所描述的简单p-n结被称为二极管(diode),它只在一个方向传导电流,即当它处于正向偏置时。

二极管(diode)是一种典型地由单个p-n结制成的电子器件,其仅在一个方向上传导电流。

19.2.3.3 NMOS晶体管

现在,让我们将两个p-n结相互连接,如下图(a)所示,这种结构被称为NMOS(负金属氧化物半导体)晶体管。在这张图中,有一个p型掺杂硅的中心衬底。两侧有两个小区域含有n型掺杂硅,这些区域分别被称为漏极和源极。注意,由于结构是完全对称的,这两个区域中的任何一个都可以被指定为源极或漏极,源极和漏极中间的区域称为通道。在沟道的顶部有一个通常由二氧化硅(SiO2)制成的薄绝缘层,它由金属或多晶硅基导电层覆盖,就是所谓的门(gate)

image.png

因此,典型的NMOS晶体管有三个端子:源极、漏极和栅极,它们中的每一个都可以连接到电压源。我们现在有两个栅极电压选项——逻辑1($V_{dd}$伏)或逻辑0(0伏)。如果栅极处的电压为逻辑1(Vdd伏),则沟道中的电子被吸引到栅极。事实上,如果栅极处的电压大于某个阈值电压(在当前技术中通常为0.15V),则由于电子的积累,在漏极和源极之间形成低电阻导电路径。因此,电流可以在漏极和源极之间流动。如果沟道的有效电阻是R沟道,那么我们有$V_{drain}=IR_{channel}+V_{source}$。如果流经晶体管的电流量低,则由于低沟道电阻(R沟道),$V_{drain}$大致等于$V_{source}$。因此,我们可以将NMOS晶体管视为开关(见上图b)。当栅极电压为1时,它被打开。

现在,如果我们将栅极电压设置为0,那么由电子组成的导电路径就无法在沟道中形成。因此,晶体管将不能传导电流,将处于o状态。在这种情况下,开关关闭。

NMOS晶体管的电路符号如上图(c)所示。

19.2.3.4 PMOS晶体管

像NMOS晶体管一样,我们可以有一个PMOS晶体管,如下图(a)所示,源极和漏极是由p型硅构成的区域,晶体管操作的逻辑与NMOS晶体管的逻辑完全相反。在这种情况下,如果栅极处于逻辑0,则空穴被吸引到沟道并形成导电路径。然而,如果栅极处于逻辑1,则孔被沟道排斥,不形成导电路径。

PMOS晶体管也可以被视为开关(图b),当栅极电压为0时,它打开,当栅极处的电压为逻辑1时,它关闭。PMOS晶体管的电路符号如图(c)所示。

image.png

19.2.3.5 NAND和NOR门

下图显示了如何在CMOS技术中构建NAND门。两个输入端A和B连接到每个NMOS-PMOS对的栅极,如果A和B都等于1,则PMOS晶体管将关断,NMOS晶体管将导通,将输出设置为逻辑0。但是,如果其中一个输入等于0,则其中一个NMOS晶体管将关闭,其中一个PMOS晶体管将打开。因此,输出将设置为逻辑1。

请注意,我们使用AND运算的运算符“.”,这种符号在表示布尔公式时被广泛使用。同样,对于OR运算,使用“+”符号。

image.png

下图显示了如何构建NOR门。在这种情况下,两个输入端A和B也连接到每个NMOS-PMOS对的栅极。然而,与NAND门相比,拓扑结构有所不同。如果其中一个输入为逻辑1,则其中一个NMOS晶体管将导通,其中一个PMOS晶体管将截止,输出将设置为0。如果两个输入都等于0,则两个NMOS晶体将截止,两个PMOS晶体将导通,输出将等于逻辑1。

image.png

NAND门的一些用途如下:

image.png

NOR门的一些用途如下:

image.png

19.2.4 组合逻辑电路

19.2.4.1 XOR门

让我们实现异或(XOR)的逻辑函数,使用运算符进行XOR运算,如果两个输入不相等,则异或操作返回1,否则返回0。已知$A \oplus B=A \cdot \overline{B}+\overline{A} \cdot B$,则真值表和实现异或门的电路如下所示。

image.png

基本逻辑门如下所示:

image.png

19.2.4.2 多路复用器和信号分离器

多路复用器(Multiplexer)的框图如下图左所示,采用n个输入位和log(n)个选择位,并根据选择位的值,选择一个输入作为输出(参见图中带箭头的线)。多路复用器在处理器设计中大量使用,我们需要从一组输入中选择一个输出。多路复用器也称为多路复用器。

信号分离器将log(n)位二进制数作为输入,1位输入,并将输入传输到n条输出线中的一条,参见下图右。多路分解器用于存储单元的设计,其中输入必须精确地反映在一条输出线中。

image.png
左:单个多路复用器结构图。中:4输入的多路复用器。右:信号分离器。

image.png
多路复用器输入至程序计数器。

19.2.4.3 编码器和解码器

解码器将log(n)位二进制数作为输入,并具有n个输出。根据输入,它将其中一个输出设置为1。

解码器的设计如下图左所示,具有两个输入和四个输出的2x4解码器的设计。假设输入是A和B。我们生成所有可能的组合:$\overline{A B}, \overline{A} B, A \overline{B}, AB$。这些布尔组合是通过计算A和B的逻辑“非”,然后将这些值路由到一组“与”门来生成的。

现在让我们考虑一个与解码器逻辑相反的电路,其框图如下图右所示。该电路有n个输入和log(n)个输出,n个输入中的一个假定为1,其余假定为0,输出位提供等于1的输入二进制编码。例如,在8输入、3输出编码器中,如果第f行等于1,则输出等于100(计数从0开始)。

image.png
左:2x4解码器的设计。右:n位编码器框图。

现在我们假设我们不存在只有一个输入行可以等于1的限制,假设有多个输入可以等于1。在这种情况下,我们需要报告具有最高索引(优先级)的输入行的二进制编码。例如,如果是第3行和第5行,那么我们需要报告第5行的二进制编码,和上图右一样。此外,4-2位编码器的电路图如下图所示。

image.png

用解码器实现解复用器:

image.png

时钟SR锁存器(下图左)和D锁存器(下图右):

image.png

J–K锁存器:

image.png

基本锁存器的比较:

image.png

19.2.5 时序逻辑电路

前面已经研究了在比特上计算不同函数的组合逻辑电路,本小节将讨论如何保存位以供以后使用,这些结构被称为顺序逻辑元件(sequential logic element),因为输出取决于过去的输入,这些输入在事件序列中较早出现。逻辑门的基本思想是修改输入值以获得所需的输出,在组合逻辑电路中,如果输入被设置为0,那么输出也被重置。为了确保电路存储一个值并在处理器通电时保持该值,需要设计一种具有某种“内置存储器”的不同类型的电路。让我们从制定一组要求开始:

  1. 电路应能自我维持,并在外部输入复位后保持其值。不应依赖外部信号来维持其存储的元件。
  2. 应该有一种方法来读取存储的值而不破坏它。
  3. 应该有一种方法将存储值设置为0或1。

确保电路保持其值的最佳方法是创建反馈路径,并将输出连接回输入,先看看最简单的逻辑电路:SR锁存器(SR latch)。

19.2.5.1 SR锁存器

下图显示了SR锁存器。有两个输入S(设置)和R(重置),有两个输出Q及其补码Q,包含了两个交叉耦合NAND门的电路。请注意,如果与非门的一个输入为0,则输出保证为1。然而,如果其中一个输入是1,另一个输入则为a,则输出为a。

image.png

用NOR门实现的SR锁存器:

image.png

19.2.5.2 时钟和信号

一个典型的处理器包含数百万或可能数十亿个逻辑门和数千个锁存器,不同的电路需要不同的时间,例如多路复用器可能需要1ns,解码器可能需要0.5ns。电路完成计算后,就可以转发输出了。如果没有全局时间的概念,很难在不同的单元之间同步通信,尤其是那些具有可变延迟的单元,导致难以设计、操作和验证处理器。由此需要时间概念,例如可以说加法器需要两个时间单位,在两个单元结束时,预期数据将在锁存器X中找到,其他单元可以在两个时间单元后从锁存器中获取值并继续计算。

考虑一个需要向打印机发送一些数据的处理器的例子。为了传输数据,处理器通过一组铜线发送一系列比特,打印机读取这些比特,然后打印数据。问题是,处理器什么时候发送数据?计算完成后,需要发送数据。我们可以问的下一个问题是,处理器如何知道计算何时结束?它需要知道不同单元的确切延迟,一旦计算的总持续时间过去,可以将输出数据写入锁存器,并设置用于通信的铜线的电压。因此,处理器确实需要时间概念。其次,设计者需要告诉处理器不同子单元所需的时间。与处理2.34ns和1.92ns等数字相比,处理1、2和3等整数要简单得多。这里的1、2、3表示时间单位,时间单位可以是任何数字,例如0.9333ns。

时钟信号(clock signal):发送到大型电路或处理器的每个部分的周期性方波。 时钟周期(clock cycle):时钟信号的周期。 时钟频率(clock frequency):时钟周期的倒数。
因此,大多数数字电路与时钟信号同步,该时钟信号在完全相同的时间向处理器的每个部分发送周期性脉冲。时钟信号为方波,如下图所示,大多数时间,时钟信号是由主板上的专用单元从外部生成的。让我们考虑时钟信号从1转变到0(向下/负边缘)的点作为时钟周期的开始,从时钟的一个向下沿到下一个向下边缘测量时钟周期,时钟周期的持续时间也称为时钟周期,时钟周期的倒数被称为时钟频率。

image.png
一个时钟信号。

电脑、笔记本电脑、平板电脑或移动电话通常会在其规格中列出频率。例如,规范可能会说处理器运行在3GHz,这个数字是指时钟频率。

典型的计算模型是:电路中执行所有基本动作所需的时间是按照时钟周期来测量的,如果生产者单元占用n个时钟周期,那么在n个时钟循环结束时,它将其值写入锁存器。其他用户单元知道此延迟,并且在第(n+1)个时钟周期开始时,它们从锁存器读取值。由于所有单元都与时钟明确同步,并且处理器知道每个单元的延迟,因此很容易对计算进行排序、与I/O设备通信、避免竞争条件、调试和验证电路。我们想向打印机发送数据的简单示例可以通过使用时钟轻松解决。

19.2.5.3 时钟SR锁存器

下图显示了SR锁存器,其增加了两个与非门,时钟作为输入之一,另外两个输入分别是S位和R位。如果时钟为0,则交叉耦合NAND门的两个输入都为1,将保持先前的值。如果时钟为1,则交叉耦合NAND门的输入分别为S和R,这些输入与基本SR锁存器相同。请注意,时钟锁存器通常称为触发器(flip-flop)

image.png
时钟SR锁存器图例。

触发器(flip-flop)是一个时钟锁存器,可以保存一位(0或1)。

通过使用时钟,我们部分解决了输入和输出同步的问题。在这种情况下,当时钟为0时,输出不受输入的影响。当时钟为1时,输出受输入影响。这种锁存器也称为电平敏感锁存器(level sensitive latch)

电平敏感锁存器(level sensitive latch)取决于时钟信号的值:0或1。通常,它只能在时钟为1时读取新值。

在电平敏感锁存器中,电路有半个时钟周期来计算正确的输出(当时钟为0时)。当时钟为1时,输出可见。最好有一个完整的时钟周期来计算输出,这需要一个边缘敏感锁存器(edge sensitive latch),边缘敏感锁存器仅在时钟的向下边缘反映输出端的输入。

边缘敏感锁存器(edge sensitive latch)仅在固定的时钟边缘(例如向下边缘,从1到0的转换)反映输出端的输入。

19.2.5.4 边缘敏感SR触发器

下图显示了边缘敏感SR触发器的结构图,连接了两个边缘敏感SR触发器,唯一的区别是第二个触发器使用了时钟信号组合。第一个触发器为主(master),而第二个为从(slave)。这种触发器也被称为主-从SR触发器。这就是这个电路的工作原理。

image.png

除了主从SR触发器,还有其它各种类型的触发器,如JK触发器、D触发器、主从D触发器等。

image.png
从上到下:JK触发器、D触发器、主从D触发器。

19.2.5.5 寄存器

我们可以通过使用一组n个主从D触发器来存储n位数据,每个D触发器连接到输入线,其输出端连接到输出线,这种n位结构被称为n位寄存器。我们可以并行加载n位,也可以在每个负时钟边沿并行读取n位。因此,这种结构被称为并行输入——并行输出寄存器。其结构如下图所示。

image.png

现在让我们考虑一个串行输入-并行输出寄存器,如下图所示,有一个输入被馈送到最左边的D触发器, 每个周期,输入都会移动到右侧的相邻触发器。因此,要加载n位将需要n个周期。 第一位将在第一个周期被加载到最左边的触发器中,它需要n个周期才能到达最后一个触发器。 到那时,其余的n - 1触发器将加载其余的n - 1位,然后我们可以并行读取所有 n 位(类似于并行并行输出寄存器)。 该寄存器也称为移位寄存器,用于实现高速I/O总线中使用的电路。

image.png

8位并行寄存器的结构图如下:

image.png

5位移位寄存器:

image.png

行波计数器:

image.png

19.2.6 内存

19.2.6.1 静态内存(SRAM)

SRAM是指静态随机存取存储器,基本SRAM单元包含两个交叉耦合的反相器,如下图所示。相比之下,基本SR触发器或D触发器包含交叉耦合的NAND门。设计如下所示。

image.png

SRAM单元的核心包含4个晶体管(每个反相器中有2个),这种交叉耦合布置足以节省单个比特(0或1)。然而,我们需要一些额外的电路来读取和写入值。此时,在锁存器中使用交叉耦合反相器到底是不是一个坏主意,它们毕竟需要更少的晶体管。我们将看到,实现用于读取和写入SRAM单元的电路的开销是非常重要的,开销不足以证明以SRAM单元为核心制作锁存器的合理性。

交叉耦合的反相器连接到每一侧(W1、W2)上的晶体管,W1和W2的栅极连接到被称为字线的相同信号,两个反相器W1和W2中的四个晶体管构成SRAM单元,它总共有六个晶体管。现在,如果字线上的电压低,则W1和W2关断,不可能读取或写入SRAM单元。然而,如果字线上的信号为高,则W1和W2导通,可以访问SRAM单元。

下图显示了一个典型的SRAM阵列,SRAM单元被布置为二维矩阵。一行中的所有SRAM单元共享字线,一列中的所有SRAM单元共享一对位线。要激活某个SRAM单元,必须打开其相关的字线,由解码器完成,获取地址位的子集,并打开适当的字线。一行SRAM单元可能包含100多个SRAM单元,通常,我们会对32个SRAM单元(在32位机器上)的值感兴趣。在这种情况下,列复用器/解复用器选择属于感兴趣的SRAM单元的位线,使用地址中的位的子集作为列选择位。这种设计方法也称为2.5D存储器组织。

image.png
SRAM单元阵列。

19.2.6.2 内容寻址内存(CAM)

下图是10晶体管CAM单元,如果SRAM单元中存储的值V不等于输入位$A_i$,那么我们希望将匹配线的值设置为0。在CAM单元中,上半部分是具有6个晶体管的常规SRAM单元,下半部有4个额外的晶体管。现在让我们考虑晶体管T1,它连接到全局匹配线,晶体管T2。T1由存储在SRAM单元中的值V控制,T2由$\overline{A_i}$控制。假设V=$\overline{A_i}$,如果两者都为1,则晶体管T1和T2处于导通状态,并且匹配线和地之间存在直接导电路径。因此,匹配线的值将设置为0。然而,如果V和$\overline{A_i}$都为0,则通过T1和T2的路径不导通。但是,在这种情况下,通过T3和T4的路径变得导通,因为这些晶体管的栅极分别连接到$\overline{V}$和$A_i$。两个栅极的输入都是逻辑1,因此匹配线将被下拉到0。读取器可以反过来验证,如果V=$A_i$,则不形成导通路径。因此,如果存储的值与输入位$A_i$不匹配,则CAM单元将匹配线驱动到逻辑0。

image.png
10晶体管CAM单元。

下图显示了CAM单元阵列。该结构主要类似于SRAM阵列。我们可以通过索引寻址一行,并执行读/写访问,此外可以将CAM单元的每一行与输入A进行比较。如果任何行与输入匹配,则相应的匹配线的值将为1。可以计算所有匹配线的逻辑OR,并确定CAM阵列中是否匹配,此外可以将CAM阵列的所有匹配线连接到优先级编码器,以查找与数据匹配的行的索引。

image.png
CAM单元阵列。

19.2.6.3 动态内存(DRAM)

现在来看看一种只使用一个晶体管来节省一点时间的存储器技术,它非常密集、面积大,而且能效高,但比SRAM和锁存器慢得多,适用于大型片外存储器。

基本DRAM(动态内存)单元如下图所示。单个晶体管的栅极连接到字线,从而启用或禁用它,其中一个端子连接到存储电荷的电容器。如果存储的位是逻辑1,则电容器带电,否则不带电。

image.png
一个动态内存的单元。

image.png
DRAM和SRAM单元的对比图。

因此,读取和写入值非常容易。我们需要首先设置字线,以便可以访问电容器。为了读取该值,需要感测位线上的电压。如果它处于地电位,则单元存储0,否则如果它接近电源电压,则存储1。类似地,要写入值,我们需要将位线(BL)设置为适当的电压,并设置字线,电容器将相应地充电或放电。

然而,就DRAM而言,并非一切都是免费的。让我们假设电容器被充电到等于电源电压的电压,实际上,电容器将通过电介质和晶体管逐渐泄漏一些电荷。该电流很小,但在长时间内电荷的总损失可能很大,最终会使电容器放电。为了防止这种情况,有必要定期刷新DRAM单元的值,亦即需要读取并写回数据值。这也需要在读取操作之后完成,因为电容器在对位线充电时会损失一些电荷。现在让我们尝试制作一个DRAM单元阵列。

我们可以用创建SRAM单元阵列的方法构建DRAM单元阵列(下图),有三点不同:

  • 存在一条位线而不是两条位线。
  • 有一个连接到位线的专用刷新电路。这在读取操作后使用,也会定期调用。
  • 在这种情况下,感测放大器出现在列复用器/解复用器之前。读出放大器还为整个DRAM行(也称为DRAM页)缓存数据。它们确保对同一DRAM行的后续访问是快速的,因为它们可以直接从读出放大器进行服务。

image.png
DRAM单元阵列。

接下来简述现代DRAM的时序方面。在过去的好日子里,DRAM内存是异步访问的,意味着DRAM模块没有做出任何时序保证。但现在每个DRAM操作都与系统时钟同步,因此,如今的DRAM芯片是同步DRAM芯片(SDRAM芯片)。

截至目前,同步DRAM存储器通常使用DDR4或DDR5标准,DDR代表双倍数据速率,使用最早标准DDR1的设备在时钟的上升沿和下降沿向处理器发送8字节的数据包,DDR也被称为双峰(double pump)操作。DDR1的峰值数据速率为1.6 GB/s,后续的DDR世代通过以更高的频率传输数据来扩展DDR1,例如,DDR2的数据速率是DDR1设备的两倍(3.2 GB/s),DDR3通过使用更高的总线频率将峰值传输速率进一步提高了一倍,自2007年开始使用(峰值速率为6.4GB/s)。

19.2.6.4 只读内存(ROM)

只读内存可分为普通ROM和PROM(可编程ROM),下面分别是它们的单元图例。

image.png
(a) 存储逻辑0的ROM单元;(b) 存储逻辑1的ROM单元。

image.png
PROM单元。

19.2.6.5 可编程逻辑阵列

事实证明,我们可以很容易地用类似于PROM单元的存储单元制作组合逻辑电路,这种器件被称为可编程逻辑阵列或PLA。PLA在实践中用于实现由数十或数百个小项(minterm)组成的复杂逻辑函数,相对于由逻辑门组成的硬连线电路的优势在于它是灵活的,我们可以在运行时更改PLA实现的布尔逻辑,相比之下,由硅制成的电路永远不会改变其逻辑。其次,PLA的设计和编程更简单,而且有很多软件工具可以设计和使用PLA。最后,PLA可以有多个输出,因此可以很容易地实现多个布尔函数。这种额外的灵活性是有代价的,代价是性能。

下图(a)所示的PLA单元原则上类似于基本PROM单元。如果栅极处的值(E)等于1,则NMOS晶体管处于导通状态。因此,NMOS晶体管的源极和漏极端子之间的电压差非常小。换句话说,可以简单地假设结果线的电压等于信号的电压,X. If (E = 0),NMOS晶体管处于截止状态。结果线是浮动的,并保持其预充电电压。在这种情况下,我们建议推断逻辑1。

现在让我们构建一行PLA单元,其中每个PLA单元在其源极端子处连接到输入线,如图(b)所示。输入编号为X1…Xn,所有NMOS晶体管的漏极连接到结果线,PLA单元的晶体管的栅极连接到一组使能信号E1…En。如果任何一个使能信号等于0,则该特定晶体管被禁用,我们可以将其视为从PLA阵列中逻辑移除。

image.png
一个PLA单元。

现在让我们创建一个PLA单元格数组,如下图所示,每行对应一个minterm。对于我们的3变量示例,每行由6列组成,每个变量有2列(原始和补充),例如,前两列分别对应于A和$\overline{A}$。在任何一行中,这两列中只有一列包含PLA单元,因为A和$\overline{A}$不能同时为真。在第一行,计算最小项$\overline{ABC}$的值,因此第一行包含对应于$\overline{A}$、$\overline{B}$和$\overline{C}$的列中的PLA单元。我们在其余行中为剩余的minterm进行类似的连接,PLA阵列的这一部分被称为AND平面,因为我们正在计算变量值(原始值或补码值)的逻辑AND。PLA阵列的AND平面独立于我们希望计算的布尔函数,给定输入,它计算所有可能的最小项的值。

image.png
PLA单元阵列。

image.png
典型的内存封装引脚和信号。

image.png
256 KB内存组织。

image.png
1MB内存组织。

image.png
DDR代次演进图。

image.png
非易失性RAM技术。

image.png
image.png
上:简化的DRAM读取时序;下:Signetics 7489 SRAM的脉冲串。

19.2.7 计算机算术

本节将设计算术运算的硬件算法,先阐整数运算的算法,如两个二进制数相加的基本算法,有很多方法可以完成这些基本操作,每种方法都有自己的优缺点。注意,二进制减法的问题在概念上与2的补码系统中的二进制加法相同。因此,我们不需要单独对待它。随后,我们将看到,n个数的相加问题与乘法问题密切相关,而且这是一个硬件上的快速操作。遗憾的是,整数除法并不存在非常有效的方法。然而,我们将考虑两种用于划分正二进制数的流行算法。

整数算术之后,我们将研究浮点(带小数点的数字)算术的方法,大多数整数算法稍作修改后都可以移植到浮点数领域。与整数除法相比,浮点除法可以非常有效地完成。

19.2.7.1 加法

让我们看看将两个1位数字a和b相加的问题,a和b都可以取两个值:0或1,因此,a和b有四种可能的组合,它们的二进制和可以是00、01或10。当a和b均为1时,它们的和将是10,两个1位数字的总和可能有两位长。让我们将结果的LSB称为和,将MSB称为进位,例如,把8和9相加,和是7,进位是1。

可以将和和进位的概念扩展到加三个1位数字。如果我们将三个1位数字相加,那么结果的范围是二进制的00到11之间。

和(sum):总和是两个或三个1位数字相加结果的LSB。 进位(carry):进位是两个或三个1位数字相加结果的MSB。

对于可以将两个1位数字相加的加法器,将有两个输出位:和s和进位c,将两个位相加的一个加法器称为半加法器(half adder)。半加法的真值表如下表所示。

image.png

image.png
半加法器。

可以加3位的加法器称为全加法器(full adder),它的电子构造如下:

image.png

n位加法器被称为纹波进位加法器(ripple carry adder),其设计如下所示:

image.png

考虑将两个数字A和B相加的问题,首先将比特集划分为4比特的块,如下图所示,每个块包含一个a片段和一个B片段。通过考虑块的输入进位将这两个片段相加,并生成一组和位和一个进位,此进位是后续块的输入进位。

image.png

此外,还有超前进位加法器(Carry Lookahead Adder),其分为两个阶段,每个阶段都拥有复杂的电子构造。

19.2.7.2 乘法

现与加法类似,先看看两个十进制数相乘的最简单的方法,不妨尝试将13乘以9。在这种情况下,13被称为被乘数(multiplicand),9被称为乘数(multiplier),117是乘积(product)

下图(a)显示了十进制的乘法,(b)显示了二进制的乘法。请注意,两个二进制数相乘的方法与十进制数完全相同。我们需要考虑乘数从最小显著位置到最大显著位置的每一位。如果该位为1,那么我们将被乘数的值写在该行下方,否则我们将写0。对于每个乘数位,我们将被乘数向左移动一位。其原因是每个乘数位代表2的更高幂。我们将每个这样的值称为部分和(见图7.10(b))。如果乘法器有m位,那么我们需要将m个部分和相加以获得乘积。在这种情况下,乘积是十进制117,二进制1110101。读者可以验证它们实际上表示相同的数字。为了便于以后的表示,让我们定义另一个称为部分积的术语。它是部分和的连续序列的和。

image.png
(a)十进制乘法;(b)二进制乘法。

常规的乘法器是$O(n^2)$,而改进版的Booth乘法器或Wallace树形乘法器(下图)可以做到$O(log(n))$的算法复杂度。

image.png
Wallace树形乘数。

19.2.7.3 除法

现在让我们看看整数除法。不幸的是,与加法、减法和乘法不同,除法是一个明显较慢的过程。任何除法运算都可以表示如下:
N是被除数,D是除数,Q是商,R是余数。假定除数和被除数为正,除法过程需要满足以下属性:

  • $R < D$且$R \ge 0$。
  • Q是满足上述等式的最大正整数。

如果我们想除掉负数,那么首先将它们转换成正数,进行除法,然后调整商和余数的符号,部分ISA试图确保余数始终为正。在这种情况下,需要将商减1,并将除数与余数相加,使其为正。

实现除法的方式有迭代除法、佘数恢复除法(Restoring Division)、非余数恢复除法(Non-Restoring Division)等。本文忽略这些算法的具体描述,有兴趣的童鞋可以自行查阅资料。

19.2.7.4 浮点数运算

浮点加法和减法的问题实际上是同一问题的不同方面。A-B可以用两种方式解释,可以说正在从A中减去B,也可以说在将-B加到A中。因此,与其单独看减法,不如将其视为加法的特例。浮点数的二进制表示、属性和特殊含义可以参见:17.2.2 浮点数

下图显示了一个示例,说明了如何将有效位解压缩,并将其放入普通浮点数的寄存器中。在32位IEEE 754格式中,尾数有23位,小数点前有0或1。因此,有效位需要24位,如果我们希望添加前导符号位(0),那么我们需要25位存储。让我们把这个号码保存在一个寄存器中,并称之为W。

image.png
展开有效位并放入寄存器。

image.png
IEEE 754格式。

浮点数的运算涉及舍入等考量,下图显示了两个浮点数相加的算法,考虑了0值。

image.png
累加两个浮点值的流程图。

浮点数相乘算法与泛型加法算法的形式完全相同,只需几步。让我们尝试乘以A x B以获得乘积C,乘法的流程图如下图所示。在乘法的情况下,我们不必对齐指数,如下初始化算法,将B的符号和装入寄存器W,W的宽度等于操作数大小的两倍,就可以容纳乘积。E寄存器初始化为$E_A+E_B - bias$,因为在乘法的情况下,指数相加,减去bias以避免重复计数,计算结果的符号很简单。

image.png
两个浮点值相乘的流程图。

此外,还有Goldschmidt除法以及Newton-Raphson除法。

image.png
Newton-Raphson方法。

19.3 计算机架构和组织

19.3.1 计算机层级

计算机系统级层次结构是将计算机与用户连接起来并使用计算机的不同级别的组合,还描述了如何在计算机上执行计算活动,并显示了在不同级别的系统中使用的所有元素。通用的计算机系统级层次结构由7个级别组成:

image.png

当然,也存在另一种层级划分,从上到下分别是:游戏应用、游戏引擎、图形API、操作系统、设备驱动、硬件设备。

image.png

下图是更加详细的层级模块,其中操作系统(OS)处于图形API等第三方SDK和驱动之间,充当着承上启下的重要作用和通讯桥梁,是整个计算机层级架构极其重要的组成部分。

image.png

在底层,计算机硬件由处理器、内存和I/O组件组成,每种类型有一个或多个模块。这些组件以某种方式互连,以实现计算机的主要功能,即执行程序。有四个主要结构要素:

  • 处理器:控制计算机的操作并执行其数据处理功能。当只有一个处理器时,它通常被称为中央处理单元(CPU)。
  • 主存储器:存储数据和程序。易丢失,当计算机关闭时,内存中的内容会丢失。相反,即使计算机系统关闭,磁盘内存的内容也会保留。主存储器也称为实存储器或主存储器。
  • I/O模块:在计算机及其外部环境之间移动数据外部环境由各种设备组成,包括辅助存储器设备(如磁盘)、通信设备和终端。
  • 系统总线:提供处理器、主存储器和I/O模块之间的通信。

image.png

19.3.2 架构vs组织

计算机架构(Computer Architecture)是对计算机各个部分的需求和设计实现的功能描述,处理计算机系统的功能行为。在设计计算机时,它出现在计算机组织之前。

计算机组织(Computer Organization)出现在计算机体系架构之后,是操作属性如何链接在一起并有助于实现架构规范的方式,处理的是结构关系。

简单而言,架构是呈现给软件设计师的计算机视图,组织是计算机在硬件上的实际实现。

image.png
计算机的层级设计、硬件、软件和架构、组织的关系图。

image.png
数字计算机方框图。

计算机体系架构和计算机组织之间的详细区别如下表:

image.png

19.3.3 冯·诺伊曼架构

历史上有两种类型的计算机:

  • 固定程序计算机:它们的功能非常具体,不能重新编程,例如计算器。
  • 存储程序计算机:可以被编程以执行许多不同的任务,应用程序存储在它们上面,因此得名。

现代计算机基于John Von Neumann(约翰·冯·诺依曼)引入的存储程序概念。在这种存储程序的概念中,程序和数据存储在称为存储器的单独存储单元中,并被同等对待,意味着用这种架构构建的计算机将更容易重新编程。 其基本结构是这样的:

image.png

有着输入、处理、输出等概念和组成的计算机模型称为冯·诺伊曼架构,它是一种将程序指令存储器和数据存储器合并在一起的电脑设计概念结构,是一种实现通用图灵机的计算设备,以及一种相对于并行计算的序列式结构参考模型(referential model)。冯·诺伊曼隐式指导了将存储设备与中央处理器分开的概念,也被称为ISA(指令集架构)计算机。

image.png
冯·诺依曼1947年出版的《电子计算仪器问题的规划和编码》中的流程图。

冯·诺依曼结构的抽象组成如下:
image.png

更进一步地,它约定了用二进制进行计算和存储,还定义计算机基本结构为5个部分,分别是中央处理器(CPU)、内存、输入设备、输出设备、总线

image.png

结合上图,各部分结构的具体描述如下:

  • 存储器:代码跟数据在RAM跟ROM中是线性存储, 数据存储的单位是一个二进制位,最小的存储单位是字节。
  • 总线:总线是用于 CPU 和内存以及其他设备之间的通信,总线主要有三种:
  • 地址总线:用于指定 CPU 将要操作的内存地址。
  • 数据总线:用于读写内存的数据。
  • 控制总线:用于发送和接收信号,比如中断、设备复位等信号,CPU收到信号后响应,这时也需要控制总线。
  • 输入/输出设备:输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。比如键盘按键时需要和CPU进行交互,这时就需要用到控制总线。
  • CPU:中央处理器,类比人脑,作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。它的结构如下所示:

image.png

  • 控制单元(CU):处理所有处理器控制信号,指导所有输入和输出流,获取指令代码,并控制数据在系统中的移动方式。
  • 算术逻辑单元 (ALU) :处理CPU可能需要的所有计算(如加法、减法、比较),执行逻辑运算、位移运算和算术运算。
  • 主存储器单元(寄存器):CPU用寄存器存储计算时所需数据,寄存器一般有以下几种:

    • 累加器(Accumulator):存储ALU的计算结果。
    • 程序计数器(PC):跟踪要处理的下一条指令的内存位置,然后PC将下一个地址传递给内存地址寄存器 (MAR)。
    • 内存地址寄存器(MAR):存储需要从内存中取出或存储到内存中的指令的内存位置。
    • 内存数据寄存器(MDR):存储从内存中获取的指令或任何要传输到内存并存储在内存中的数据。
    • 指令寄存器(IR):可分为两种:
    • 当前指令寄存器(CIR):在等待编码和执行时存储最近获取的指令。
    • 指令缓冲寄存器(IBR):不立即执行的指令放在指令缓冲寄存器IBR中。
    • 通用寄存器(GPR):存放需要进行运算的数据,比如需进行加法运算的两个数据。

在冯诺伊曼体系下计算机指令执行的简要过程如下:

  • CPU读取程序计数器获得指令内存地址,CPU控制单元操作地址总线从内存地址拿到数据,数据通过数据总线到达CPU被存入指令寄存器。
  • CPU分析指令寄存器中的指令,如果是计算类型的指令交给逻辑运算单元,如果是存储类型的指令交给控制单元执行。
  • CPU 执行完指令后程序计数器的值通过自增指向下个指令,比如32位CPU会自增4。
  • 自增后开始顺序执行下一条指令,不断循环执行直到程序结束。

冯诺依曼瓶颈(Von Neumann bottleneck)是无论做什么来提升性能,都无法摆脱这样一个事实,即一次只能执行一条指令,并且只能按顺序执行,这两个因素都阻碍了CPU的能力。我们可以为冯诺依曼处理器提供更多缓存、更多RAM或更快的组件,但如果要在CPU性能方面取得原始收益,则需要对CPU配置进行有影响力的检查。 这种架构非常重要,用于PC乃至超级计算机。

19.3.4 多核结构

下图是典型多核计算机主要部件的简化视图。大多数计算机,包括智能手机和平板电脑中的嵌入式计算机,以及个人计算机、笔记本电脑和工作站,都安装在主板上。印刷电路板(PCB)是一种刚性的平板,用于固定和互连芯片和其他电子部件,该电路板由通常为两到十层的层组成,这些层通过蚀刻到电路板中的铜路径将组件互连。计算机中的主要印刷电路板称为系统板或主板,而插入主板插槽的较小的印刷电路板则称为扩展板。主板上最突出的元素是芯片,芯片是一块半导体材料,通常是硅,在其上制造电子电路和逻辑门,所得产品称为集成电路。

image.png
多核计算机主要元件的简化视图。

下图左是IBM zEnterprise EC12大型计算机处理器芯片的照片,有27.5亿个晶体管,有六个内核(处理器),还有两个标记为L3缓存的大区域,由所有六个处理器共享,L3控制逻辑控制L3高速缓存和内核之间以及L3高速缓存与外部环境之间的流量。此外,在核心和L3缓存之间还有存储控制(SC)逻辑,内存控制器(MC)功能控制对芯片外部内存的访问,GX I/O总线控制访问I/O的通道适配器的接口。下图右则展示了单个核的内部结构,只是构成单个处理器芯片的硅表面区域的一部分。

image.png
晶片(Wafer)、芯片(Chip)和门(Gate)之间的关系如下:

image.png
QPI(QuickPath Interconnect)是Intel于2008年推出的点对点互连方法,QPI和其他点对点互连方案的重要特征是多个直接连接、分层协议架构和分组数据传输。

下图说明了QPI在多核计算机上的典型使用。QPI链路(由图中的绿色箭头对表示)形成了一个交换结构,使数据能够在整个网络中移动,可以在每对核心处理器之间建立直接QPI连接。如果图中的核心A需要访问核心D中的内存控制器,则它通过核心B或C发送请求,后者必须将该请求转发到核心D的内存控制器。同样,具有八个或更多处理器的大型系统可以使用具有三个链路的处理器构建,并通过中间处理器路由流量。

image.png
使用QPI的多核配置。

image.png
QPI层示意图。

image.png
不同的芯片组织。

image.png
多核组织备选方案。

19.3.5 嵌入式系统

术语嵌入式系统是指在产品中使用电子设备和软件,而不是通用计算机,如平板电脑或台式机系统。每年售出数百万台电脑,包括笔记本电脑、个人电脑、工作站、服务器、大型机和超级计算机,相比之下,每年生产数十亿个嵌入大型设备的计算机系统。如今,许多(也许是大多数)使用电力的设备都有嵌入式计算系统,在不久的将来,几乎所有这样的设备都将具有嵌入式计算系统。

具有嵌入式系统的设备类型几乎太多,无法列出,样例包括手机、数码相机、摄像机、计算器、微波炉、家庭安全系统、洗衣机、照明系统、恒温器、打印机、各种汽车系统(如变速器控制、巡航控制、燃油喷射、防抱死制动和悬挂系统)、网球拍、牙刷以及自动化系统中的多种类型的传感器和致动器。

通常,嵌入式系统与其环境紧密耦合,导致与环境交互的需要所施加的实时约束。约束条件(如所需的运动速度、所需的测量精度和所需的持续时间)决定了软件操作的时间,如果必须同时管理多个活动,会带来更复杂的实时约束。

下图概括地显示了嵌入式系统组织,除了处理器和内存之外,还有许多元素与典型的台式机或笔记本电脑不同:

image.png
嵌入式系统的可能组织。

  • 可能存在多种接口,使系统能够测量、操作和以其他方式与外部环境交互。嵌入式系统通常通过传感器和致动器与外部世界交互(感知、操纵和通信),因此通常是反应系统,反应系统与环境持续交互,并以该环境确定的速度执行。
  • 人机界面可以像闪光灯一样简单,也可以像实时机器人视觉一样复杂。在许多情况下,没有人机界面。
  • 诊断端口可用于诊断所控制的系统,而不仅仅用于诊断计算机。
  • 可使用专用现场可编程(FPGA)、专用(ASIC)或甚至非数字硬件来提高性能或可靠性。
  • 软件通常具有固定的功能,并且特定于应用程序。
  • 效率对于嵌入式系统至关重要。需针对能量、代码尺寸、执行时间、重量、体积以及成本进行了优化。

与通用计算机系统也有几个值得注意的相似之处:

  • 即使使用名义上固定功能的软件,现场升级以修复错误、提高安全性和添加功能的能力对于嵌入式系统来说也变得非常重要,而不仅仅是在消费类设备中。
  • 一个相对较新的发展是支持多种应用的嵌入式系统平台,良好例子是智能手机和音频/视频设备(如智能电视)。

19.3.5.1 微处理器与微控制器

早期的微处理器芯片包括寄存器、ALU和某种控制单元或指令处理逻辑。随着晶体管密度的增加,有可能增加指令集架构的复杂性,最终增加内存和多个处理器,现代微处理器芯片包括多个内核和大量的高速缓存。

微控制器芯片对可用的逻辑空间进行了实质上不同的使用,下图概括地显示了微控制器芯片上常见的元件。微控制器是包含处理器、用于程序的非易失性内存(ROM)、用于输入和输出的易失性内存(RAM)、时钟和I/O控制单元的单个芯片。微控制器的处理器部分具有比其他微处理器低得多的硅面积和高得多的能量效率。

image.png

也被称为“芯片上的计算机”,每年数十亿个微控制器单元被嵌入到从玩具到家电到汽车的各种产品中,比如单个车辆可以使用70个或更多个微控制器。通常,特别是对于更小、更便宜的微控制器,它们被用作特定任务的专用处理器,比如微控制器在自动化过程中被大量使用。通过提供对输入的简单反应,它们可以控制机器、打开和关闭风扇、打开和闭合阀门等,是现代工业技术的组成部分,是生产能够处理极其复杂功能的机械的最廉价的方法之一。

微控制器具有多种物理尺寸和处理能力,处理器的范围从4位到32位架构。微控制器往往比微处理器慢得多,通常工作在MHz范围,而不是微处理器的GHz速度。微控制器的另一个典型特征是它不提供人机交互,被编程用于特定任务,嵌入其设备中,并在需要时执行。

19.3.5.2 嵌入式与深度嵌入式系统

嵌入式系统的一个子集,以及相当多的子集,被称为深度嵌入式系统(Deeply embedded system)。尽管这个术语在技术和商业文献中被广泛使用,但你会在互联网上无法明确地寻找一个直截了当的定义。通常,我们可以说,一个深度嵌入式系统有一个处理器,其行为很难被程序员和用户观察到。深度嵌入式系统使用微控制器而不是微处理器,一旦设备的程序逻辑被烧录到ROM(只读存储器)中,就不可编程,并且与用户没有交互。

深度嵌入式系统是专用的、单用途的设备,可以检测环境中的某些东西,执行基本级别的处理,然后对结果进行处理。深度嵌入式系统通常具有无线能力,并以联网配置出现,例如部署在大面积(例如,工厂、农业领域)上的传感器网络,物联网在很大程度上依赖于深度嵌入式系统。典型地,深度嵌入式系统在内存、处理器大小、时间和功耗方面具有极端的资源限制。

19.3.6 ARM架构

ARM架构是指从RISC设计原则演变而来的处理器架构,用于嵌入式系统。本节将简述之。

ARM指令集是高度规则的,旨在高效实现处理器和高效执行。所有指令均为32位长,遵循常规格式,使得ARM ISA适合在广泛的产品上实现。

增强基本ARM ISA的是Thumb指令集,是ARM指令集的重新编码子集。Thumb旨在提高使用16位或更窄内存数据总线的ARM实现的性能,并允许比ARM指令集提供的代码密度更好的代码密度。Thumb指令集包含记录为16位指令的ARM 32位指令集的子集。前些年定义的版本是Thumb-2。

ARM Holdings许可了许多专用微处理器和相关技术,但其产品线的大部分是Cortex系列微处理器架构。有三种Cortex架构,方便地用缩写A、R和M标记。

  • Cortex-A和Cortex-A50:是应用处理器,适用于智能手机和电子书阅读器等移动设备,以及数字电视和家庭网关(如DSL和有线互联网调制解调器)等消费设备。这些处理器以更高的时钟频率(超过1GHz)运行,并支持内存管理单元(MMU),是全功能操作系统(如Linux、Android、MS Windows和移动操作系统)所需的。MMU是通过将虚拟地址转换为物理地址来支持虚拟内存和分页的硬件模块。这两种架构同时使用ARM和Thumb-2指令集,主要区别在于Cortex-A是32位机器,而Cortex-A50是64位机器。
  • Cortex-R:设计用于支持实时应用程序,其中需要通过对事件的快速响应来控制事件的定时。它们可以在相当高的时钟频率(例如200MHz到800MHz)下运行,并且具有非常低的响应延迟。Cortex-R包括对指令集和处理器组织的增强,以支持深度嵌入式实时设备。这些处理器中的大多数没有MMU,有限的数据需求和有限数量的同时处理消除了对虚拟内存的复杂硬件和软件支持的需求。Cortex-R确实具有专为工业应用设计的内存保护单元(MPU)、缓存和其他内存功能。MPU是一种硬件模块,它禁止内存中的一个程序意外访问分配给另一个活动程序的内存。使用各种方法,在程序周围创建一个保护边界,并且禁止程序内的指令引用该边界之外的数据。使用Cortex-R的嵌入式系统包括汽车制动系统、大容量存储控制器、网络和打印设备。
  • Cortex-M:主要是为微控制器领域开发的,在微控制器领域,快速、高确定性中断管理的需求与极低门计数和最低可能功耗的需求相结合。与Cortex-R系列一样,Cortex-M架构有一个MPU,但没有MMU。Cortex-M仅使用Thumb-2指令集,其市场包括物联网设备、工厂和其他企业使用的无线传感器/致动器网络、汽车车身电子设备等。Cortex-M系列包含Cortex-M0、Cortex-M0+、Cortex-M3、Cortex-M4等版本。下图是基于Cortex-M3的典型单片机芯片:

image.png

19.3.7 云计算

尽管云计算的一般概念可以追溯到20世纪50年代,但云计算服务在2000年代初首次出现,尤其是针对大型企业。从那时起,云计算已经扩展到中小型企业,最近还扩展到了消费者。苹果的iCloud于2012年推出,在推出一周内就拥有2000万用户,2008年推出的基于云的笔记和归档服务Evernote在不到6年的时间内就接近了1亿用户。本节将简要概述。

在许多组织中,越来越突出的趋势是将大部分甚至所有信息技术(IT)运营转移到称为企业云计算的互联网连接基础设施。与此同时,个人电脑和移动设备的个人用户越来越依赖云计算服务来备份数据、同步设备和使用个人云计算进行共享。NIST在NIST SP-800-145(NIST云计算定义)中对云计算的定义如下:

云计算(Cloud computing):是一种模型,用于实现对可配置计算资源(例如,网络、服务器、存储、应用程序和服务)的共享池的无处不在、方便的按需网络访问,这些资源可以通过最小的管理工作量或服务提供商交互快速调配和发布。

基本上,通过云计算,可以获得规模经济、专业网络管理和专业安全管理,这些功能对大小公司、政府机构以及个人电脑和移动用户都有吸引力。个人或公司只需支付所需的存储容量和服务费用,无论是公司还是个人,用户都无需设置数据库系统、获取所需的硬件、进行维护和备份数据,所有这些都是云服务的一部分。

理论上,使用云计算存储数据并与其他人共享数据的另一大优势是云提供商负责安全。客户并不总是受到保护,云提供商之间出现了许多安全故障,例如Evernote在2013年初成为头条新闻,当时它告诉所有用户在发现入侵后重置密码。

云网络是指必须具备的网络和网络管理功能,以支持云计算。大多数云计算解决方案都依赖于互联网,但这只是网络基础设施的一部分。云网络的一个示例是在提供商和订户之间提供高性能和/或高可靠性网络,在这种情况下,企业和云之间的部分或全部流量绕过互联网,使用云服务提供商拥有或租用的专用专用网络设施。更一般地说,云联网是指访问云所需的网络能力的集合,包括利用互联网上的专门服务、将企业数据中心链接到云,以及在关键点使用防火墙和其他网络安全设备来强制执行访问安全政策。

我们可以将云存储视为云计算的一个子集,本质上,云存储由远程托管在云服务器上的数据库存储和数据库应用程序组成,使小型企业和个人用户能够利用可根据其需求扩展的数据存储,并利用各种数据库应用程序,而无需购买、维护和管理存储资产。

云计算的基本目的是提供方便的计算资源租赁,云服务提供商(CSP)维护通过互联网或专用网络可用的计算和数据存储资源,客户可以根据需要租用这些资源的一部分。实际上,所有云服务都是使用三种模型之一提供的(下图):SaaS、PaaS和IaaS。

image.png
替代信息技术架构。

image.png
云计算元素。

image.png
云服务模型。

剖析虚幻渲染体系(19)- 计算机硬件体系(2)

推荐阅读
关注数
1670
文章数
216
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息