type
status
date
slug
summary
tags
category
icon
password
Fork from Hytidel.
Show respect!
第一章概要
概念
- 性能衡量
- 响应时间:计算机完成某任务的时间
- 吞吐率:单位时间内完成的任务数
- 性能: 1 / 执行时间
计算
- CPU时间
=CPU时钟周期数×时钟周期时间
=CPU时钟周期数/时钟频率
=指令数 × CPI × 时钟周期时间
CPI(每条指令的时钟周期数):由CPU硬件决定
- 加权平均CPI——综合效率指标
- 功耗
第二章MIPS汇编
四大基本原则(面向流水线的ISA设计)
- 简单源于规整
- 算术运算总是有三个操作数
- 越小越快
- MIPS只有32个寄存器
- 加速经常性事件
- 立即数操作
- 优秀的设计需要折中
- 为了保持指令长度一致(32位),MIPS采用了多种指令格式
寄存器约定
寄存器名 | 寄存器编号 | 用途 | 是否调用者保存 |
$zero | 0 | 常数 0 | 不可重写 |
$v0, $v1 | 2, 3 | 返回值 | 否 |
$a0 ~ $a3 | 4 ~ 7 | 函数传参的前 4 个参数 | 否 |
$t0 ~ $t7 | 8 ~ 15 | 临时变量 | 否 |
$s0 ~ $s7 | 16 ~ 23 | 保存参数 | 是 |
$t8, $t9 | 24, 25 | 临时变量 | 否 |
$gp | 28 | Global Pointer, 静态数据的全局指针 | 是 |
$sp | 29 | Stack Pointer, 堆栈指针 | 是 |
$fp | 30 | Frame Pointer, 帧指针, 保存过程帧的第一个字 | 是 |
$ra | 31 | Return Address, 返回地址 | 是 |
汇编指令
算术运算
- 加减
- 逻辑运算
- and:按位与,将数据的某些位保留下来
- or:按位或,将某些位强行变1
- xor:按位异或
- 与1异或:取反
- 与自己异或:为0,可判断两数是否相等,也可以快速清零
- nor:按位或非,先or再取反
- 实现单独的取反功能
nor $t0, $t1, $zero内存操作
- 读写
- lw/lb
$rt=$rs里的值 +offset(偏移量)- sw/sb
- 把
$rt的值存到地址$rs + offset的地方
- 寻址
- 字节寻址:连续读数,地址+4字节
- 内存对齐:w必须是4的倍数,h必须是2的倍数
- 大端:高位字节低地址,从左往右写(MIPS默认)
- 小端:低位字节低地址,从右往左写,倒着来
寄存器的值要跟内存里的值进行操作时,需要先读内存加载到寄存器再操作
跳转指令
- beq:
$rs,$rt相等跳转
- bne:不相等跳转
- j:无条件跳转
- target为26位,可在256MB范围内跳转
- jal:函数调用专用跳转
- 将下一条指令的地址(pc+4)塞进
$ra中,方便跳回来
- jr:R型指令,调到
$rs里面存的地址中
if写法:
for写法:
32bit立即数
- 两步走
- 加载高16位:先用
lui(Load Upper Immediate) 指令,把立即数的高 16 位塞进目标寄存器,这时候低 16 位会自动补零。 - 合并低16位:然后再用
ori(Or Immediate) 指令,把剩下的低 16 位常数“或”进去,这才凑成一个完整的 32 位立即数。
伪指令
- 伪指令就是汇编指令的变种。除了这些玩意儿,大部分汇编指令跟机器指令都是一对一的
指令后缀
后缀 | 他妈的含义 | 典型指令 | 老子的白话解释 |
i | Immediate (立即数) | addi, ori, lui | 指令里自带一个 16 位的常数,不用去别处抠数据,自给自足。 |
u | Unsigned (无符号/不报溢出) | addu, lbu, sltu | 1. 算术里指**“爆了也别报警”;2. 读内存/比较里指“把数据当成纯正数”**,高位补 0。 |
b | Byte (字节) | lb, sb | 每次只吞吐 8 位 数据。胃口小,专门吃零碎玩意儿。 |
h | Half-word (半字) | lh, sh | 每次吞吐 16 位 数据。比上不足比下有余的货。 |
w | Word (字) | lw, sw | 每次吞吐 32 位 数据。MIPS 的标准套餐,一次吃一满口。 |
v | Variable (变量/动态) | sllv, srav | 移位多少位不是定死的,得看寄存器里的脸色。 |
a | Arithmetic (算术/带符号) | sra, srav | 专门用于右移,高位补符号位。为了保证负数移位完还是负数。 |
l | Link (保存返回地址) | jal, bal | 跳走之前,先把下一条指令地址存进 $ra。记着回家的路。 |
r | Register (寄存器) | jr | 跳转地址不是写死的,是存放在寄存器里的。 |
.s | Single (单精度浮点) | add.s, lwc1 | 搞 32 位浮点数算的。算这种数的 CPU 单元都他妈是另开的小灶(协处理器)。 |
.d | Double (双精度浮点) | add.d, ldc1 | 搞 64 位浮点数算的。精度更高,也更吃资源。 |
指令分类
- R型是干苦力的,算数的。
op(6 bits):操作码。在 R-型指令里,这 6 位全是他妈的 0!rs(5 bits):第一个源寄存器。rt(5 bits):第二个源寄存器。rd(5 bits):Destination (目的地)!也就是算完的结果往哪儿塞。shamt(5 bits):全称是 Shift Amount(移位量)。- 只有在做
sll(左移)或者srl(右移)的时候才用。 - 普通的加减法,这里就是纯种的 0,没它什么屁事儿。
funct(6 bits):功能码。这才是决定“干什么活”的关键!- 同样是
op=0,如果这里是 32 (二进制100000),那就是 add。 - 如果是别的数,可能就是减法、乘法或者逻辑运算。


