一、冯诺依曼结构

Von-Neumann 概念

  • 计算机的结构与处理的问题无关
  • 计算机由4个单元构成
  • 主存储器里的格子是相同大小的,每个都有个地址
  • 程序和数据放在相同的存储器里
  • 使用二进制
  • 程序由一系列命令构成
  • 顺序可以通过跳转指令改变
image-20200215163311067

Von-Neumann Architektur

image-20200215163344965

RISC & CISC

CISC: Complex Instruction Set Computer

  • 通过微程序实现

  • 优点:易编程,少的存储需求

  • 缺点:复杂的解码

RISC: Reduced Instruction Set Computer

  • 通过固定接线(feste Verdrahtung)实现
  • 优点:简单,高效的实施
  • 缺点:难编程
image-20200215164015342

Befehlsformat

Einadressform

image-20200217191456458

Zweiadressform(IA-32)

  • 计算结果覆盖第一个操作数

  • 只有一个操作数可以在主存储器里

image-20200217191530181

Dreiadressform(CISC)

  • 操作数和结果被准确寻址
image-20200217191607875

Codierung

image-20200215164316863
image-20200215164452560

二、汇编/ISA

Register 寄存器

image-20200215115337148
image-20200216104446165

汇编指令

MOV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mov ax, 10h
mov eax, 10h
mov eax, ebx

; 和存储器的存取Speicherzugriff
mov eax, [20] ; 把地址20的32bit值存放到eax里

MOV EAX,[EBX]
MOV EAX,12[EBX] ; Intel-语法
MOV EAX,[EBX+12] ; nasm-语法

; 在 [] 里允许的寄存器: EAX-EDX, ESI, EDI, EBP, ESP

MOV EAX,[EBX][ESI] ; Intel-语法
MOV EAX,[EBX+ESI] ; nasm-语法

MOV EAX,12[EBX][EDX] ; Intel-Syntax
MOV EAX,[EBX+EDX+12] ; nasm-Syntax

MOV EAX,12[EAX][ECX*2] ; Intel-Syntax
MOV EAX,[EAX+ECX*2+12] ; nasm-Syntax

ADD/SUB

1
2
3
4
5
6
add eax, ebx ; eax := eax + ebx
sub ebx, 10 ; ebx := ebx - 10

;----------下面是错误示范
add ax, bl ; 8bit和16bit不能直接相加
add [36], [86] ; 最多只能写1个地址

INC/DEC

1
2
inc bl ; bl := bl + 1
dec esi ; esi := esi - 1

NEG

1
neg ecx ; ecx := -ecx

MUL/IMUL

MUL是乘法,IMUL是带符号乘法

1
2
3
4
5
6
7
8
mul ecx ; edx:eax := eax*ecx  , 溢出部分存在edx里面

IMUL EBX ; EDX:EAX := EAX*EBX (wie MUL)
IMUL EBX,ECX ; EBX := EBX*ECX
IMUL EBX,ECX,42 ; EBX := ECX*42 (Konstante max. 8 Bit!)
IMUL EBX,1234 ; EBX := EBX*1234 (Konstante 32 Bit!)
;----------下面是错误示范
mul 10 ; 不能乘常数

DIV

1
div ebx ; eax := (edx:eax)/ebx , edx := 余数

JMP

1
2
3
4
5
6
JMP 0x1234 ; EIP=0x1234 然后从0x1234继续执行
JMP fehler ; 跳转到一个标签
MOV EAX,0x4567
JMP EAX ; Mache bei EIP=0x4567 weiter
fehler: ;定义标签
JMP [EBX]

CALL/RET

调用子程序,CALL会额外执行PUSH EIP, RET额外执行POP EIP

image-20200215122730817

汇编语法

EQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
startwert: EQU 15  ; 定义startwert为15

mov eax, startwert
mov ebx, startwert + 1 ;对定义的值使用

werte1: RESB 64 ; 定义了64bit区域,没有定义内容
werte2: RESW 5 ; 定义5字区域 = 10 Bytes
tabelle1: DB 17,55,68,71 ; 定义存储空间 4 Bytes 并且定义内容
tabelle2: DW 0x1234,0x8888,0x5432 ; 定义并初始化3个字 words
tabelle3: DD 0x12345678,0x88882222,0x54329876 ; 定义初始化3个双字节

MOV EAX,[adresse1] ; 和 MOV EAX,[0x1770] 一样
MOV [werte1],BL ; 把BL存储到werte1的地址里
MOV AX,[tabelle2] ; tabelle2的第一个值放到AX里,也就是 AX只后值变为0x1234.


SASM 特色语法

print
1
2
PRINT_UDEC 2, dx
NEWLINE

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
%include "io.inc"

section .data

data_string1:
DB 72,101,108,108,111 ; „Hello“
DB 32 ; Leerzeichen
DB 87,111,114,108,100 ; „World“
DB 0 ; 0-Wert (Terminal)

data_string2: DB "Hello World", 0
data_integer: DD -7

CMAIN:
mov ebp, esp ; for correct debugging
PRINT_STRING data_string1
NEWLINE
PRINT_STRING data_string2
NEWLINE
PRINT_DEC 4, data_integer
NEWLINE
PRINT_UDEC 4, data_integer
NEWLINE

Flags

image-20200216104601930

CF

carry flag,进位标志,最高位进位为1

1
sub eax, ebx  ; cf = 1 if eax < ebx

SF

sign flag,符号标志,负号为1

OF

overflow flag,溢出标志,溢出为1

ZF

zero flag,结果为0标志,为零为1

跳转指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CMP EAX,5
JE ist_5 ; Sprung, wenn EAX = 5

CMP EAX,EBX
JE sind_gleich ; Sprung zu ’sind_gleich’, wenn EAX = EBX

DEC EAX
JNZ weiter ; Springt, wenn EAX vor dem DEC ungleich 1 war

ADD EBX,ECX
JO fehler_ueberlauf ; Springt, falls die Addition einen ¨Uberlauf erzeugt hat

CMP EAX,EBX
JLE kleiner_gleich ; Springt, wenn EAX <= EBX (vorzeichenbehaftete Zahlen)

逻辑运算

  • SAL/SAR 左移, 右移, 带符号 (最高位是1,位移补1)

  • SHL/SHR 左移右移,不带符号

  • ROL/ROR 带循环左移

  • RCL/RCR 带循环左移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MOV AX,0x8421 ; 1000 0100 0010 0001
SHR AX,1
; AX: 0x4210 0100 0010 0001 0000
MOV AX,0x8421 ; 1000 0100 0010 0001
SHL AX,1
; AX: 0x0842 0000 1000 0100 0010
MOV AX,0x8421 ; 1000 0100 0010 0001
SAR AX,1
; AX: 0xC210 1100 0010 0001 0000
MOV AX,0x8421 ; 1000 0100 0010 0001
ROL AX,1
; AX: 0x0843 0000 1000 0100 0011
MOV CL,1
MOV AX,0x8421 ; 1000 0100 0010 0001
ROR AX,CL
; AX: 0xC210 1100 0010 0001 0000
MOV DX, 0xffff ; DX:AX = -1
MOV AX, -1
MOV BX, 2
IDIV BX ; AX = -1/2 = 0
MOV DX, 0xffff ; DX:AX = -1
MOV AX, -1
SAR AX, 1 ; AX = -1 (vorzeichenbehaftet arithmetisch)
; wird nach unten abgerundet fuer negative Zahlen!

Virtuelle Speicherverwaltung

三、Mikroarchitektur

12.1 Schaltnetze(组合逻辑电路)

Schaltenetze是Mikroarchitektur里最小的组成元素,它是没有“思想”的。常见的有Multiplexer, Dekodierer, Tore, Addierer

image-20200101221224473

12.1.1 Multiplexer(MUX)(数据选择器)

Multiplexer是一个基本单元。它有n个输入信号,输出一个单独的信号。有一个控制型号会决定那哪个信号会输出

image-20200101221707459

image-20200101221707459
12.1.2 Dekodierer

Dekodierer会把一串输入的二进制码转换成单一的信号输出。输出的长度是\(N\)(1个是1,其余是0),输入的长度是\(logN\)

image-20200101222047658

12.1.3 Tore

Tore 会把决定输入的信号是否继续传递。如果控制信号是1就继续传递,否则就终止继续传递。

image-20200101222349149

12.2 Schaltwerke(时序逻辑电路)

Schaltwerke 和 Schaltnetze相反,它是有“思想”的。因为它有一个传入的函数。比如寄存器(Register)和存储器(Speicher)是Schaltwerke的种类。每个Schaltwerk是和Signalleitungen相关联。下面左边是Schaltnetzen右边是Schaltwerke

image-20200101223042703

12.3 Implementierungsoptinoen 实现选项

Mikroarchitektur 的任务是在作为指令循环(Befehlszyklus),独立数据转换(individuelle Datentransfers),操作逻辑块(Blöcke für Operationslogik)来实现ISA层。

  • "Fest verdrahtete" Architekturen,固定的结构
  • Programmierbare Architekturen,可编程的结构
12.3.1 "Fest verdrahtete" Architekturen

它有一系列的开关(Schaltungen)组成,也就是无论指令还是信号控制都会被直接实施。它直接对于了Gatter层

