基于DLX指令集的5级流水线CPU设计与实现概要Word文件下载.docx
《基于DLX指令集的5级流水线CPU设计与实现概要Word文件下载.docx》由会员分享,可在线阅读,更多相关《基于DLX指令集的5级流水线CPU设计与实现概要Word文件下载.docx(23页珍藏版)》请在冰豆网上搜索。
*.s),汇编器(*.s->
*.o),链接器(*.o->
*.elf),生成了可执行文件,生成的可执行文件放到内存里。
一句C语言编译成汇编语言的举例:
c=a+b;
LDR1,8000;
a
LDR2,8001;
b
ADDR3,R1,R2
SDR3,8003;
C
对于不同的CPU,认识的指令不同,助记符也不同,上面是随意举例。
汇编器将助记符变成二进制代码时,将会根据指令集的格式要求变成由操作码和操作数组成的01序列,CPU对操作码进行译码就知道该做些什么事情(逻辑电路部分开关决定功能流向)。
在进行会变运行时,CPU从内存中取指令、取数据,进行取指、译码、执行(计算、访存)、写回等操作,I/O访问内存,将信息读出,显示给用户。
可以看出CPU的能力,就是访存和计算(加法,减法可以用加法实现,乘法可以分成加法,除法同理)所以数据交换和计算配合专用外围功能模块就可以实现多姿多彩的计算机世界。
纵观CPU的发展:
1971年:
4004,4位CPU,,2300个晶体管,46条指令,4004+RAM+ROM+寄存器芯片=第一台微型计算机。
1972年:
8008最早的8位微处理器。
1973年:
8080性能是4004的20倍,得益于MOS管电路的发展。
1977年:
16位的8086,VLSI工艺取得了突破进展…
从4004的2300个晶体管到今天拥有4亿1000万晶体管的双核微处理器,工作电压从12伏特到1.2伏特。
现在的发展趋势:
核集成度、频率扩增、核扩增。
而CPU发展存在的障碍也变得十分突出,工艺不能做的无限小:
这是自然规律,细菌-病毒-蛋白-..原子。
导线越细电阻越大。
频率不能无限增大:
不考虑设计因素,主频的提升带来功耗飞升。
DRAM和磁盘的集成度虽然提高,但是访问速度在过去的多少年间并没有大的提升,所以CPU与内存的矛盾越来越加剧(因为流水线的频率是由关键路径段决定的,访存不能太久,单纯以增加深度提高频率容易产生分支预测、功耗等问题,因此流水线长度的尺度把握一直是处理器设计中的一个重要核心问题)。
指令集和流水线,指令集就是协议
设计CPU的关键就是采用的指令集(ISA)和流水线(pipeline)结构。
根据所采用的指令集,设计流水线实现它,并消除结构相关、数据相关、控制相关、等。
指令集是CPU能够识别的指令的命令的集合。
是由操作码和操作数组成的串行二进制码,从助记符根本看不出来,程序编译的时候会把汇编语言变成标准的指令格式。
指令集代表了CPU做事的方法和能力。
指令集描述了一种二进制指令及其执行规则,大家都拿这个规范去设计自己硬件电路,实现所规定的功能,这个过程中最重要的是解决结构相关、数据相关、。
相当于一种“通信协议”,你爱遵守不遵守,你遵守了之后就可以用一样的编译器,就可以互跑对方的程序,就像遵守一样的通信协议就能通信,一样的程序,一样的指令集去编译,用不同的硬件去跑,执行的结果一样(除了某些浮点数计算的协议不一样),但实现的过程不尽相同,就像分别用STM32和51去实现MODBUS协议,能达到一样的目的,但走不同的路。
很多人看了,龙芯采用的MIPS指令集,就大感失望,以为核心技术又是用的人家的。
其实不然,设计一个指令集是一件很简单的事情,你可以,我可以,大家都可以。
就相当于写一个协议,但如果这个协议只有你自己遵守,那就完全没有意思了。
一个CPU是否强悍是否自主研发,不在于其采用了什么指令集,而在于它对指令集的实现能力!
就好像一本小说是否精彩,不在于使用英文字母书写还是用阿拉伯字母书写的一样!
定义指令集没什么技术含量,但是实现这个指令集就需要科研人员付诸巨大的努力了!
举个最简单的例子:
Intel和AMD这么多年都采用的是Intel的X86指令集,但是CPU的性能却越来越强,主频越来越高,换句话说这么多年Intel和AMD都是在研发X86指令集的更强实现!
真正有挑战性的工作是研发指令集的实现!
这才是自主研发的核心。
其中逻辑设计、数字集成电路设计等都是关键,一点点的时延、功耗都可能带来性能的骤降。
为什么要买指令集授权?
龙芯买的其实根本不是专利授权,而是商标授权。
买了之后,龙芯可以标上“MIPS兼容”的标识。
这意味着,以前为MIPS架构开发的软件都可以直接在龙芯上用了。
没这个标识,别人是不敢用龙芯的。
有些人认为自主产权就该开发一套自己的架构。
这是完全不考虑生存的人才能干的事情。
自己的一套架构,开发工具、中间件和应用软件都只能自己开发了。
你还没占领市场,谁闲的没事给你开发软件啊?
你没有应用软件,你占领哪门子市场啊?
明眼人一眼就看出,龙芯买授权,一定是为大规模商业行动做的准备。
有了MIPS授权,就好卖了。
MIPS前些日子刚刚和如日中天的Android牵了手,MIPS架构的处理器又占据了PS/3这类产品的市场很久,龙芯打上“MIPS”的标签,不久可以在很多场合大卖。
估计一年之内就会有这类的消息了。
所以不必为龙芯是不是自主知识产权操心了。
关注一下龙芯如何跟英特尔这些霸王对决倒是正着。
指令集分成RISC、CISC。
CISC与RISC只是一种设计理念的区别,没有十分具体,界限明显的区别。
一个是让CPU做更多更复杂的事情(执行那样的指令),硬件电路比较复杂。
一个是CPU做的事情都是基础的(28原则有20%的指令使用频率最大,占运行时间的80%),编译器做的更多些。
基于CISC的CPU已经不多见,这是有时代背景的,早期存储器容量非常小,代码不能太长,所以要设计一些功能强大的指令,用硬件实现其功能,打个比方,乘法可以分拆成若干次加法,CISC就单独做一条乘法指令,而RISC在编译阶段就用多条加法指令代替该惩罚指令。
这种时代的背景可以从另一角度的指令集的分类上体会。
根据CPU中用来存储操作数的存储单元来分类,典型的CPU提供的暂存器单元通常有:
寄存器、堆栈和累加器(堆栈只是一种身份),这三种不同的类的指令集反映了当时的工艺和体系结构水平。
分别看:
堆栈型、累加器、寄存器型指令执行加法指令的区别,以认识它们的分别实现相同计算的过程,体会CPU做事方式与能力跟指令集是密切相关的。
累加器型:
LOADA//LOADXAC<
--M[x]
ADDB//ADDXAC<
--(AC)+M[x]
StoreC//STOREXM[x]<
--(AC)
堆栈型:
PUSHA//PUSHxstack[sp]<
--M[x];
sp=sp-1
PUSHB
ADDstack[sp+1]<
--stack[sp]+stack[sp+1];
sp=sp+1
POPCM[x]<
--stack[sp];
寄存器型的指令所有操作数均需命名,且要显式表示(隐式的意思是我要操作的数已经约定好在堆栈或者累加器中了),因而指令比较长。
可以生成比较高效的代码。
寄存器型的指令集流行的原因:
早期的大多数机器都是采用堆栈型或累加器型指令集结构,但是自1980年以来的大多数机器均采用的是寄存器型指令集结构。
原因是,集成电路技术飞速发展。
寄存器和CPU内部其它存储单元一样,要比存储器快。
对编译器而言,可以更容易有效地分配和使用寄存器。
可以将当前大多数通用寄存器型指令集结构进一步细分为三种类型:
1、寄存器-寄存器型ADDR1,R2,R3
2、寄存器-存储器型ADDR2,R1,C
3、存储器-存储器型ADDA,B,C
第一种的优点:
简单,指令字长固定,是一种简单的代码生成模型,各种指令的执行时钟周期数相近。
缺点:
通过Load/Store指令条数多,因而其目标代码较大。
第二种的优点:
可以直接对存储器操作数进行访问,容易对指令进行编码,且其目标代码较小。
指令中的操作数类型不同。
在一条指令中同时对一个寄存器操作数和存储器操作数进行编码,将限制指令所能够表示的寄存器个数。
由于指令的操作数可以存储在不同类型的存储器单元,所以每条指令的执行时钟周期数也不尽相同。
第三种的优点:
是一种最紧密的编码方式,无需“浪费”寄存器保存变量。
指令字长多种多样。
每条指令的执行时钟周期数也大不一样,对存储器的频繁访问将导致存储器访问瓶颈问题。
设计指令集除了要设计指令集结构和功能,包括:
指令宽度、类型、条数、操作码、操作数等,还要关心寻址技术。
寻址就是如何根据指令的操作码的指示寻找操作数?
它是CPU在实现指令功能时暗含的操作。
比较常见的寻址方式如下:
寄存器寻址AddR4,R3
立即数寻址AddR4,#3
直接寻址(绝对寻址)AddR1,(1001)
寄存器间接寻址AddR4,(R1)
偏移寻址AddR4,100(R1)
到此,已经对指令集的概念做了比较详细的叙述,对于DLX指令集,分为I型、J型、R型指令,它们的指令格式分别如下:
如果按照功能分类的话,DLX指令又可以分为ALU指令,访存指令、跳转指令,指令的功能和具体的操作如下:
CPU的硬件就是用来实现上述指令所规定的功能。
DLX指令集结构的指令格式、寻址方式和操作都非常简单。
也许有人会担心,这些特性会使得目标代码中指令条数增多,导致程序运行时间加长,从而使这种指令集结构的机器性能并不会太高。
3、流水线—20世纪最伟大的发明
发明流水线的竟然是通用汽车的福特,大大提高了生产效率。
当然汽车生产流水线跟CPU中的流水线是不同的概念,但道理都一样。
假设装配一辆汽车有十个步骤,每个步骤耗时1小时,一个人装配一整辆汽车就需要十小时。
而若是有10个人,每个人只干一个步骤,干完之后传递给下一个人,这样,满负荷下1个小时就能生产一辆汽车。
电视剧《我的兄弟叫顺溜》,顺溜拿子弹、装子弹、瞄准发射各需要一分钟,也就是打一个子弹需要三分钟,他叫来两个队友,一个负责递子弹给另一个,另一个负责装子弹,顺溜只负责瞄准发射。
这样1分钟就可以打出一发子弹。
指令的执行一般分为:
取指、译码、执行、写回。
若是每次只执行一条指令,则指令执行时间将大大增加。
即相当于有四个人,每个人只干一样活,第一个周期第一条指令取指、第二个周期第二条指令取指,第一条指令译码,第三个周期第一条指令执行,第二条指令译码,第三条指令取指…依次流动。
可以看出流动的频率取决与这四个阶段中需要执行的最长时间的那个。
比如四个阶段分别需要执行1、1、2、1个周期,那么流动周期就是2。
真实的pipeline电路使用寄存器实现了各段的隔离,是流动的关键(因为寄存器在每个时钟周期的上升沿或下降沿才将数据锁存输出)
对于DLX指令集,设计其拥有将指令执行划分为5个阶段:
取指令周期(IF)
指令译码/读寄存器周期(IR)
执行/有效地址计算周期(EX)
存储器访问/分支完成周期(MEM)
写回周期(WB)
实现这五个阶段的一个简单的数据通路如下:
数据通路图可以看做是静态的,包含所有情况的通路,实际上每段对第N个任务进行处理时,操作码也是在流水线寄存器中同步流动的,对操作码进行译码(switch),每个clock换了新任务都switch一下这非常关键。
决定使用哪些通路完成响应功能。
其中IF和ID段中不管是什么指令做的事情都一样。
加了寄存器起了隔离(这个周期计算、下个周期要用,免遭破坏,自己的结果跟自己走)和流动的作用。
加了流水线寄存器的通路如下:
这就是CPU的基本的流水线结构了。
下面看看对于不同的指令,译码后各段分别做些什么事情。
IF段根据PC指针的地址读取指令
ID段
读IR寄存器(指令寄存器)、读寄存器、并将读出结果放入两个临时寄存器A和B中。
同时对IR寄存器中内容的低16位进行符号扩展,然后将符号扩展之后的32位立即值保存在临时寄存器Imm中。
A←Regs[IR6..10]
B←Regs[IR11..15]
Imm←((IR16)16##IR16..31)
EX
1、对于访存指令要计算有效地址
LWR1,100(R2)ALUoutput←A+Imm
SW100(R2),R1
2、对于寄存器-寄存器的ALU指令ADDR1,R2,R3ALUoutput←AopB
3、对于寄存器-立即数的ALU指令
ADDIR1,R2,#3ALUoutput←AopImm
4、对于分支指令
BEQZR1,#800ALUoutput←NPC+ImmCond←(Aop0)
MEM
1、访存指令
Load:
LMD←LMDMEM[ALUoutput]
Store:
Mem[ALUoutput]←B
2、分支指令(说放在IF阶段也没事)
if(Cond)PC←ALUoutput
elsePC←NPC
WB
1、寄存器-寄存器型ALU指令:
Reg[IR16..20]←ALUoutput
2、寄存器-立即值型ALU指令:
Reg[IR11..15]←ALUoutput
3、Load指令:
Reg[IR11..15]←LMD
注意到流水线数据通路中有四个多路选择器。
选择器1和2都位于EX阶段,上边是1,控制是A参与加法运算还是NPC参与,如果是分支指令则NPC,寄存器-寄存器和寄存器-立即数的ALU指令都是A参与。
2则是控制是B参与运算还是立即数参与运算,当时寄存器-寄存器型的ALU指令时B参与。
分支和寄存器-立即数的指令都是立即数参与运算。
选择器3是MEM段的,判断EX段计算出的CON是不是成立,若是成立的话则将aluout给PC,若是不成立,则将NPC给PC。
选择器4则是在WB阶段起作用的,看是将ALUout写回到寄存器还是将LMD写回。
前者是ALU指令,后者是load指令。
4、流水线带来的烦恼—相关
你现在有1000条指令,每条指令都3周期,执行周期就是3000吗,基本上不可能!
!
存在相关和其带来的停顿!
优化进行中....
指令之间存在的相关,限制了流水线的性能。
流水线中的相关是指相邻或相近的两条指令因存在某种关联,后一条指令不能在既定的时钟周期开始执行,否则出错。
消除相关的基本方法——暂停暂停流水线中某条指令及其后面所有指令的执行,该指令之前的所有指令继续执行。
1、结构相关:
结构相关:
当指令在重叠执行过程中,硬件资源满足不了指令重叠执行的要求,发生资源冲突。
2、数据相关:
因一条指令需要用到前面指令的结果,而无法与产生结果的指令重叠执行。
3、控制相关:
当流水线遇到分支指令和其它会改变PC值的指令时就发生控制相关。
当指令在流水线中重叠执行时,流水线有可能改变指令读/写操作数的顺序,使之不同于它们在非流水实现时的顺序,举例说明数据相关:
ADDR1,R2,R3
SUBR4,R1,R5
表面上看,顺序执行没什么错误,但是R1在上一条指令的正确结果要WB才写回。
但SUB在ID就要R1。
硬件才不管,来取就给,不管对错。
SUB在EX段需要R1时,此时ADD在MEM段,正确的值在EX/MEM.aluout中,扯线过去。
两条指令的数据i和j数据相关可能发生的4种情况:
(1)i读,j读不会冲突
(2)i写,j写对于DLX不会发生错误,因为都是只有在WB阶段写回。
(3)i先读,j后写DLX不会错,因为永远都是ID读,WB写回。
(4)i先写,j后读会发生冲突。
采用定向技术,主动去找正确的数据,将计算结果从其产生的地方直接送到真正需要它的地方,这是硬件的支持。
以此减少暂停带来的损失。
(查找本周期内我需要的正确数据在哪,可不能穿越时空)
并不是所有的都可以用定向技术解决,比如当前你需要的数据在本周起都还没开始计算,怎么办?
到哪里去取?
如果是已经计算出来了,怎么扯线都可以。
如LWR10(R2)
SUBR4R1R5
R1的正确值至少到MEM段末才能取到,SUB就算是在EX时也看不见啊看不见。
所以要用互锁部件提供暂停(载入延迟)之后再取。
检测数据相关:
ID段可以检测所有数据相关。
尽早检测相关、指导决策。
针对DLX流水线出现的所有相关,设计的所有定向路径:
3、控制相关
一旦分支转移成功,正确的地址要在Mem段的末尾才会被写入PC。
一旦ID段检测到分支指令,就暂停执行其后的指令,直到分支指令达到Mem段,确定新的PC为止,(也就是分支指令到达WB段,新的PC值顺利到达MEM/WB,再传达PC,下一条指令才进行IF)分支转移成功将导致DLX流水线暂停3个周期。
怎么能尽早判断分支是否成功,成功了又怎么尽早计算出转移地址?
将“=0?
”测试提前到ID段,在ID段增加一个加法器,计算分支目标地址。
开销减少一拍。
再改通路再减一拍。
记住IF一开始就要寻址的,所以在ID内将转移结果给PC可以再EX开始的时候就成功转移,所以只需要暂停ID的一拍(修改通路2)。
这个过程用下面三个图来表示:
分支暂停3个周期
修改通路1,减一拍,如下图
修改通路2,再减一拍
还有别的减少流水线分支损失的方法:
(1)冻结或排空流水线,思路:
在流水线中停住或删除分支后的指令,直到知道转移目标地址。
(2)预测分支转移失败
(3)预测分支转移成功,没有好处。
(4)延迟分支,分支开销为n的分支指令后紧跟有n个延迟槽,流水线遇到分支指令时,按正常方式处理(没有载入延迟),顺带执行延迟槽中的指令,从而减少分支开销,关键是怎么安排延迟指令,哪些指令可以作为延迟指令?
有三种调度方法:
从前调度、从目标处调度、从失败处调度
从前调度:
将分支指令之前的拿下来当延迟指令,反正早晚要执行,这种方法不会有任何损失。
从目标处调度:
将要跳转的代码当延迟指令,如果成功则不会有影响,反正执行的是成功时的代码。
这样如果分支转移失败,将多执行一条指令。
从失败处调度:
如果分支失败,则不会有影响,反正执行的是失败时的指令。
如果成功了则会多执行代码。
看起来这相当于什么都没做,但调度的作用是让执行的延迟指令在分支成功跳转的情况下没有任何影响。
要保证失败了多执行的指令对系统无害。
解决分支开销的过程:
我在屋子里按墙格顺序扔飞镖,有个教练来了(分支指令),喊了暂停,本来需要暂停(stall)扔飞镖3秒,后来2秒,现在1秒。
暂停之后教练再告诉我应该接着刚刚的目标扔还是扔到教练号指向的地方去。
现在我嫌浪费时间不想停下来,那我扔到哪去呢?
我可以挑原本无论如何要扎的地方放在暂停的时候扎(从前调度),也可以继续扎下一个目标(从失败处调度),也可以向教练号指向的地方扔(从成功处调度)。
记分牌与tomasulo算法(与设计无关)
捋一捋,自从认识到相关之后,就要解决它。
首先解决结构相关,重复设置功能部件的方法。
解决数据相关用定向路径,但是定向路径也有解决不了的时候,会带来暂停(在某条指令插入一个stall,就是一个周期内流水线对于该条指令和以后的所有指令什么都不做)。
对于分支开销,从3个暂停硬件优化到1个暂停,同时可以通过分支预测、延迟槽+指令调度的方式来解决掉这一个暂停。
但是相关会带来暂停这个东西是必然的,那么有什么方法可以减少由相关带来的暂停,来增加指令执行的并行度呢?
有别的策略:
从软件方面我可以从编译器的角度进行代码的静态调度,比如通过没有循环间相关的循环展开可以将stall降到很低的水平。
但是有没有一整套方法不是优化,而是进行变革!
直接解决掉相关及其带来的停顿问题!
那就是硬件调度的方法,如记分牌算法。
记分牌算法不光是一种调度算法,它已经不是在原来的5级流水线上做优化,而是硬件的大变革,调度方式的变革,相当于给CPU移植了“操作系统”管家让指令的运行更加有效率。
但事实上下面的记分牌算法只是针对浮点指令的,即PC取出指令后判断是浮点指令的话,就交给记分牌算法去处理。
原来的定点指令还采取流水线的策略。
因为记分牌虽然消除了相关,但是逻辑电路比较复杂,判断周期等都需要考虑。
对于简单的定点指令,可能流水线结构处理起来更有优势。
所以一般来说记分牌和TOMASULO算法都只是针对浮点指令。
tomasulo算法是记分牌的改进,即通过数据总线直接对寄存器进行存取,而且不对真正的寄存器操作,而是采用备份(寄存器重命名,缓冲)的方法,这样在建立依赖关系的基础上就解决了WAR,WAR相关。
如果一个CPU的定点指令用流水线计算,浮点指令用下面两种算法,那么如果定点指令和浮点指令之间出现相关该如何处理呢?
关系类似于这样
它的特点:
(思想是检测所有的相关,并让没有相关的指令先去执行,不用等待)
硬件组成:
记分牌控制器+两个乘法部件+一个除法部件+一个ADD部件+一个integer部件做访存。
每一个部件都相当于一个四级结构。
在控制器的控制下,不同的指令被发射到各个功能部件。
如果要执行的指令存在结构相关,即比如一条加法指令要使用ADD部件,但是ADD部件在被使用,相当于存在结构相关,那么该条指令不执行且以后的所有指令都不执行。
正常的情况下是一个时钟周期给往后的一条指令分配一个部件,然后开始部件级的并行(多个部件共同工作,各自在四级的路上往前走)。
这就不同于之前的按周期的流动分别将一条条的指令投入同一个5级流水线,那样是流水线的各个部分并行。
要理解这两种指令并行的不同。
每一个部件的四级结构(4个阶段)
检测结构相关。
相当于IF,要取指令