- I型是灵活的,搞立即数、读写内存、条件判断。
- 将R-型指令中的rd、shamt、funct字段合并为constant or address字段
op(6位):这次绝对不是 0 了!每一个 I-型指令都有自己唯一的编号(比如addi是 8,lw是 35)。CPU 一看前 6 位,就知道这活儿是加法、读内存还是跳槽。rs(5位):还是老样子,源寄存器。rt(5位):这是最他妈坑爹的地方!- 在
addi或者lw(读内存)里,它是目的地(结果存这儿)。 - 在
beq(相等跳转)或者sw(存内存)里,它是第二个源。 - 这
rt就是个“两面派”,一会儿当爹一会儿当儿子,你得看前面的op才知道它是个什么货色。 immediate(16位):这就是传说中的立即数!- 它占据了指令整整一半的长度(16 位)。
- 它可以存一个常数,或者一个内存地址的偏移量。
- 范围:因为它带符号,所以能存 32768 到 32767 之间的数。你要是想存个 100 万?去你妈的,存不下!

- J型是到处飞的。
寻址
寻址模式 | 英文全称 | 核心逻辑 | 典型指令 | 解释 |
1. 立即数寻址 | Immediate Addressing | 数据就在指令里揣着 | addi, ori | 兜里揣着现钱,想花直接掏,不用去银行。 |
2. 寄存器寻址 | Register Addressing | 数据在寄存器里存着 | add, sub | 钱在碗里,直接伸手抓,快得一批。 |
3. 基址寻址 | Base Addressing | 寄存器地址 + 偏移量 | lw, sw | 按图索骥。拿着仓库地址(寄存器)再数几个坑位(偏移量)去找货。 |
4. PC相对寻址 | PC-Relative Addressing | 当前行号 + 偏移量 | beq, bne | 就在附近。从你站的地方往前或往后跑几步。 |
5. 伪直接寻址 | Pseudodirect Addressing | 指令里存了大半个地址 | j, jal | 瞬间移动。虽然不能满地图乱飞,但在同一个大区里想去哪去哪。 |
jr是R型指令,跳转范围是32位。PC相对寻址只有16位,可以转化为伪直接寻址拓展为26位。
第三章运算
整数
加
- 溢出
- 正正相加,符号位1溢出
- 负负相加,符号位0溢出
- 正负相加,不溢出
- 全加器
- 输入:A(加数1)、B(加数2)、Cin(上一位传过来的进位)。
- 输出:S(这一位的和)、Co(给下一位的进位)。