优点:高表现 höhere Performanz

缺点:

  • 开发成本高 Zeitaufwändige Entwicklung
  • 易受错误影响 Fehleranfällig
  • 难移植 Schwer anpassbar
12.3.2 Programmierbare Architekturen

所有的ISA指令编码(Opcodes)会在控制单元(Steuerwerk)解码。每个指令编码会执行在微程序存储器(Mikroprogrammspeicher)里存储的子程序。每一个ISA 指令都有一个微程序。它的执行由Leitwerk来做。

优点:

  • 设计简单
  • 指令的分解
  • 控制的时钟控制 Kontrollierte Taktsteuerung
  • 易改变
  • 易改错

缺点:速度慢

image-20200101225429061

12.3.3 Mikroprogrammzyklus

每一个微程序有一个执行的过程

1、确定下一个微指令的地址(IFETCH)

2、从Mikroprogrammspeicher里读取指令

3、把指令放入微指令寄存器(Mikroinstruktionsregister)简写(MIR),它是信号缓冲器,它控制整个系统的信号

4、控制信号执行所有单元里的操作(Rechenwerk, Busse, Leitwerk)

这个微程序的执行是时间脉冲控制的(taktgetrieben)。就是说有个外部的时间脉冲Takt,它控制这个控制信号什么时候和多长时间是有效的。在时间脉冲里只有0和1的互换。这两个值代表一个给定的时间点。一个时间脉冲循环对应了2个“上升边”中间的时间。从0变成1是“上升边”,从1变成0是“下降边”。人们使用时间脉冲,因为Schaltnetze 和 Schaltwerke需要一定时间来把生成所有信号。让这些信号传递需要其他的Schaltnetze/werke。

12.3.5 Daten- und Adressbus

下面介绍数据总线和地址总线。在计算机里有很多数据和地址线路。他们和Leitwerk, Steuerwerk, E/Awerk, Speicher相连接。对于大量原件来说,点对点的连接是不实际的。所有就有了总线。在一个总线里,许多原件可以“挂”在上面读取信息。通常情况下,总线会有数据线路(Datenpfaden),地址线路(Adressenpfade)和控制线路(Kontrollpfade)。除了这种读取,还有存取控制(Zugriffkontrolle),它为了知道谁被允许进行读写操作。从而避免冲突。

image-20200102134631277

Tri-State Bus

Tri-State Bus是可以多重使用的管道。我们使用Multiplexer来实现它。\(S_{i}\)是控制信号,\(D_{i}\)是数据,\(OE\)是Output Enable,表示可以被输出,它由控制信号来控制。在同一时间只有一个数据在Bus上

image-20200102141745601

12.3.6 Mikroinstruktionen

为了实现微指令,我们需要微指令寄存器(Mikroinstruktionsregister)他是一个很长的为1Bit信号控制,多Bit信号控制,和Immediates而造的Bit数组。

image-20200102142603823
  • 1 Bit Signal 它是比如Kontroll-Bus-Signale (R/W), 单个门的输出,Tri-State Bus控制
  • Mehrbitsignale 通常是Dekodierer和Multiplexer的输入
  • Immediates 这是微程序的地址,或者常数

对此有个编码的对微指令的展示,它不是控制单个信号,而是控制信号组。优点是对存储空间的减少和编程友好性的提升,缺点是解码会耗时间,对硬件的要求高。

Mikroinstruktionsformat

微指令格式可以分为3个种类。水平(Horizontal)(不加密),垂直(Vertikal)(严格编码)和,类水平(Quasihorizontal)(最优编码)。

image-20200102143819038

其他形式的图:

image-20200102144000500

Mikroprogrammspeicher(MPS)

微程序被作为微代码(Mikrocode)放在微程序存储器中。在我们的例子里,它在Leitwerk中。

image-20200102145029938

为了微指令可以被调用,需要可访问(adressierbar)的MPS。它只有相对少的地址,但空间却很宽(这里是80Bit)。它有不同的实现方式:

  • ROM(Read-Only Memory) 固定的程序
  • PROM(Programmable Read-Only Memory)可以重写写入的ROM
  • RAM(Random Access Memory) 在程序开始的时候重新写入
  • Hybirdoptionen 这里放着ROM的基础程序,开始的时候会被复制入RAM

Mikroinstruktionswort

之前提到过微指令的字宽是80Bit。它对应了MPS的一行。Bit数组的每个元素是对架构中的每个元素的直接控制信号。

image-20200102150428686

  • Bit 0 是控制 Hauptspeicher
  • Bit 1 是控制Instruktionsregister的读取
  • Bit 2 - 5 是控制ISA中Befehlszähler的输出和增加
  • Bit 6-17 和 Bit 58-73 是Leit 和Steuerwerk的常数
  • Bit 18-21 和 Bit 49-57 是对AM2910(Leitwerk)和AM2901(Rechenwerk)的指令控制,他们含有编码过的指令
  • Bit 22 和 Bit 37-48 是Datenpfaden的控制
  • Bit 23-36 是AM2904(Steuerwerk)的信号控制

12.4 Mikroleitwerk

下图是Mikroleitwerk的位置

image-20200102152307789

Mikroleitwerk的任务是拿出下一条微指令。微指令计数器(Mikrobefehlszähler)会计算出下一个值。它会放在Mikroinstruktionsregister里。下一条指令的选择和控制语句Steuerungsbefehl和控制信号Steuersignalen(条件)有关。控制信号放在MIR的Bit 18-21 里因为有4Bit的空间,可以放16种指令。它决定了下一条指令在哪

image-20200102152601179

12.4.1 Mikroprogrammierung 的想法

微编程实现了ISA中的每个单个指令。每个ISA指令都有一个编码(Opcode),它把指令的功能和操作数都编码进去了。那么这个Opcode就必须导致Mikroprogramm的运行。那么就存在一个在宏编程(Makroprogrammierung)和微编程(Mikroprogrammierung)之间的接口。宏编码(Makro Opcode)接管了Leitwerk的控制。为了知道这个编码在哪,需要宏指令寄存器(Makro Instruktionsregister)。它也是微编程的一部分

image-20200102153535336

通过这个接口,微架构就确定了ISA,然而只是基本指令格式,却和ISA有紧密联系。在我们的例子中只用了8Bit的指令,当然有更长的可能性。

12.4.2 Makro-Mikro Opcode Abbildungsspeicher(宏编码和微编码映射存储器)

每个ISA指令表达了一个微程序。当然,可能有不止一条指令。所以1对1的映射是没有意义的。映射存储器把一个ISA Opcode 映射到微程序地址上。它由此定义了每个ISA指令,所以映射存储器是ISA的中心定义。

image-20200102155117619

我们可以这样想象这个过程。首先在Instruktionsregister里有个ISA指令。它的Opcode传入映射存储器,传出一个地址,然后传入Leitwerk。于是我们就可以读取下一条微指令。更准确的,JAMP指令是管理这个映射的。

image-20200102155140984

12.4.3 Mikroleitwerk的结构

先上总图

image-20200102161702121

LIFO栈是一个专门放Mikrobefehlszähler的栈。它实现了内部子程序的调用。它与普通栈有些区别:

  • 固定深度(5)
  • 没有参数
  • 没有局部变量

它导致了不能使用广义的子程序。

Addressen Multiplexer

它的任务是找出下一个微指令的地址。它被INST PLA控制(Instruktion Programmable Logic Array)。地址有5个来源:

  • 空常数 Null Konstante ,它跳转到MPS的开始位置
  • 外部地址 Externe Adresse 它可能是映射存储器传出的ISA Opcode的地址,还可能是MIR的常数
  • 微指令计数器的地址+1
  • 计数器寄存器 (不相关)
  • 上层栈元素 (调用子程序)

INST PLA

它是一个Schaltnetz,它定义了Leitwerk的功能。它的输入由4 Bit的从MIR来的指令和一个Flag(CCEN Conditon Code Enable),它决定条件比特位JKonditionsbit(CC Conditon Code)是否被阅读,构成。它的输出是对Multiplexer,栈或者计数器的控制信号。当然外部控制信号是可能的。下表是PLA的指令表。

image-20200102163637101

有些指令我们深入讨论。

12.4.4 PLA中重要指令的实现

JAMP

image-20200102163857925

它从MIR中出来,加载到PLA上(绿线),它决定了从映射存储器中来的指令。从IR里来的Opcode通过映射存储器的解码,再经过地址选择器Adr- MUX放在MPS的开始地址上。同时微指令计数器MBZ会增加(蓝线)

image-20200102165427231

CONT(Continue)

image-20200103142938639

它会把MBZ+1,最终也就是+2

image-20200103143137695

JZ(Jump Zero)

它跳转到地址0处,并把栈删除。

image-20200103143245850

我们可以这样想象,它从MIR出来到PLA里。传入MUX的值是NULL,然后从MUX里出来的值会变成0。以至于到MPS里的地址也会是0。

image-20200103143827143

CJP(Conditional Jump)

它会跳转到一个地址,如果再CC里的条件成立。

image-20200103152300684