减
- 溢出
- 正 + 正 = 负?(炸了)
- 负 + 负 = 正?(炸了)
- 正 - 负(即正+正)= 负?(炸了)
- 负 - 正(即负+负)= 正?(炸了)
- MIPS中,指令 add 检测溢出,addu 忽略溢出
乘


这张图最精妙的地方,就是下面那个 64 位的 Product 寄存器。
- 开局状态:
- 左边 32 位:全是 0(准备用来存加法的结果)。
- 右边 32 位:塞进去的是 Multiplier (乘数)。
- 运算过程:
- 看最右边那一位(乘数的最低位)。
- 如果是 1,就把 32 位被乘数 加到 64 位寄存器的左半边。
- 如果是 0,屁也不加。
- 关键动作来了:整个 64 位寄存器一起向右移一位!
- 逻辑:效果是一样的!你把数往左挪,和你把底下的纸往右抽,相对位移他妈的一模一样。
- 骚点:随着计算的进行,右边的乘数被一位位挤出去了(因为不需要了),而左边算出来的乘积正好一位位占领了右边的坑位。
- 最后结果:等 32 次循环跑完,原来的乘数被彻底“扫地出门”,整个 64 位寄存器里装的全是算好的乘积。
除


- 余数的符号必须跟被除数(Dividend)保持一致!
- 7÷(−2)=−3……1 (被除数 7 是正,余数 1 就是正)
- (−7)÷2=−3……(−1)(被除数 -7 是负,余数 -1 必须是负)
- (−7)÷(−2)=3……(−1)(被除数 -7 是负,余数还是 -1)
浮点数
加减法
- 对阶
- 小阶向大阶看齐,向右移位直到指数相同
- 丢精度
- 尾数加减
- 规格化为IEEE格式
- 舍入
- 四舍五入,是5就保偶数

乘法
- 指数相加
- 尾数相乘
- 硬乘
- 规格化
- 舍入
- 确定符号
例子

第四章处理器
- 7/11/32/54/133
- 不含流水线的处理器
- ALU
- 流水线性能计算
- 流水线冒险
- 分支冒险气泡插入
- 多发射
- 延迟槽,分支预测,分支检测前移
重点的图







指令的执行
- 取指令
- PC从存储单元取指
- 根据寄存器编号从寄存器中读取内容
- 后续操作与指令类型相关
- 除跳转指令,其他都要经过ALU
- 计算
- 方寸地址
- 分支条件
- 存储访问指令读写内存
- 将ALU运算结果写入寄存器
- PC跳到下一条指令
流水图


- 寄存器写入口的 MUX (RegDest):
- 决定是把结果写进 rt 寄存器还是 rd 寄存器。
- ALU 输入端的 MUX (ALUSrc):
- 0 代表选寄存器里的值,1 代表选指令里的“立即数”(就是那根从 Instruction 连出来的线)。
- Data Memory 后面的 MUX (MemtoReg):
- 0 代表把 ALU 算出来的结果写回寄存器,1 代表把从内存捞出来的宝贝写回寄存器。
- PC 顶上的那个 MUX (PCSrc):
- 最牛逼的一个:它由 Branch 信号 和 ALU 的 Zero 信号 共同控制(看到那个 AND 门 没?)。
- 只有当这是一条分支指令(Branch=1)且两个数相等(Zero=1)时,才让 PC 跳到新的地址。
数据通路
部件:
- 算术逻辑单元 (ALU):这就是工厂里的核心加工机。不管是加减乘除还是比大小,东西扔进去,出来就是结果。这是干活最猛的地方。
- 寄存器堆 (Register File):这是工人的随身工具包。常用的数据得放在这儿,伸手就能拿,速度贼快。
- 存储器 (Memory):这是大仓库。指令仓库(Instruction Memory)放的是“施工图纸”,数据仓库(Data Memory)放的是“原材料”和“成品”。
- 程序计数器 (PC):这是那个看大门的。他手里拿着个名单,决定下一秒该去哪儿掏指令。
- 多路复选器 (MUX):这是路口的红绿灯和分叉路。不同指令的数据来源不同时,需用复选器。
- 各种连接线 (Buses/Wires):这就是传送带。数据(电信号)顺着这些线从寄存器跑到 ALU,再从 ALU 跑到仓库。
信号:
- 读写使能类 (
RegWrite,MemRead,MemWrite):决定谁能动,谁不能动。
- 路径选择类 (
RegDst,ALUSrc,MemtoReg,Branch):决定数据走哪条路。
详细
1. RegDst (寄存器目标位)
- 干嘛的:决定谁是“接盘侠”。
- 逻辑:
- 0:把结果写进指令的
rt段([20:16] 位)。(通常是 Load 指令) - 1:把结果写进指令的
rd段([15:11] 位)。(通常是 R 型算术指令)
2. RegWrite (寄存器写使能)
- 干嘛的:终极“准生证”。
- 逻辑:
- 1:允许把数据写进寄存器。
- 0:别碰!哪怕算出来的结果再香,也不准存。
- 这就是个保险栓。如果不该写的时候你开了 1,你那寄存器里存的宝贝分分钟变成一坨不可名状的屎。
3. ALUSrc (ALU 源操作数选择)
- 干嘛的:选“下酒菜”。
- 逻辑:
- 0:选寄存器里的第二个值(
Read data 2)。(R 型指令干架用) - 1:选指令里的那 16 位立即数(经过符号扩展后的)。(
addi,lw,sw这种带数字的用)
4. Branch (分支控制)
- 干嘛的:决定要不要“反复横跳”。
- 逻辑:
- 1:这是一条分支指令(比如
beq),如果此时 ALU 算出来两个数相等(Zero=1),那就跳! - 0:给老子老老实实走下一条路。
5. MemRead (存储器读使能)
- 干嘛的:翻大仓库。
- 逻辑:
- 1:把数据存储器(Data Memory)里的东西掏出来。
- 0:仓库大门锁死,不准看。
6. MemWrite (存储器写使能)
- 干嘛的:往仓库倒垃圾。
- 逻辑:
- 1:把寄存器里的东西写进数据存储器。
- 0:不准往仓库里乱塞东西。
7. MemtoReg (存储器到寄存器选择)
- 干嘛的:决定谁才是“真成果”。
- 逻辑:
- 0:把 ALU 算出来的结果写回寄存器。
- 1:把从存储器里捞出来的东西写回寄存器。
单周期数据通路

控制单元
- ALU的控制信号
ALU的控制信号 | 功能 F (运算操作) |
0000 | and |
0001 | or |
0010 | add |
0110 | sub |
0111 | slt |
1100 | nor |
op | ALU op | operation | funct | ALU function | ALU control |
lw | 00 | load word | xxxxxx | add | 0010 |
sw | 00 | store word | xxxxxx | add | 0010 |
beq | 01 | branch equal | xxxxxx | subtract | 0110 |
R型指令 | 10 | add | 100000 | add | 0010 |
R型指令 | 10 | subtract | 100010 | subtract | 0110 |
R型指令 | 10 | and | 100100 | and | 0000 |
R型指令 | 10 | or | 100101 | or | 0001 |
R型指令 | 10 | slt | 101010 | slt | 0111 |
指令 | RegDst | ALUSrc | Mem-toReg | Reg-Write | Mem-Read | Mem-Write | Branch | ALUOp1 | ALUOp2 |
R型指令 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
lw | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
sw | $x$ | 1 | $x$ | 0 | 0 | 1 | 0 | 0 | 0 |
beq | $x$ | 0 | $x$ | 0 | 0 | 0 | 1 | 0 | 1 |
流水线
五级流水线
- IF:从内存取指
- ID:译码,读寄存器
- EX:执行运算
- MEM:访存
- WB:回写到寄存器
流水线加速比
理想为4-5,但达不到
因为
- 各级不等长
- 存在数据冒险
冒险
- 结构冒险:两个工人都想抢同一个扳手,打起来了,流水线得停。
- 解决:把内存拆成“指令内存”和“数据内存”
- 数据冒险:后面的人要等前面的人算出结果才能动,停顿 (Stall) 了。
- 解决:
- 塞气泡
- 旁路/前推


避免访存,提高效率。
但是取数-使用型冒险不能用前推解决:

前推只能推一步,如果涉及到访存,只能通过塞气泡或者指令调度(将无关紧要的指令插进来执行)来解决。
- 控制冒险:需根据前面某条指令的结果才能确定分支的执行。
- 解决:
- ID(译码)阶段加入硬件比较寄存器和计算分支地址,在这一步就决定要不要跳转。
- 分支预测:默认不跳转,直接取紧跟着的指令
- 向后的分支:猜发生,直接跳
- 向前的分支:猜不发生,直接执行下一条
流水线数据通路