它从MIR中读取指令到PLA。如果CC=1(失败),那么值从MBZ里来,相当于CONT。如果CC=0,那么还有个决定。它取决于OE En的值。如果它是“PL”,那么下个值从MIR的地址区域来。如果是“MAP”那么从映射存储器里来。最终加载到MUX再到MPS。

image-20200103152604367

CJS

它根据条件调用子程序

image-20200103152649404

如果CC=1,那么CONT。如果CC=0,把下一个地址存再LIFO栈里。然后看OE En。如果是“PL”,读取MIR的地址区域的值。如果是“MAP”,读取映射存储器的值。

image-20200103152843121

CRTN(Conditional return from subroutine)

根据条件从子程序返回。

image-20200103152935418

如果CC(Condition Code - Flag)=1,那么CONT。如果CC=0, 那么读取LIFO栈的最后一个值(POP),然后通过MUX加载到MPS里。

image-20200103153100967

12.4.5 Leitwerk的实现

我们由一个5个输入源的地址选择器(Adressen Multiplexer),还有个INST PLA,它告诉MUX地址从哪里来。

  • PLA是Schaltnetz,通常由一个列表实现
  • 输入时2910指令(CC从2904来),输出时一个MUX控制信号,OE Enable 和一个栈控制

此外还有Schaltwerke给寄存器(MBZ,栈,栈指针)和栈控制

12.4.6 Makro/ISA 指令读取

读取ISA指令用到JAMP指令,它通过从IR读取的Opcode的选择调用了一个子程序。那么有个问题:下一条指令怎么被加载?为了实现它,宏指令计数器(Makro Befehlszähler)会放在地址总线上。地址总线包含下一条指令的地址,它是从数据总线的存储器中被读取的。为了实现它,存储器必须允许”读取“操作。最后,IR才能从地址总线读取值。

12.4.7 存储器和指令计数器控制

为了把正确的值放在地址总线上,指令计数器必须自增。也即是说,我们必须控制BZ和Speicher。为此工作的是MIR的Bit 0-5。

image-20200103154622805

这些Bit控制宏指令计数器,IR和存储器

image-20200103154723160

存取信号(Zugriffssignale)(存储器和BZ)

MIR的前6Bit是给存储器和Makro BZ的存取信号。具体值在下表

image-20200103154936869

IFETCH

读取下个指令的具体流程是IFETCH规定的。它是一个特定的微程序,它一直在地址0处。这个指令包含了3个地址

  • 地址0:读取主存储器(Hauptspeicher)(MWE = R),BZ会被加载到地址总线上(BZ_EA = E), 执行CONT
  • 地址1:读取数据总线的值(IR_LD = L),执行CONT
  • 地址2:执行JAMP

在每个地址最后“无其他影响”会被设置。每个子程序执行它的指令,并且之后跳回地址0

12.5 Mikrorechenwerk(微计算单元)

微计算单元的任务是代数和逻辑运算。下面是微计算单元的位置:

image-20200103160025724

Am2901在我们的例子里是一个4Bit的计算单元。由一下部件组成:

  • 位移单位(Schiebeeinheiten)
  • 16个寄存器
  • 8个指令(3 Bit 编码)
  • +/- 和逻辑操作

这种单个的计算单元可以继承成任意位宽的计算单元。下面是4个计算单元的集成,每次会把溢出传递过去。

image-20200103160429979

为了仔细介绍其中的原件,我们将使用下图:

image-20200103160549973

12.5.1 计算单元的ALU

下面我们介绍Arithmetic Logic Unit(ALU)或者叫计算单元。它负责自己的计算。共有8个指令可用。他们通过3Bit可以在微指令被控制。ALU有2个入口(R,S),1个出口(F),还有很多控制信号:IN(Carry), OUT(Carry, Zero, Overflow)。

image-20200103161608330

之前提到有8个指令。下面是他的表格。

image-20200103161701984

可以看到,只有简单的运算才有指令。由此才可能通过简单少量的逻辑去实现。为了计算更复杂的程序,需要写一个微程序。

12.5.2 计算单元的数据连接

计算单元(Rechenwerk)有一个外部的出口(Y)和入口(D)

image-20200103162001885

  • 外部的出口可以是在数据总线,或者在地址总线
  • 通过出口可以实现操作数和结果的互相传递
  • 外部出口是DRAM(Datenbus)输入的接口,或者常数。
  • 无论是入口还是出口,数据的来源和去处都是通过MIR的控制信号控制

从下图可以推断出,数据在计算单元是怎么传递的

image-20200103162509447

12.5.3 计算单元的寄存器

寄存器的位置在2门寄存器单元(Zwei-Tor Registerwerk)。那里有16个4 Bit的寄存器。并且有1个入口和2个出口。它使得每个时间脉冲可以有2个读取过程

image-20200103162810356

使用的寄存器有2种

  • 通过Mikro IR :4 Bit 在MIR里
  • 通过Makro IR:3 Bit 在IR里(最高Bit一直0),也就是在ISA里只有8个寄存器可见

在MIR里有2个区域(ASEL,BSEL),它们决定了给A/B 入口的值从Makro IR 还是从 Mikro IR里来。A-MUX/B-MUX把对应的寄存器地址传递到计算单元。

image-20200103163437132

12.5.4 ALU 操作数(Operanden)

ALU的操作数源的选择是通过一个MUX的,它通过3 Bit(8个可能的控制)微指令控制的。MUX有5个源

  • 外部数据入口
  • A 寄存器出口
  • B 寄存器出口
  • Null
  • Q 寄存器 (位移结果,或者一个先前的ALU结果)

除此之外ALU操作数的MUX有2个出口(R/S),它走入ALU。

image-20200103164003169

12.5.5 操作数选择Operandenauswahl

在微指令中可以给选择给ALU的操作数,但没有所以的可能性,原因是为了避免不需要的复杂度。见下表。

image-20200103165234402

将ALU的操作数和从MUX里来的操作数结合,我们得到了下面的操作集合:

image-20200103165407685
12.5.6 位移单位(Schiebeeinheiten)

位移单位是用作数据的预处理和后处理。它也是3Bit指令控制的。控制位移单位的信息也控制从A/F到Y的映射。也就是说,它给出了是A还是F应该映射到Y。F的回循环(Loopback)到寄存器B也会被它控制。

image-20200103170045945

12.5.7 位移和选择指令

这些指令决定,F的值映射到寄存器B还是Q里。此外它还决定Y,F或者A被写入。信号控制会从位移指令解码。(表格灰色部分对考试不作要求)

image-20200103170457014

12.5.8 计算单元的实现

计算单元可以被许多步骤实现:

  • ALU可以作为Schaltnetz被实现
  • ALU操作数选择可以用一个大的MUX作为Schaltnetz实现
  • 寄存器是Schaltwerke
  • 选择和数据线路:用门(Tore)和MUX
  • 位移单元是Schaltnetze,并且附带寄存器和Schaltwerke
12.5.9 多个片段的组合

一个片段是计算单元,多个计算单元可以相互连接。组合的结果是:每4 Bit有一个自己的状态Bit(Statusbit)。它们会连接起来:

  • Zero Flag:所有片段Null
  • Carry Flag:必须传递
  • 从高位计算片段来的负Bit

这些组合必须接到Leitwerk里。还有一个结果是会在层之间有旋转指令(Rotierbefehlen)。这些必须通过所有的片段。如果我们想要带上Carry旋转,那么最后一位Carry Bit必须要包含再里面。RAM和计算单元的寄存器Q会需要这些命令。

image-20200103171636838

12.6 Wortrandlogikbaustein

也叫(Am2904)它是必需品,当我们连接多个组件的时候。

image-20200103191557996

最重要的元件是两个状态寄存器(Statusregister)

  • 机器状态寄存器(Maschinenstatusregister)(MSR) :ISA编程者可见
  • 微状态寄存器(Mikrostatusregister)(\(\mu SR\)):ISA编程者不可见

image-20200103191917913

它们是独立被控制的并且按Bit位加载来自计算单元的状态信号(Statussignalen)。可能的状态信号如下:

  • 溢出(Überlauf)(OVR)
  • 多余部分(Übertrag)(C)
  • 符号(Vorzeichen)(N)
  • 空记号(Null-Anzeige)(Z)

状态信号通过4个状态入口(Statuseingang) \(I_{C},I_{N},I_{Z}\)\(I_{OVR}\)被加载

image-20200103192425755

通过允许比特位(Enablebits)可以避免不预期的改变。它们是\(\overline{CE_{\mu }}和\overline{CE_{M}}\)通关建立的测试逻辑(Testlogik)可以访问状态寄存器。源于此的条件代码会在CT输出,它和在Mikroleitwerk中的测试入口\(\overline{CC}\)绑定。为了知道,条件访问从Mikrostatusregister还是从Maschienenstatusregister来,需要通道\(I_{4},I_{5}\)。当\(I_{5,4}=01(MI)\)时是从MikroSR被读取,\(I_{5,4}=10(MA)\)时时从MaschienenSR。通过\(I_{0}到I_{3}\)会在16种条件中选择1个条件。

image-20200103193604880

从指令比特位(Instruktionsbit)\(I_{0}到I_{3}\)中选择的条件可以在下表中看到。这个表格可以在两个A-B操作之后用作比较。它会在之后传入在Mikroleitwerk的CC。

image-20200103193933526

为了实施算数操作,我们需要多余数据的信号(Übertragssignale),它会从Am2904传入微计算单元。

12.7 微程序的例子

12.7.1 IFETCH

之前提过,IFETCH是拿到下一条指令的地址,并且跳转到这个地址。这些在3个时间脉冲中实现:

  • Takt 1: BZ 被放到地址总线,并且有对存储器的读取 Bit 2-> E/H
  • Takt 2: IR 被打开,下一条指令的数据会从数据总线被读取。
  • Takt 3: 会执行JAMP指令,跳转到那个指令的地址,并执行那个指令

image-20200120180639308

对这个功能,只有开头6个bit和AM2910指令是重要的。在整个过程,它是0,Bit为被设置成“R”,意思是可以从存储器读取。

  • Takt 1:第2个Bit 被设成 "E" 。这使得BZ的内容被放在地址总线,对应的数据会从主存储器放到数据总线
  • Takt 2:第1个Bit被设置成“L"。使得当前在数据总线的值会读取到IR里,此外第3个Bit被设成”I“,意思是BZ增长
  • Takt 3:所有的控制Bit被设成”H“,为了避免不必要的激活。然后执行JMAP
12.7.2 简单加法

下面我们看两个寄存器(RA和RB)的简单加法,结果存在RB里

image-20200120181831602

接下来会对每一个Bit详细说明

  • 第一个相关的格子是”AB“。这个决定了它的操作数。它的意思是操作数”R“是寄存器A,操作数”S“是寄存器B
  • 下一个格子”ADD“,这两个操作数会被加起来
  • RAMF可以决定,操作的结果从F出口里出来,放到寄存器B里
  • 有IR的两个格子说明了A-MUX和B-MUX从哪里取出地址。设置成IR说明地址从IR里读取不是从MIR里读取。
  • 下一个”L“使得机器状态寄存器(Maschinenstatusregister)可以被改变
  • ”CJP“意思是有条件跳转。由于CCEN Bit 被设置成PS(PASS),会无条件跳转
  • IFETCH说明,会跳转到0,并且读取下一条指令
12.7.3 从存储器去寄存器

在这个例子中,我们将在一个存储空间(Speicherzelle),在寄存器A里的值,写入寄存器B。

image-20200120193718159

为了实现这个指令,我们需要2个时间脉冲:

  • Takt1:把RA的值放到地址总线,于是我们就在存出器中得到了RA的地址。为此我们需要操作数ZA,于是我们得到了RA的存取和0。这个值被加起来放到数据总线上。(YMUX=AB)
  • Takt2:我们把这个值从数据总线读取到RB里,为此我们把信号从KMUX设置到DB。于是给计算单元的下一个值就从数据总线出来了,当我们此外操作数选择(Operandenauswahl)设置成”DZ”。接着我们使用位移指令“RAMA”或者“RAMF”,它把值从ALU的F出口出来,放到B寄存器中。
  • 接下来跳转到地址0处执行IFETCH
12.7.4 Conditional Jump Carry

下面我们看如果Carry被设置了的有条件跳转。这个指令跳转到一个已知的地址,当Carry Flag被设置。否则就接着执行。

image-20200120194821738

在这个例子里,我们假设第一行对应地址50并且立即值(immediate-Wert)是52

  • Takt1:会用UGTEQ检查,CF有没有被设置。把CCEN-Bit设成“C”,为了说明,这个条件已经检查过了。如果CF被设置,那么会跳转到地址52处
  • Takt2:(Adr.51)如果没有被跳转,会接着执行地址51。目前BZ还显示着立即值,它却不被需要,所以BZ必须增长(3. Bit)。然后执行一次IFETCH使程序继续执行
  • Takt2:(Adr.52)如果跳转了,程序必须从52处继续执行,BZ必须放到地址总线上,于是下一个Takt可以读取下一个指令
  • Takt3:最后一步BZ从地址总线被读取。因此现在存储空间的数据在地数据总线的BZ的地址上。这个值会重新加载到BZ上,然后执行IFETCH。

13 笔记ALU编程

  1. CJP 指令
  • \(\overline{CCEN}=PS\) CJP指令从D入口读取地址,它是BAR-Feld绑定在一起
  1. 主存储器
  • \(\overline{MWE}=R\) 允许读取
  • \(\overline{MWE}=W\) 允许写入
  1. 计算单元
image-20200122083240856
image-20200122083301808
image-20200122083353245

从A和B入口可以选择使用16个计算单元的内置寄存器

  1. 总线
  • 下面的元件可以放在地址总线:
    • BZ 通过 \(\overline{BZ\_EA}=E\)
    • Rechenwerksbaustein 通过 \(\overline{ABUS}=AB\)
  • 只有主存储器可以读取地址总线上的数据
  • 这些元件可以放在数据总线上:
    • 主存储器 \(\overline{MWE}=R\)
    • BZ 通过\(\overline{BZ\_ED}=E\)
    • Rechenwerksbaustein 通过\(\overline{DBUS}=DB\)
  • 下面的元件可以从数据总线上接收数据
    • 主存储器 \(\overline{MWE}=W\)
    • BZ 通过\(\overline{BZ\_LD}=L\)
    • Rechenwerksbaustein 通过\(KMUX=D\)
    • IR 通过 \(\overline{IR\_LD}=L\)
  1. 模板:

CI0/CI1:

image-20200103165407685

使用r0:

  • BSEL/ASEL:MR
  • RA/RB Addr :0

从DB里读取:

  • KMUX = D

结果到RB:

  • DEST: RAMF
  • Y-MUX: HH

结果到Adressbus:

  • DEST: NOP
  • ABUS: AB

第14章 物理层Physikalische Schicht

在这一层会把抽象的通过符号表示的电子元件通过半导体实现。半导体是一个化学元素,它的导电性在导体和绝缘体之间,所有它比较适合做晶体管(Transistor)。关于半导体,我们主要用硅。

通过添加有针对性的杂质,可以实现在低温下的导电性能。我们通常用p添加(p-Dotierung, Aluminium),或者n添加(n-Dotierung, Phosphor)。这些添加在一个特定的排序,通过电场,可以实现晶体管。

image-20200121121712803

14.0.1 晶体管作开关(Transistoren als Schalter)

晶体管是最重要的开关元件在微架构里。为什么晶体管这样工作取决于半导体的自身品种。在n导区域和p导区域(n-leitenden und p-leitenden Zonen)的接触表面(Berühungsfläche)存在一个隔离层(Sperrschicht),它使得通常情况下没有电流流过晶体管。这个隔离层只会通过一个控制电流或者控制电压被提起,只有这样,电流才可以流过。(一个晶体管由2个pn通道(pn-Übergängen)构成)最常用的晶体管叫MOS晶体管(MOS-Feldeffekt-Transistor) (MOS -> metal-oxide-semiconductor)

image-20200121122743473

14.0.2 MOS晶体管

一个MOSFET有2个状态:

  • 开 Schalter offen

    通过一个正电压加在隔离门(isolierten Gate),电子从硅底层(Silizium-Substrat)被抽到表面。在2个n-区域中间(Source, Drain)由此产生了狭小的n导层(n-leitende Schicht)。通过这个狭小的层可以使得电流从源流向汇点

  • 关 Schalter geschlossen

    当门电流远离的时候,导电的通道消失,晶体管不再能导电

通过门电压可以控制Drain电流的有和无

14.0.3 半导体生产过程

半导体的生产过程可以简单分为8个步骤

image-20200121124328016

  1. 我们从一个硅层开始
  2. 上面图上光刻胶(Fotolack)
  3. 未完成的晶体管被光照,上面会放一个模板(Schablone)在光源和晶体管之间
  4. 通过光照在光刻胶上产生了洞
  5. 然后我们会用有腐蚀性的(ätzend)的东西,在这些窗口洞上的氧化硅移除
  6. 接着光刻胶被移除,之后就可以看到晶体管的基本结构
  7. 通过扩散(Diffusion)可以产生p层和n层(p- und n- Schichten)
  8. 在之后的几步就有了完成的晶体管

第5部分 Gatter 层

第15章 Schaltnetze

首先我们讨论Schaltnetze,这个没有状态。它只有入口值,通过一个过程函数转换成出口值。

image-20200121125410324

15.0.1 Schaltfunktionen

简单来说,n元开关函数(n-stellige Schaltfunktion)是一个函数,它是由{0,1}的集合构成的。也就是\(\{0,1\}^{n} \rightarrow \{0,1\}\),我们有n个输入信号,1个输出信号

例子:

  • 单元开关函数(Negation)

    neg: {0,1} -> {0,1}

    neg(0) = 1, neg(1) = 0

image-20200121125825484

  • 二元开关函数(Konjunktion)

    image-20200121125907359

15.0.2 1集和0集

  • Einsmenge : 所有输入源的集合,使得输出的集合是{1},比如neg = {(0)}, konjunktion = {(1,1)}
  • Nullmenge: 所有输入源的集合,使得输出集合是{0}, 比如konj = {(0,0),(0,1),(1,0)}

15.0.3 Gatter - Elementare Einheiten