流水线寄存器

- 作用:数据隔断,按照时钟周期执行,确保数据同步
红圈里的名字,全是两级名字的组合(如 IF/ID):
- PC 寄存器:这是“发令枪”。每一秒钟它都指着下一条该死指令的地址。
- IF/ID:存的是刚取出来的指令码和 PC+4 的地址。
- ID/EX:这里面存的东西最杂!有从寄存器里掏出来的操作数,有符号扩展后的立即数,还有主控单元发出的控制信号。
- EX/MEM:存的是 ALU 算出来的结果,以及要存入内存的数(如果是
sw指令的话)。
- MEM/WB:存的是从内存里捞出来的数,或者 ALU 的结果,等着最后写回寄存器。
这些寄存器不仅是存数的,它们还在帮指令“传话”。指令每走一级,它的“身份证明”和“控制参数”都要跟着寄存器一起往下挪。这就是同步。
带阻塞的流水线

解读
1. 物理结构:那四根蓝色的“通天柱”
这四根柱子(IF/ID, ID/EX, EX/MEM, MEM/WB)是时间的围墙。
- 它们把整个 CPU 强行切成了五个房间(IF, ID, EX, MEM, WB)。
- 怎么看? 每一级房间只干自己的活,干完就把结果和指令的“档案袋”(那堆控制信号和编号)扔进柱子里。下一秒,下一级房间从柱子里捡起档案继续干。
- 这就是“按劳分配,到点下班”。柱子就是为了保证每一级指令互不干扰,谁也别想抢跑。
2. 处理矛盾:两个“大内高手”的博弈
这是整张图最硬核的地方,专门对付“数据冒险”:
- 前推单元 (Forwarding Unit):它是“截胡专家”。
- 它的逻辑:能不停车就不停车!只要后面的指令算出了结果,它就通过那些蓝色的“秘密通道”直接把数送到 ALU 门口。
- 冒险检测单元 (Hazard Detection Unit):它是“紧急刹车”。
- 它的逻辑:有些数(比如
lw拿的数)实在太慢,截胡都来不及,它就果断拉手刹(PCWrite=0),让后面的人原地罚站,顺便在流水线里吐个“肥皂泡(NOP)”。
- 这就是“能私了就私了(前推),私了不了就报警(阻塞)”。
3. 控制信号:三组“随身锦囊” (EX, M, WB)
你得盯着那些从
Control 单元流出来的蓝虚线。- 怎么看? 这些信号不是一次性用完的,它们像剥洋葱一样,每过一级柱子,就丢掉一层。
- EX 在执行级用掉,M 在访存级用掉,WB 熬到最后写回级才用。
- 这叫“带着锦囊上路”。指令跑到哪,规矩就跟到哪,谁也别想乱来。
4. 整体节奏:被“最慢者”支配的恐惧
- 怎么看? 整个流水线的时钟周期,必须迁就那个最慢的房间(通常是 MEM 级读内存)。
- 这就好比一个宿舍的人一起去食堂,只要有一个人在那儿磨叽拉稀(访存延迟),全宿舍的人都得在门口等着他!这就是流水线的代价:虽然吞吐量大了,但单条指令的耗时其实变长了。
带冒险检测的流水线

- 主要目的:分支预测,尽量早跳转减少空转
- 核心区别:
- ID阶段直接比较寄存器中的两值是否相等,如果相等,Control直接发送IF.Flush信号,将下一条的控制指令清零,杀掉这条操作。
- 在ID阶段同时预先算好跳转地址,如果满足跳转条件直接有地址可以跳转。
- PC的值需要先经过一个Mux,决定下面的指令是否跳转。
异常与中断
来源
- 异常:源于CPU内部事件
- 无效指令,溢出,系统调用指令
- 中断:源于外部IO控制器
处理
- MIPS的异常通常由协处理器CP0处理
- 保存报错指令地址PC,存进寄存器EPC (Exception Program Counter)
- 记录引发异常的原因到状态寄存器Cause
- 强制跳到异常处理入口
0x 8000 0180
- 指令撤销
IF-Flush与分支预测的一样。把刚取出来的指令变成 0。ID-Flush如果 EX 级的指令出事了,处于 ID 级的指令也变0。EX-Flush同理,如果是在更后面出事,EX 级的指令也变0。- 假设不在 MEM/WB 发生异常。其实生活里很常见,如页表失效。
第五章缓存
Cache
直接映射
- 每个存储器地址对应一个唯一的Cache地址