门(Gatter)会通过开关函数被描述。Konjunktion 用 \(\&\) , Disjunktion 用 \(\geq1\)

  • Konjunktion(UND)

    image-20200121130543227

  • Disjunktion(ODER)

    image-20200121130608187

初次室外还有Not-And(NAND) 和 Not-Or (NOR)门被使用

15.0.4 Negation

为了表示取反,我们在入口和出口引入了一个小圆圈,在出口画上了一条线

image-20200122065944215

15.0.5 另一种符号

当我们在网上看各种电路图,它们会经常使用其他符号

image-20200122070108453

15.0.6 数据流网Datenflusspläne

许多门符号可以组成数据流网,它还被叫做逻辑电路图(logische Schaltpläne)。

比如\((x\wedge y\wedge z)\vee(x\wedge y\wedge \bar{z})\)

image-20200122070447878

15.0.7 布尔代数

一元运算符:\(\neg\)

二元运算符:\(\and ,\lor\)

15.0.9 一些公式

image-20200122071137657

image-20200122071201285

15.0.10 对偶Dualität

每个公理都有是一对的\((\and,\lor,0,1)\)\((\lor,\land,1,0)\)

接下来我们会使用下面的写法:

  • \(\bar{a}\) 替代\(\neg a\)
  • 优先级:not ,and, or
  • \(ab\) 替代 \(a\land b\)

15.0.11 Schaltnetz

一个Schlaltnetz是一个功能单元,它实现了一个或者多个开关功能(Schaltfunktion)。所有我们可以把一组开关功能用\(\{0,1\}^{n}\)来表示

image-20200122071922359

15.0.12 加法器

它由4个入口(2*2Bit)组成,用来双重计算加和。

image-20200122072102611

从入口信号集合我们可以看出,它有多少个定义的点。所以一个n元的开关函数\(2^{n}\)个定义的点(比如在4个入口,就有16种不同的数据可能性)

15.0.13 解码器Decoder

对于入口为P的解码器,最多可以有K个出口,\(k\leq 2^{p}\)

image-20200122073232043

经典的解码器有:

  • 在存储器中的地址解码器、
  • 在控制单元和计算单元的指令解码器

比如一个2Bit的解码器长这样

image-20200122073613654

15.0.14 编码器Encoder

他是解码器的对立面。K个入口,P个出口,\(k\leq2^{p}\)

image-20200122073801671

比如一个2Bit的编码器:

image-20200122073916749

15.0.15 选择器和反选择器 MUX/ DEMUX

它们被用来选择那些入口信号可以出来。对于一个n源\({log_{2}}^{n}\)有个控制信号。

image-20200122074338651

比如下面是一个选择器/反选择器的具体实现:

image-20200122074457158

15.0.16 只读存储器Festspeicher (ROM)

一个结合逻辑电路网的例子是ROM。在这里我们用一个n元函数映射到m元上。

  • 我们有n元的地址
  • 我们有m元的数据单元

我们可以组合解码器和编码器

image-20200122074831662

另一个可能的表示是:

image-20200122074925237

15.0.17 电路函数Schaltfunktion和电路代数Schaltalgebra

一个Schaltnetz可能由多个Schaltfunktion组成,所以每个输出信号都由一个Schaltfunktion。怎么来表示它呢?我们可以用DNF来表示它

image-20200122075509948

对应的DNF是:

image-20200122075531091

15.0.21 代数组合

DNF和KNF过于繁琐,如果我们之间在电路网上实现的话。所以要简化DNF和KNF。比如下面这个简化

image-20200123070426415

现在问题是,这个形式是否更好了。DNF的优势是穿过的电路开关都是一样的。当我们化简过后实现它,会出现通过4个元件的路径,他会导致不可预见性的问题

image-20200123070904632
image-20200123070934418

15.0.22 电路网简化

我们想要:

  • 少的电路元件
  • 少的入口
  • 少的层次(Stufen)
  • 短的电路
  • 便宜的电路元件

然而没有简化电路的有效算法。简化电路的开始是融合(Verschmelzung),比如下面:

image-20200123071253008

为了简化电路,我们需要下面的定义:

  • k元单项式(k-stelliges Monom) : k个单项式的KNF形式
  • Minterm:n元单项式
  • Disjunktives Polynom: 几个单项式的DNF形式
  • Argumentbereich 1位导致不同: 相邻的\((01011,00011)\)
  • 这个相邻的概念可以从多项式看出:\(x_{1}\bar{x_{3}}x_{4},x_{1}x_{3}x_{4} \rightarrow x_{3}\)不相关
  • Verschmelzungsmenge消除集合:n元单项式变成k元单项式

比如n=3

下面这个式子被简化:

image-20200123072002993
image-20200123072028209

我们只能合并有1项不同的两个式子。或者我们可以扩展,在后面加上相同的元素

image-20200123072221772

另一个简化可能性是图象式简化,这里我们可以应用编程,对于简化我们需要n维立方体:

image-20200123072407882

15.0.23 KV图

另一个展示可能性式KV图比如下面的

image-20200123073317798

在这里,每一个长方形都可以变成一个变量。

简化

现在我们可以堆每个图里的单个值用1个n元的Minterm表示。这却不是很高效,因为:

  • 标记所以在DNF/KNF里的Minterme/Maxterme
  • 当式子可以消融,那么就可以组合

构造

KV图没有固定的格式,比如下面式2个变量的,有8种不同的KV图

image-20200123073950519

所以我们可以用以下几点:

  • 当KV图式正方形的时候(变量是偶数个),可以向右对称
  • 这个新的地区就对应了新的变量
  • 当KV图是一个长方形(奇数个变量)那么向下翻
  • 新地区是新变量

比如下面这个是n=3和n=4

image-20200123074410991

相邻特性

在KV图里,我们也观察相邻特质。

image-20200123074502334

比如n=4

我们想要简化下面的式子:

image-20200123074529898

于是我们可以这么做:

  • 所有的Minterme放到KV图里
  • 构造2,4,8...的组
  • 这些组合必须相邻
  • 由这些组合我们有新式子
  • 那些不能简化的Minterme,我们叫蕴涵项Primimplikanten
  • 我们的目标是从蕴涵项里得到最小DNF
  • 结果不一定唯一
image-20200123074834365

于是有:

image-20200123074912208

15.0.24 函数组Funktinosbündeln的优化

现在我们会了从最小DNF里得到电路图。当我们看函数组中每个函数的共同的电路元件,它经常可以组合在一起。这些经常不造在最小DNF上。为了简化函数组,我们要用编程方法。比如我们有下面2个式子:

image-20200123075400091

那么每个简化就没有意义。我们有它们两个的电路图:

image-20200123075441751

如果我们组合优化,那么可以得到下面的

image-20200123075545724

15.0.25 NAND和NOR

大部分电路技术用NAND和NOR因为它比较好实现。而且它功能齐全,可以表示所有运算。

image-20200210152316968

这里需要多次用摩根定律。

NAND电路网使用

最小多项式可以变成NAND/NOR的形式。它很便捷,但不是最小化的

NAND用在2bit加法器上

这是加法器的电路图,下面是变成NAND的

image-20200210152810411

15.0.26 Hazards

每个电路信号在元件之间传播的速度有限

  • 传导 -> 没10cm 需要1ns 纳秒
  • 门需要0.05 ns

这个在门里的运行速度会受很多因素波动

  • 温度
  • 入口信号的值
  • 导线的长度和种类
  • 信号传递者的种类和数量

第16章 Schaltwerke

16.1 Schaltwerke的实现

在Schaltwerk里,结果Resultatsfunktion和状态函数Zustandfunktion会通过Schaltnetz实现\((\lambda,\delta)\)

每个反馈路的延时会通过\(\tau\)来实现。在某时刻\(t_{i}\)的出口和状态的值可以通过下面的函数表示 \[ a^{t_{i}}=\lambda(e^{t_{i}},q^{t_{i}-\tau}) \\ q^{t_{i}}=\delta(e^{t_{i}},q^{t_{i}-\tau}) \] 图像化展示如下:

image-20200210154055007

16.1.1 Schaltwerke 和有限状态机 endliche Automaten

  • 有限状态机:很重要的定义
  • 实际应用:状态空间大
  • 两种状态机: MEALY Automaten MOORE Automaten
  • \(\delta\)\(\lambda\)的表示:状态机表和状态机图

Mealy状态机

一个有限的Mealy型状态机可以用六元的元组表示 \[ M(E,A,Q,\lambda,\delta,q) \] 这对应了下面的入口值:

  • $ E=e_{1},...,e_{m}$ 入口符号的集合
  • $ W=a_{1},...,a_{r}$ 出口符号的集合
  • $ Q=q_{1},...,q_{n}$ 状态空间
  • \(\lambda :E\times Q \rightarrow A\) 结果函数
  • \(\delta :E\times Q \rightarrow Q\) 状态函数
  • q: 开始状态

其中,E,A,Q都是有限的,下面是它的状态机表

image-20200210154950065

左边对应了状态,上面对应了输入值。2,a分别是会被调成2,输出a。下面是状态机图

image-20200212121648366

Moore状态机

它和Mealy状态机很像。。 \[ N=(E,A,Q,\lambda,\delta,q) \] 状态机表:

image-20200212121941845

我们可以这样理解:

  • 当前的状态是2,那么输出是a
  • 如果读入了x,那么状态机保留状态2
  • 如果读入\(y \lor z\),那么状态机的状态到1
  • 这也对应了其他情况

下面是状态机图

image-20200212122426953

MOORE和MEALY的等价性

输入序列相同,那么输出序列也相同。所以可以从MOORE状态机变成一个MEALY状态机。反之也可以

RS触发器(Latch) ,NOR实现

下面我们看RS Latch,它有两个门Reset 和 Set。2个状态 \((q_{1},q_{2})\)和2个出口\((a_{1},a_{2})\),\(a_{2}\)是反转出口。RS触发器是用来存储单个Bit的

image-20200212123346050

它的功能我们可以如下想象:

  • 当R和S口都设为0的时候,它是安静状态(Ruhezustand),保存原来的状态
  • 当S设为1的时候。\(x\)出口会被设为1
  • 当R设为1的时候,\(x\)会被设为0
  • \(\bar{x}\)\(x\)总是相反的
  • S=R=1这种情况不被允许

避免无定义的状态

为了避免S=R=1这个不被定义的状态,我们需要其他的逻辑。

  • 我们在R入口里放一个UND门,并且和S相连
  • 在S入口里放一个UND门和R相连
image-20200213204357640

JK触发器(Jump and Kill)

JK触发器以RS触发器为基础。它解决了RS触发器未定义的S=R=1的状态。在JK触发器中,S=R=1会反转它的状态\(x:=\bar{x}\)

image-20200213204741141

D触发器

D触发器也是以RS触发器为基础。我们只有一个D入口,还有时钟信号入口(enable)

image-20200213204919782
  • D入口信号分成2路,上面的被反转一次进入r入口
  • 下面的进入s入口
  • 时钟信号控制它是否会接着往下传递

存在的困难

Schaltnetze和Schaltwerke不是开始就稳定。这个过程需要时间,由此就会产生混乱

16.1.2 平行和同步Parallelität und Synchronisation

为了定义平行,我们需要定义过程Prozess:

  • 可以以时间段分割处理
  • 分割把一个过程分成若干个步骤
  • 过程的例子有:程序解释和在Schaltwerk里的持续的信号齿面

在一个过程里有许多分割的处理片段。

同步

解释同步之前,有必要了解上面是2个过程的异步

如果两个过程异步,那么它们没有时间先后关系。 就是两个过程是不需要协调的,\(P_{1}\)可以不用等\(P_{2}\)做完了再继续

同步化就是把两个过程同步的措施

异步过程的例子

  • 两个程序跑再两台不同的计算机上
  • 两个电话信号没有关系

同步过程的例子

  • 铁路火车
  • 两个程序的协作

函数性

一个计算是函数的,当它的输入唯一可确定并且结果是可以重复产生的。(例子:序列化的程序,它没有与其他交流)所以很多相同关联交流的异步的过程是非函数的,就算它们单独是函数的。它包括两个过程访问同一个变量

例子:相连的Schaltnetze

为了理解函数性的准则,我们看一个例子。下面两个函数的Schaltnetz连载一起是否还是函数的

image-20200213211223010

下面的事情可以发生:

  • A比B慢。于是有可能发生:比如xyuv是\(0100 \rightarrow 0011\) 。那么z会成为1,当然这对于0011的输入是不可能的
  • 也就是说,z可以取决于它的历史纪录,也就不是函数的
  • 但是加了一段时间后,它就又会变成函数的,那么就可以想到时间脉冲来解决

通过时间脉冲来同步

为了使得Schaltnetze过了一段时间后再次协调,可以加入时间脉冲。在一个计算机里很多个过程会通过小的步骤同步。从技术上来说,几乎所有的Schaltwerk都是通过时间脉冲来同步的。

同步的Schaltwerk

一个同步的Schaltwerk就是它的反馈通过时间型号来控制。它控制中间的小状态。一个时间型号通过入口信号来控制也是有可能的

同步的Schaltwerk也有很多相对的缺点,它比较慢而且耗费高。但安全且正确的功能比它的缺点要重要很多。当一个计数器有时钟(Taktung),它被称为FlipFlop。

例子:齿轮计数器Flankenzähler

一个同步的Schaltwerk的例子是所谓的齿轮计数器。它是如下定义的:

  • 要求的是初始状态e=0的Schaltwerk
  • 今后应该由开始的0推导而出
  • 每第4个齿轮应该输出1

一个齿轮是从0到1的跳转或者从1跳转至0

image-20200213212755243

16.1.3 Taktung

FlipFlop

加入时钟控制型号的Latch

RS-Flipflop

image-20200217170230765

JK-FlipFlop

image-20200217170714739

D-FlipFlop

image-20200217170839582

Master-Slave-FlipFlop

为了避免信号冲突

第18章 VHDL

VHDL是Very-High-Speed Integrated Circuits Hardware Description Language.

VHDL基本概念

  • Entity -> 外部接口
  • Architecture -> 实现功能

之后VHDL还有变量和信号

  • Variablen 是局部的没有延时的变量用\(:=\)来赋值
  • Signale 可以是全局的,有延时的。用\(<=\)来赋值

数据类型

  • 标量类型,写在Entity里
1
2
3
A: in bit;
G: out boolean;
K: out integer range -2**4 to 2**4-1
  • 组合信号类型,写在Entity里
1
2
D: in bit_vector(0 to 7)
M: in signed (4 downto 0)

除了这些以外,还有一些有很多值可能性的类型

  • std_logic

它除了0,1还有其值的可能性

image-20200215104345118
  • std_logic_vector

一个由许多std_logic组成的vector

1
2
3
4
byte: in std_logic_vector(7 downto 0) -- 定义
byte <= "10000001" --赋值
byte(3 downto 0) --得到中间结果
byte(4)
Entity

Entity是一个接口,里面定义了输入输出信号。每个信号都有类型

1
2
3
4
5
6
7
8
9
entity addierer is
port(
a: in std_logic
b: in std_logic
cin: in std_logic
sum: out std_logic
cout: out std_logic
);
end entity addierer;
Architecture
1
2
3
4
5
architecture addop of addierer is
begin
sum <= a xor b xor cin;
cout <= (a and b) or (a and cin) or (b and cin)
end

在begin之前也可以定义中间变量,信号

VHDL中的计算

when 和 else
1
2
3
4
5
6
7
architecture v1 of tasterbinaer is 
begin
led <= "101" when taster(0)='1' else
"010" when taster(1)='1' else
"100" when taster(2)='1' else
"000";
end architecture v1;
process

用于表示时钟信号关连的结构

1
2
3
4
5
6
7
8
9
10
11
12
architecture v1 of tasterbinaer is 
signal counter: unsigned(15 downto 0);
begin
process(clk) -- clk是时钟信号
begin
if rising_edge(clk) then
counter <= counter + 1;
end if;
end process;
led <= counter;
end architecture v1;

第19章 存储器

19.1 存储器Speicher

存储器很重要

  • 它包含很多程序和数据
  • 很多指令需要对存储器的直接存取

19.1.1 内存墙The Memory Wall

存储器的速度比CPU慢很多

image-20200215110149051

通过这个增长趋势可以出现下面的困难

  • 那些对存储器存取的指令需要等待
  • 处理器会经常空运作
  • 处理器没有完全利用好

19.1.2 半导体存储器Halbleiterspeicher

我们先来看存储器的不同种类和技术。经典冯诺依曼存储器是RAM。Ramdom Access Memory在这个存储器上由很多存储单元Speicherzelle。除此之外还有很多其他的:

  • Read-Only Memory(ROM) 嵌入式的eingebaut
  • Programmable Read-Only Memory (PROM) 像ROM一样除了只能编程一次
  • Eraseable Programmable Read-Only Memory(EPROM)像PROM可以通过UV光把里面的内容清除再次编程
  • 暂时的存储器 -> 静态RAM(SRAM-FlipFlops),动态RAM(DRAM)

之后我们介绍这些暂时的存储器Flüchtigen Speicher

SRAM

SRAM全名Static Random Access Memory。它像触发器一样

image-20200215111200053

我们可以很好看到,单个Bit是怎样存储的。有两个晶体管,它手Select门影响。也就是它只会当Select=1的时候进行存取,否则晶体管就关闭

它的工作原理是这样的,第二个存储片段是倒相器。这就使得右边是\(\overline{bit}\)左边是\(bit\)。这种存储器是用在寄存器和缓存上面的

存储的时候bit 设位1,\(\overline{bit}\)设为0。读取的时候bit设为1,\(\overline{bit}\)设为1。根据电压差可以判断出内部存储的bit。

  • 优点:
    • 通电就有值
    • 很快
    • unempfindlich
  • 缺点:
    • 相对很大而且很贵
    • 能耗高

DRAM

DRAM全名 Dynamic Random Access Memory。和SRAM不一样。和SRAM不一样,DRAM是由电容制造的。此外还有一个选择导线和Bit导线

Cache

image-20200216165015178

Cache的结构

Cache由若干个Cache Set(Cachemenge),每个Cache Set由很多Cachezeilen构成。