- MIPS按字对齐,只查找4的倍数,Byte Offset没用,用来找字里面的字节

- [11,2]用来计算index()
- 只有index和tag都对上且valid=1才算命中
FastMATH

- 将横轴拉长
Cache的性能
相联

将原本匹配的块编成组,组内有多少个块就多少个比较器,匹配上其中一个就行
- 全相联
- 一个块可被放在任何地方
- 需要查找Cache的所有项
- 每个项都要一个比较器
- 多路组相联
- 每个组包含n块
- 块可以被放在组内的任何一块
- 只需要n个比较器

- 增加相联度,可降低缺失率,但收益递减

替换策略
- 优先存没有信息的块
- LRU算法,选择最长时间没使用的替换掉
- 电路复杂,超过四路难以实现
- 目前主流是近似LRU
多级Cache
Cache缺失原因
- 强制缺失:第一次访问
- 容量缺失:Cache太小不足以容纳所有块引起的缺失
- 例:一个被替换的块又被访问
- 冲突缺失:映射规则约束太死,导致数据发生资源争夺
- 全相联不会有这个问题
设计变化 | 对缺失率的影响 | 可能对性能的负面影响 |
增加 cache 容量 | 减少容量缺失,降低缺失率 | 增加访问时间 |
提高相联度 | 减少冲突缺失,降低缺失率 | 增加访问时间 |
增加块容量 | 因空间局部性,对宽范围内变化的块大小都能降低缺失率 | ① 增加缺失代价 ② 块过大时增加缺失率 |
为了在这些缺陷中取得较优解,对Cache进行分级
- L1-Cache:直连CPU,容量小速度快
- 追求最小化命中时间
- L2-Cache:处理L1的miss,容量比L1大但速度慢
- 追求低缺失率,避免访问主存
- L2的miss交给主存
虚拟存储器

- 地址映射:将虚页号映射到物理页号
- 若缺页,需从硬盘取回,消耗数百万时钟周期
- 降低缺页率:
- 虚页使用全相联:绝对不能miss
- 替换算法智能化,最大程度减少miss发生
页表(PTE)

Virtual Address (32位虚拟地址)
- 高 20 位 是 Virtual Page Number (VPN)。
- 低 12 位 是 Page Offset。这代表一页的大小是 =4KB。
Valid 位(有效位)
- 1:在内存,直接访问
- 0:在硬盘,需要访问硬盘
- 虚拟内存好处:碎片化管理。
- 在LRU算法(近似)中,PTE的引用位被周期性清零。只有在某页被访问时,引用位才置1。
- 每个页表只有一个1bit的引用位。
- 从磁盘向内存写入数据通常成块写入,不单独写地址
- 采取回写(Write-back)策略,更新的值等在页表中被替换了才写回硬盘。
快表(TLB)
- 页表的缺陷:地址映射需要额外一次访存
- 解决:现代CPU 在自己身体里(MMU 内部)搞了一个极小、极快的缓存,专门用来存最近用过的页表项,这就是TLB。
- 就是页表的缓存,快表miss了就去读页表

- Valid (有效位):这行映射是不是真的。
- Dirty (脏位):这页数据被没被改过(回写磁盘时用)。
- Ref (引用位):用来决定谁该滚出内存。
- Tag:用来匹配虚拟页号的标识。
- 第一步:看 TLB。中了?直接拿物理地址去 Cache 找数据。
- 第二步:TLB 没中,看页表 (Page Table)。
- 页表里有?(Valid=1),赶紧把这一项同步到 TLB 里,然后去内存拿货。
- 页表里也没有?(Valid=0),缺页中断! 唤醒操作系统,从硬盘里搬进内存,改页表(如果dirty位为1,则先写回),重新执行指令。
- Author:Richard Liu
- URL:http://richardliuda.top/article/CS3
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!