Index是用来表示第几个Cache Set的,长度是\(log_{2}|CacheSet|\)

1个Cache Set 由n个Cachezeile就是n-fach assoziativ

offset表示一个Zeile中间的具体某个Byte的,长度是\(log_{2}|Cachezeile|\)

image-20200217214233915

Cache寻址优缺点

image-20200217131935697

Cache 分类

image-20200216165216465

Cache 优化

计算机性能

Roofline Modell

image-20200216170928063

Praktikum

Week 1

64bit寄存器

image-20200509112119446

Immediate-操作符

64位宽的常数只能写在mov指令中,对于32位常数

连接ssh

1
ssh fan@lxhalle.in.tum.de

反汇编指令

1
objdump gauss -d -M intel | less

优化级别

O1,O2, O3, Ofast, Os, Og

命令是:

1
gcc -O2 -o gaussO2 gauss.c

存储区域

1
objdump -wh gauss

上面这个指令可以看到Speicherbereiche

.text: 程序代码

.rodata:全局变量,常数(readonly)

.data: 初始化的全局变量(read-write)

.bss: 0初始化的全局变量

image-20200509115300412

在堆中申请内存

1
2
3
4
5
6
char* p = malloc(256 * sizeof(char));
if (p == NULL) {
abort(); // 申请失败
}
free(p);
int* v6 = alloca(v3 * sizeof(int)); // 在栈中申请

补码

取反+1

image-20200509204459987

Week2

指令

MOV r/m, r — r, r/m — r/m, imm

r/m/imm的意思是

  • r = 寄存器
  • m = 内存指针 (Speicherreferenz, z.B. [rax]) I
  • imm = 常数(Zahl, die direkt in die Instruktion codiert wird)
1
2
3
4
5
6
7
8
9
mov eax, 4
mov eax, 0x41
mov eax, ebx
mov rax, 4
mov byte ptr [rbp-0x12]. 0x63
mov rdx, qword ptr [rbp-0x8]
movzx eax, byte ptr [rbp-0x12]
movsx eax, al
lea rdi, [rip+0xe88]

PUSH r/m : Legt ein Element auf den Stack I

POP r/m : Nimmt ein Element vom Stack

ADD r/m, r — r, r/m — r/m, imm I

SUB r/m, r — r, r/m — r/m, imm

MUL r/m

MUL的操作数是 rax 和传入的参数,存在 rdx:rax里

IMUL是带符号的乘法

DIV r/m

除法要先清空 rdx

rax 是商, rdx 是余数

Jump 1b/f

b 是backwards 往回跳,f是forwords,往前跳

C语言编译过程

gcc编译时,文件扩展名为.S和.s的区别是,.S支持预处理,而.s不支持。

gcc编译一般分为四个阶段,分别是预处理、编译、汇编、链接。

预处理

1
gcc -E main.c -o main.i

编译预处理的文件

1
gcc -fno-asynchronous-unwind-tables -S main.i

汇编

汇编的作用是将汇编代码转成对应的二进制形式的cpu指令:

1
gcc -c main.s

链接

链接的作用是把代码之间的引用关系关联起来,最终生成一个完整的程序:

1
gcc main.o lib.o -o main.exe

由上可见,文件扩展名为.s的文件其实就是汇编代码文件。

快速编译

1
gcc main.c strlen.S -o main.exe

在汇编语言中用printf调试

现在一个c文件中写好自己的print函数

1
2
3
4
void my_print(int a) {
printf("%c %d\n", a, a);
return;
}

然后再汇编.S 文件中加入 .extern my_print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.intel_syntax noprefix
.global strlen_asm
.extern my_print // 加入自己的调试函数
.text

// size_t strlen_asm(const char* rdi);
strlen_asm:
xor eax,eax
mov ecx, eax // intel 用 edi
call my_printf // 调用
l1:
mov r8b, [rdi]
cmp r8b, 0
je l2

inc ax
add rdi, 1
jmp l1
l2:
ret

用的时候,把要输出的变量放到ecx里,然后调用程序即可

浮点数表示

无符号

image-20200509204636506

带符号

image-20200509204557491

快除常数

根据这个公式,可以把除法转换成乘法和位运算

image-20200509211109053
1
2
3
4
5
6
7
8
div3 :
movabs rdx , 6148914691236517206
mov rax , rdi
sar rdi, 63
imul rdx
mov rax , rdx
sub rax, rdi
ret

函数传参

传参顺序是

1
rdi, rsi, rdx, rcx, r8, r9

第7个参数以后用栈传递

1
rsp+0x8, rsp+0x10

返回值在 rax 里

调用约定

  • Callee-saved: rbx, rbp, rsp, r12 - r15

    就是函数开始和函数结束的值要一样

  • caller-saved: rax, rcx, rdx, rdi, rsi, r8 - r15

Week3

系统调用

  • 在操作系统里的调用,如输出在终端,读取数据,申请内存

调用约定

  • 每个系统调用对应一个index, 在 rax
  • syscall 覆盖 rcx, r11
  • 返回值在 rax
  • 参数在 rdi,rsi,rdx,r10,r8,r9

一些系统调用

exit(60) 结束一个进程, exit_group(231) 结束所有线程

read(0) write(1) open(3) close(3) 文件读写

文件读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <fcntl.h>

int main() {
char buf[64];
int fd = open("d.txt", O_RDONLY);
if (fd == -1)
return 0;

ssize_t len = read(fd, buf, sizeof(buf));
if (len == -1)
return 0;

printf("%d", len);
close(fd);
}

系统参数

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char **argv) {
for (int i = 0;i < argc; ++i) {
printf(";%s;\n", argv[i]);
}
return 0;
}

Week4

使用gcc调试

使用 -Wall -Wextra 可以获得更多报错,警告信息

1
gcc -o strange -O0 -Wall -Wextra strange.c

Week5

浮点数

image-20200611164737522

计算

\[ \text {SignBit} \times \text {Mantisse} \times 2^{\text {Exponent}-\text {Bias}} \]

SSE

128位寄存器 xmm0 - xmm15 , 它可以32bit64bit 同时计算

image-20200620052118405

它可以用来计算浮点数

指令

  1. XOR异或:pxor dst, src

xorps xmm1, xmm1 全部异或

  1. 从内存读取浮点数 movss dst, src

    比如 movss xmm0, [rip + .Lconstx]

  2. movdmovq 可以从普通寄存器拷贝过来

    cvtsi2ss xmm1, r8 把带符号的寄存器或内存拷贝为单精度浮点数

    cvtss2si r8, xmm1 把float拷贝为整数

  3. 加法: addss dest, src

  4. 减法:subss dest, src

  5. 乘法:mulss dest, src

  6. 除法: divss dest, src

  7. 开平方根: sqrtss dest, src

  8. 比较: ucomiss op1, op2

    1
    2
    3
    4
    5
    6
    cmpFloat:
    ucomiss xmm1 , xmm0
    jp .Lunordered ; xmm0 or xmm1 NaN
    jb .Llesser ; xmm1 < xmm0
    ja .Lgreater ; xmm1 > xmm0
    je .Lequal ; xmm1 == xmm0调用约定

调用约定

返回值:xmm0

参数: xmm0 - xmm7

比如:

image-20200620053841991

要调用它:

1
2
3
4
5
6
ex :
mov rdi , 1
movss xmm0 , [ rip + .Lconstx ]
mov rsi , 2
movss xmm1 , [ rip + .Lconsty ]
call fn

Week6: SMID

SISD: Single Instruction Single Data Stream

之前的操作都是固定长度的(byte, word, dword, qword)操作, 如果要操作数组就要遍历

SIMD: Single Instruction Multiple Data Stream

指令

加法

paddd xmm1, xmm2/m128 32位加法

每次加4个32位的数

image-20200620063653375

paddb xmm1, xmm2/m128 1byte加法

paddq xmm1, xmm2/m128 64位加法

每次加2个64位的

image-20200620063716290

addps xmm1, xmm2/m128 加4个float

addpd xmm1, xmm2/m128 加2个double

pshufd xmm0, xmm0, 0x00 把低位的复制成高位的

1
2
haddps xmm0 , xmm0  ; 把xmm0中4个32bit加起来放在低位
haddps xmm0 , xmm0

对齐Alignment

要求16bit对齐的指令

movaps xmm/m128 xmm/m128 复制4个float

movups xmm/m128 xmm/m128 在不对齐的情况下复制 4个float

AVX寄存器

位宽256bit

ymm0 - ymm15

它支持3元运算,如 \(a = b + c\)

指令

vaddps xmm/ymm, xmm/ymm, xmm/m128/ymm/m256 [1] = [2] + [3]

image-20200620070324893

vmovsd xmm, xmm, xmm 合并2个64bit浮点数到1个xmm寄存器

vbroadcastss xmm/ymm, xmm/m32 广播一个32bit操作数到一个xmm里

image-20200620070339900

vpsllvd xmm/ymm, xmm/ymm, xmm3/m128/ymm/m256 每32位左移

vpsrlvd xmm/ymm, xmm/ymm, xmm3/m128/ymm/m256 每32位右移

vpsravd xmm/ymm, xmm/ymm, xmm3/m128/ymm/m256 每32位 arithmetric 右移

vzeroall ymm ymm 清零

vzeroupper ymm ymm 上128bit清零

BMP文件读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include <stdio.h>
#include <stdlib.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;

/* HELP:
__attribute__ ((__packed__)) means that
data is stored without byte alignment

DEBUG: command to open a file in binary mode on linux
> od -tx1 -Ax [file] | more

*/

struct __attribute__ ((__packed__)) BMP_HEADER {
WORD signiture; // 2 bytes for signiture
DWORD size; // 4 bytes for size of bmp file
WORD reserved1; // 2 bytes for reserved 1
WORD reserved2; // 2 bytes for reserfed 2
DWORD offset; // offset
};

struct __attribute__ ((__packed__)) BMP_INFO {
DWORD size; // size of info header
int width; // the bitmap width in pixels (signed integer)
int height; // the bitmap height in pixels (signed integer)
WORD num_color_planes; // number of color planes
WORD bits_per_pixel; // number of bits per pixel
DWORD compression_method; // the compression method being used
DWORD image_size; // the image size
int horizontal_resolution; // the horizontal resolution (pixel per metre; signed integer)
int vertical_resolution; // the vertical resolution (pixel per metre; signed integer)
DWORD num_colors; // number of colors in the color palette
DWORD num_important_colors; // the number of important colors used
};

struct __attribute__ ((__packed__)) COLOR {
__u_char blue, green, red;
};


struct __attribute__ ((__packed__)) BMP_FILE {
struct BMP_HEADER* bmp_header; // BMP file header
struct BMP_INFO* bmp_info; // DIB header

struct COLOR** data; // image data
};

void readBMPFile(const char* filePath, struct BMP_FILE* bmp_file) {
/*
HOW TO USE: Input: a FILE pointer of bmp file and a BMP_FILE pointer for output bmpfile
The result is stored in the parameter bmp_file.
For example:
struct BMP_FILE* bmp_file = (struct BMP_FILE*)calloc(1, sizeof(struct BMP_FILE));

readBMPFile("../testout.bmp", bmp_file);

freeBMPFile(bmp_file);
free(bmp_file); bmp_file = NULL;
*/

// open bmp file
FILE* fp = fopen(filePath, "rb");
if (fp == NULL) {
printf("file does not exist!\n");
return;
}

// allocate memory for headers
bmp_file->bmp_header = (struct BMP_HEADER*)calloc(1,sizeof(struct BMP_HEADER));
bmp_file->bmp_info = (struct BMP_INFO*)calloc(1, sizeof(struct BMP_INFO));

// read in headers
fread(bmp_file->bmp_header, sizeof(struct BMP_HEADER), 1, fp);
fread(bmp_file->bmp_info, sizeof(struct BMP_INFO), 1, fp);

int size = bmp_file->bmp_info->image_size; // image size;
int w = bmp_file->bmp_info->width; // image width
int h = bmp_file->bmp_info->height; // image height

// allocate memory for image array
bmp_file->data = (struct COLOR**)calloc(h, sizeof(struct COLOR*));
for (int i = 0;i < h; ++i) bmp_file->data[i] = (struct COLOR*)calloc(w, sizeof(struct COLOR));

// read in image array
int aligned_byte = (size - w*h*3)/h;
for (int i = 0; i < h; ++i) {
fseek(fp, bmp_file->bmp_header->offset + (w*3 + aligned_byte)*i, SEEK_SET);
fread(bmp_file->data[h-1-i], sizeof(struct COLOR), w, fp);
}

fclose(fp);
}

void writeBMPFile(const char* filePath, struct BMP_FILE* bmp_file) {
FILE* fp = fopen(filePath, "wb");

// write headers
fwrite(bmp_file->bmp_header, sizeof(struct BMP_HEADER), 1, fp);
fwrite(bmp_file->bmp_info, sizeof(struct BMP_INFO), 1, fp);

int size = bmp_file->bmp_info->image_size; // image size;
int w = bmp_file->bmp_info->width; // image width
int h = bmp_file->bmp_info->height; // image height

// write image array
int aligned_byte = (size - w*h*3)/h;
__u_char byte = 0;
for (int i = 0; i < h; ++i) {
fseek(fp, bmp_file->bmp_header->offset + (w*3 + aligned_byte)*i, SEEK_SET);
fwrite(bmp_file->data[h-1-i], sizeof(struct COLOR), w, fp);
fwrite(&byte, sizeof(__u_char), aligned_byte, fp);
}

fclose(fp);
}

void printHeaders(struct BMP_FILE* bmp_file) {
struct BMP_HEADER* bmp_header = bmp_file->bmp_header;
struct BMP_INFO* bmp_info = bmp_file->bmp_info;

printf("BMP FILE HEADER: \n");
printf("Signiture: %#X\n", bmp_header->signiture);
printf("size: %d\n", bmp_header->size);
printf("reserved1: %#X\n", bmp_header->reserved1);
printf("reserved2: %#X\n", bmp_header->reserved2);
printf("offset: %#X\n\n", bmp_header->offset);

printf("DIB HEADER: \n");
printf("size of info: %d\n", bmp_info->size);
printf("width: %d\n", bmp_info->width);
printf("height: %d\n", bmp_info->height);
printf("number_color_planes: %#X\n", bmp_info->num_color_planes);
printf("number of bits per pixel: %d\n", bmp_info->bits_per_pixel);
printf("compression method: %#X\n", bmp_info->compression_method);
printf("image size: %d\n", bmp_info->image_size);
printf("horizontal resolution: %#X\n", bmp_info->horizontal_resolution);
printf("vertical resolution: %#X\n", bmp_info->vertical_resolution);
printf("number of colors: %#X\n", bmp_info->num_colors);
printf("number of important colors: %#X\n\n", bmp_info->num_important_colors);
}

void printImageArr(struct BMP_FILE* bmp_file) {
for (int i = 0;i < bmp_file->bmp_info->height; ++i) {
for (int j = 0;j < bmp_file->bmp_info->width; ++j) {
struct COLOR c = bmp_file->data[i][j];
printf("%X:%X:%X ", c.blue, c.green, c.red);
c.blue = 0; c.red = 0; c.green = 0;
bmp_file->data[i][j] = c;
}
printf("\n");
}
}

void freeBMPFile(struct BMP_FILE* bmp_file) {
// free all used memory for BMP_FILE
for (int i = 0;i < bmp_file->bmp_info->height; ++i) {
free(bmp_file->data[i]);
bmp_file->data[i] = NULL;
}
free(bmp_file->data); bmp_file->data = NULL;
free(bmp_file->bmp_header); bmp_file->bmp_header = NULL;
free(bmp_file->bmp_info); bmp_file->bmp_info = NULL;
}

void makeAndWriteBMPFile(const char* filePath, struct COLOR** arr, int width, int height) {
struct BMP_HEADER* bmp_header = (struct BMP_HEADER*)calloc(1, sizeof(struct BMP_HEADER));
struct BMP_INFO* bmp_info = (struct BMP_INFO*)calloc(1, sizeof(struct BMP_INFO));

int aligned_byte = (4 - width*3 % 4) % 4;

// initialize BMP_INFO
bmp_info->size = 40;
bmp_info->width = width;
bmp_info->height = height;
bmp_info->num_color_planes = 1;
bmp_info->bits_per_pixel = 24;
bmp_info->compression_method = 0;
bmp_info->image_size = (width*3 + aligned_byte)*height;
bmp_info->horizontal_resolution = 0;
bmp_info->vertical_resolution = 0;
bmp_info->num_colors = 0;
bmp_info->num_important_colors = 0;

// initialize BMP_HEADER
bmp_header->signiture = 0x4d42;
bmp_header->size = bmp_info->image_size + bmp_info->size + 14;
bmp_header->reserved1 = 0;
bmp_header->reserved2 = 0;
bmp_header->offset = 0x36;

// construct and write BMP file
struct BMP_FILE* bmp_file = (struct BMP_FILE*)calloc(1, sizeof(struct BMP_FILE));
bmp_file->bmp_header = bmp_header;
bmp_file->bmp_info = bmp_info;
bmp_file->data = arr;
writeBMPFile(filePath, bmp_file);

//free allocated memory
free(bmp_header); bmp_file = NULL;
free(bmp_info); bmp_info = NULL;
free(bmp_file); bmp_file = NULL;
}


int main() {
struct BMP_FILE* bmp_file = (struct BMP_FILE*)calloc(1, sizeof(struct BMP_FILE));

readBMPFile("../test.bmp", bmp_file);
printImageArr(bmp_file);
printHeaders(bmp_file);

freeBMPFile(bmp_file);
free(bmp_file); bmp_file = NULL;

struct COLOR** data = (struct COLOR**)calloc(700,sizeof(struct COLOR*));

for (int i = 0;i < 700; ++i) {
data[i] = (struct COLOR*)calloc(700, sizeof(struct COLOR));
for (int j = 0;j < 700; ++j) {
data[i][j].red = 255;
data[i][j].blue = data[i][j].green = 0;
}
}

makeAndWriteBMPFile("../output.bmp", data, 700, 700);


for (int i = 0;i < 700; ++i) { free(data[i]); data[i] = NULL; }
free(data); data = NULL;
return 0;
}