差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
自己设计一款cpu [2018/09/18 16:12] group003 [存储组织描述] |
自己设计一款cpu [2018/09/19 16:18] (当前版本) group003 [让CPU运行软件程序] |
||
---|---|---|---|
行 2: | 行 2: | ||
我们以姜咏江老师的书《自己设计制作CPU与单片机》中一个简单CPU为例子,说明CPU的工作原理和设计过程。 \\ | 我们以姜咏江老师的书《自己设计制作CPU与单片机》中一个简单CPU为例子,说明CPU的工作原理和设计过程。 \\ | ||
该CPU的程序在设计之初是固定的,并没有设计外部程序输入接口,所以这是一个专用CPU。\\ | 该CPU的程序在设计之初是固定的,并没有设计外部程序输入接口,所以这是一个专用CPU。\\ | ||
+ | {{::jdcpu.docx|jdcpu完整代码}} | ||
====CPU的端口描述==== | ====CPU的端口描述==== | ||
<code verilog> | <code verilog> | ||
行 94: | 行 95: | ||
该CPU的初始状态用初始化信号变量reset_n来驱动,reset_n信号下降沿有效,在always语句体中敏感信号列表中用negedge来申明。 | 该CPU的初始状态用初始化信号变量reset_n来驱动,reset_n信号下降沿有效,在always语句体中敏感信号列表中用negedge来申明。 | ||
<code verilog> | <code verilog> | ||
+ | always @(posedge clock or negedge reset_n) | ||
+ | begin | ||
+ | if (!reset_n) | ||
+ | begin | ||
+ | pc <= 0; | ||
+ | sp <= 0; | ||
+ | lda <= 0; | ||
+ | add <= 0; | ||
+ | out <= 0; | ||
+ | sdal <= 0; | ||
+ | sdah <= 0; | ||
+ | str <= 0; | ||
+ | sub <= 0; | ||
+ | jmp <= 0; | ||
+ | jz <= 0; | ||
+ | jn <= 0; | ||
+ | call <= 0; | ||
+ | ret <= 0; | ||
+ | mult <= 0; | ||
+ | divi <= 0; | ||
+ | jp <= 0; | ||
+ | end | ||
</code> | </code> | ||
- | 这一段初始化程序描述了CPU复位后的初始状态,如果reset_n从1变为0,那么begin...end块中的语句被执行。被复位的除了指令标志之外,还有程序计数器pc、堆栈指针sp和节拍jp。sp的初值设为0,说明堆栈开口向下,数据入栈后,sp加1,而数据出栈前,sp要减1;程序计数器pc初始化为0,说明CPU开始运行,从程序存储器的0地址取指,节拍jp被赋值0,表示CPU指令的动作从0节拍开始;各条指令标志都为0,表示开始时没有确定是哪一条指令执行。 | + | 这一段初始化程序描述了CPU复位后的初始状态,如果reset_n从1变为0,那么begin...end块中的语句被执行。被复位的除了指令标志之外,还有程序计数器pc、堆栈指针sp和节拍jp。sp的初值设为0,说明堆栈开口向下,数据入栈后,sp加1,而数据出栈前,sp要减1;程序计数器pc初始化为0,说明CPU开始运行,从程序存储器的0地址取指,节拍jp被赋值0,表示CPU指令的动作从0节拍开始;各条指令标志都为0,表示开始时没有确定是哪一条指令执行。\\ |
该设计中的全部指令有: | 该设计中的全部指令有: | ||
<code verilog> | <code verilog> | ||
+ | //指令: | ||
+ | reg lda, //取数:从数据单元取数到da | ||
+ | add, //加:da与数据单元相加,结果放入da | ||
+ | out, //输出:将数据单元内容输出到输出寄存器 | ||
+ | sdal, //低8位立即数:将8位立即数扩充为16位送da | ||
+ | sdah, //高8位立即数:将8位立即数作为高8位,与原da低8位连接成16位放在da中 | ||
+ | str, //da送数据存储单元: | ||
+ | sub, //减:da与数据单元相减,结果放入da | ||
+ | jmp, //跳转 | ||
+ | jz, //da为0跳转 | ||
+ | jn, //da为负跳转 | ||
+ | call, //调用子程序 | ||
+ | ret, //返回 | ||
+ | mult, // | ||
+ | divi, // | ||
+ | stp; //停止 | ||
</code> | </code> | ||
===取指令周期的描述=== | ===取指令周期的描述=== | ||
- | CPU的正常运行状态分为取指周期和执行周期,这主要由时钟节拍和指令标志两部分变量确定。节拍jp表明指令执行动作的顺序,而指令标志是用来指示正在执行的指令。从节拍取指为0开始描述。 | + | CPU的正常运行状态分为取指周期和执行周期,这主要由时钟节拍和指令标志两部分变量确定。节拍jp表明指令执行动作的顺序,而指令标志是用来指示正在执行的指令。\\ |
+ | 从节拍取指为0开始描述。\\ | ||
<code verilog> | <code verilog> | ||
+ | // 节拍jp指出的状态: | ||
+ | case (jp) | ||
+ | 0: begin //空拍,稳定地址寄存器数据需要 | ||
+ | jp <= 1; //转到1拍 | ||
+ | end | ||
</code> | </code> | ||
- | 由于jp=0节拍被用于程序计数器pc将值传递到程序存储器的前端地址寄存器(并不是所有的存储器都要求这样),所以这一拍在外被设定为空操作。在这一拍中,将节拍变量赋值1,从而使CPU运行转到下一个节拍为1的状态。 | + | 由于jp=0节拍被用于程序计数器pc将值传递到程序存储器的前端地址寄存器(并不是所有的存储器都要求这样),所以这一拍在外被设定为空操作。在这一拍中,将节拍变量赋值1,从而使CPU运行转到下一个节拍为1的状态。\\ |
===指令分析的描述=== | ===指令分析的描述=== | ||
- | 当jp=1时,程序存储器的地址已经被确定好了,所以可以从程序存储器的输出端口得到要取出的指令。一般情况下,应将取出的指令放到指令寄存器ir中分析,目的是防止后面程序存储器的地址有变,从而使输出的指令发生变化。由于我们的设计没有变动存储单元的地址,因而就可以直接对端口输出导线值进行逻辑分析。 | + | 当jp=1时,程序存储器的地址已经被确定好了,所以可以从程序存储器的输出端口得到要取出的指令。一般情况下,应将取出的指令放到指令寄存器ir中分析,目的是防止后面程序存储器的地址有变,从而使输出的指令发生变化。由于我们的设计没有变动存储单元的地址,因而就可以直接对端口输出导线值进行逻辑分析。\\ |
- | 这样在jp=1的节拍就可以利用程序存储器的输出,识别出是什么指令,从而约束后面节拍执行的指令。在jp=1的描述如下: | + | 这样在jp=1的节拍就可以利用程序存储器的输出,识别出是什么指令,从而约束后面节拍执行的指令。在jp=1的描述如下:\\ |
<code verilog> | <code verilog> | ||
+ | 1: begin //依指令前5位编码来识别指令,并将指令标识置位 | ||
+ | case (q_w[15:11]) | ||
+ | 5'b00001: lda <= 1; //lda:00001 | ||
+ | 5'b00010: add <= 1; //add:00010 | ||
+ | 5'b00011: out <= 1; //out:00011 | ||
+ | 5'b00100: sdal <= 1; //低8位,扩充有符号16位 | ||
+ | 5'b00101: sdah <= 1; //高8位,与前面低8位输入合成16位 | ||
+ | 5'b00110: str <= 1; //da送数据单元 | ||
+ | 5'b00111: sub <= 1; | ||
+ | 5'b01000: jmp <= 1; | ||
+ | 5'b01001: if (da==0) jz <= 1; //累加器da是0,跳转 | ||
+ | 5'b01010: if (da[15]==1) jn <= 1; //累加器da为负,跳转 | ||
+ | 5'b01011: call <= 1; | ||
+ | 5'b01100: ret <= 1; | ||
+ | 5'b01101: mult <= 1; | ||
+ | 5'b01110: divi <= 1; | ||
+ | 5'b11111: stp <= 1; | ||
+ | default: jp <= 0; | ||
+ | endcase //节拍区分指令结束 | ||
+ | jp <= 2; //转到jp=2的状态 | ||
+ | end | ||
</code> | </code> | ||
- | q_w[15:0]是全部数据,q_w[15:11]是指令代码,依据这5位的数值来确定是哪一条指令在执行,继而将相应的指令标志赋值1,指令标志指示该条指令是否处于执行状态。在1节拍中,指令jz和jn除了节拍之外还有累加器限制。 | + | q_w[15:0]是全部数据,q_w[15:11]是指令代码,依据这5位的数值来确定是哪一条指令在执行,继而将相应的指令标志赋值1,指令标志指示该条指令是否处于执行状态。在1节拍中,指令jz和jn除了节拍之外还有累加器限制。\\ |
- | 如果将节拍的0状态称为取指令,1状态称为分析指令,那从节拍2状态开始就进入了指令的执行过程。 | + | 如果将节拍的0状态称为取指令,1状态称为分析指令,那从节拍2状态开始就进入了指令的执行过程。\\ |
===指令执行周期的描述=== | ===指令执行周期的描述=== | ||
- | 指令执行周期的详细描述实际上是CPU设计最核心的部分。 | + | 指令执行周期的详细描述实际上是CPU设计最核心的部分。\\ |
- | <code veilog> | + | <code verilog> |
+ | 2: begin //CPU进入jp=2的状态 | ||
+ | case (q_w[15:11]) //用指令编码确定指令 | ||
+ | 5'b00001: begin //lda <= 1; | ||
+ | mar<=q_w[10:0]; //数据地址给到数据地址寄存器 | ||
+ | jp <= 3; //转到jp=3的状态 | ||
+ | end | ||
+ | 5'b00010: begin //add <= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | 5'b00011: begin //out <= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | |||
+ | 5'b00100: begin //sdal <= 1; | ||
+ | da <= {{8{q_w[7]}},q_w[7:0]}; //将指令中写的8位立即数扩充成16位有符号数送到累加器da | ||
+ | sdal<= 0; //sdal指令执行完成 | ||
+ | pc <= pc+1; //准备取下一条指令 | ||
+ | jp<= 0; //节拍状态复位 | ||
+ | end | ||
+ | |||
+ | 5'b00101: begin //sdah <= 1; | ||
+ | da[15:0] <= {q_w[7:0],da[7:0]}; //将指令中写的8位数放入累加器的高8位,累加器低8位数不变 | ||
+ | sdah <= 0; //sdal指令执行完成 | ||
+ | pc <= pc+1; //准备取下一条指令 | ||
+ | jp<= 0; //节拍状态复位 | ||
+ | end | ||
+ | |||
+ | 5'b00110: begin //str <= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | ddata <= da; //累加器da送数据存储器 | ||
+ | jp <= 3; //指令str未执行完,转jp=3 | ||
+ | end | ||
+ | 5'b00111: begin //sub <= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | |||
+ | 5'b01000: begin //jmp <= 1; | ||
+ | pc <= q_w[10:0];//将跳转程序地址送程序计数器 | ||
+ | jmp <=0; //跳转指令完成 | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b01001: begin //jz <= 1; | ||
+ | if (jz) pc <= q_w[10:0];//如果da=0则跳转 | ||
+ | else pc <= pc+1; //不然执行下一条指令 | ||
+ | jz <=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | |||
+ | 5'b01010: begin //jn <= 1; | ||
+ | if (jn) pc <= q_w[10:0]; | ||
+ | else pc <= pc+1; | ||
+ | jn<=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b01011: begin //call <= 1; | ||
+ | pc_back <= pc+1;//保存下一条指令的地址 | ||
+ | jp <= 3; | ||
+ | end | ||
+ | 5'b01100: begin //ret <= 1; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | 5'b01101: begin //mult<= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | 5'b01110: begin //divi <= 1; | ||
+ | mar<=q_w[10:0]; | ||
+ | jp <= 3; | ||
+ | end | ||
+ | 5'b11111: jp<=0; //stp指令,返回jp=0状态 | ||
+ | default: jp <= 0; //其他情况一律节拍返回jp=0状态 | ||
+ | endcase | ||
+ | end | ||
</code> | </code> | ||
- | jp=2状态结束后,sdal、sdah、jmp、jz、jn等指令就已经执行完成了,这说明这几条指令的指令周期只有3个时钟节拍。指令执行完成后,指令标识和节拍变量切记归零,若不是转移指令,还要讲程序计数器pc加1,以便CPU去取下一条指令;如果没有执行完成,那么将节拍状态改为下一个。 | + | jp=2状态结束后,sdal、sdah、jmp、jz、jn等指令就已经执行完成了,这说明这几条指令的指令周期只有3个时钟节拍。指令执行完成后,指令标识和节拍变量切记归零,若不是转移指令,还要讲程序计数器pc加1,以便CPU去取下一条指令;如果没有执行完成,那么将节拍状态改为下一个。\\ |
- | 节拍状态jp=2之后,没有完成的指令要进入jp=3的状态。如果在jp=2状态中,有向存储器地址寄存器传送了数据的动作,那么就要空操作一拍,即在jp=3的状态中,直接将jp的值设定为4,就此转到下一个节拍。 | + | 节拍状态jp=2之后,没有完成的指令要进入jp=3的状态。如果在jp=2状态中,有向存储器地址寄存器传送了数据的动作,那么就要空操作一拍,即在jp=3的状态中,直接将jp的值设定为4,就此转到下一个节拍。\\ |
<code verilog> | <code verilog> | ||
+ | 3: begin | ||
+ | case (q_w[15:11]) | ||
+ | 5'b00001: begin //lda <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | 5'b00010: begin //add <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | | ||
+ | 5'b00011: begin //out <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | | ||
+ | 5'b00110: begin //str <= 1; | ||
+ | dwren <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | 5'b00111: begin //sub <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | | ||
+ | 5'b01011: begin //call <= 1; | ||
+ | pc <= q_w[10:0];//pc接收子程序地址 | ||
+ | swren <= 1; //发出写堆栈信号 | ||
+ | jp <= 4; | ||
+ | end | ||
+ | 5'b01100: begin //ret <= 1; | ||
+ | sp <= sp-1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | 5'b01101: begin //mult <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | 5'b01110: begin //divi <= 1; | ||
+ | jp <= 4; | ||
+ | end | ||
+ | default: jp <= 0; | ||
+ | endcase | ||
+ | end | ||
</code> | </code> | ||
在这一段描述中,只有call指令有实质性动作,其他指令都是空操作,为什么凡是在上一节拍中向存储器传送地址的指令,在此都要空一拍呢,这是因为我们使用的存储器前端都有特殊寄存器,我们在设计时并不能对这个特殊寄存器进行直接操作,特殊寄存器得到地址数据,还要通过一个时钟节拍传递才行。特殊寄存器接收数据的过程是在存储器内部进行的,设计过程中需要空置一拍。 | 在这一段描述中,只有call指令有实质性动作,其他指令都是空操作,为什么凡是在上一节拍中向存储器传送地址的指令,在此都要空一拍呢,这是因为我们使用的存储器前端都有特殊寄存器,我们在设计时并不能对这个特殊寄存器进行直接操作,特殊寄存器得到地址数据,还要通过一个时钟节拍传递才行。特殊寄存器接收数据的过程是在存储器内部进行的,设计过程中需要空置一拍。 | ||
<code verilog> | <code verilog> | ||
+ | 4: begin | ||
+ | case (q_w[15:11]) | ||
+ | 5'b00001: begin //lda <= 1; | ||
+ | da<=q_data; //存储单元数据送累加器 | ||
+ | pc <= pc+1; | ||
+ | jp <= 0; | ||
+ | lda<= 0; //lda指令执行完成 | ||
+ | end | ||
+ | 5'b00010: begin //add <= 1; | ||
+ | b<=q_data; //存储单元数据送前端寄存器b | ||
+ | a<=da; //累计器da内容送前端寄存器a | ||
+ | jp <= 5; | ||
+ | end | ||
+ | 5'b00011: begin //out <= 1; | ||
+ | oo <= q_data; //将数据存储单元输出 | ||
+ | pc <= pc+1; | ||
+ | jp <= 0; | ||
+ | out<= 0; | ||
+ | end | ||
+ | | ||
+ | 5'b00110: begin //str <= 1; | ||
+ | dwren <= 1; //发出写数据寄存器信号 | ||
+ | jp <= 5; | ||
+ | end | ||
+ | 5'b00111: begin //sub <= 1; | ||
+ | b<=q_data; | ||
+ | a<=da; | ||
+ | jp <= 5; | ||
+ | end | ||
+ | | ||
+ | 5'b01011: begin //call <= 1; | ||
+ | sp <= sp+1; //写完堆栈之后,堆栈指针前移一位 | ||
+ | swren <= 0; //停止写堆栈信号 | ||
+ | jp <= 5; | ||
+ | end | ||
+ | 5'b01100: begin //ret <= 1; | ||
+ | pc <= q_s; //返回地址送到程序计数器 | ||
+ | ret <= 0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b01101: begin //mult <= 1; | ||
+ | b<=q_data; | ||
+ | a<=da; | ||
+ | jp <= 5; | ||
+ | end | ||
+ | 5'b01110: begin //divi <= 1; | ||
+ | b<=q_data; | ||
+ | a<=da; | ||
+ | jp <= 5; | ||
+ | end | ||
+ | default: jp <= 0; | ||
+ | endcase | ||
+ | end | ||
</code> | </code> | ||
- | 在这一节拍完成的指令有lda、out、ret,除了ret之外,在结束时都对pc进行了加1操作,目的是让CPU转到下一条指令取指执行。 | + | 在这一节拍完成的指令有lda、out、ret,除了ret之外,在结束时都对pc进行了加1操作,目的是让CPU转到下一条指令取指执行。\\ |
- | 执行到这里,jp=5时就只剩下6条指令了,由于它们执行时基本动作较多,因而占用的时钟节拍也多。 | + | 执行到这里,jp=5时就只剩下6条指令了,由于它们执行时基本动作较多,因而占用的时钟节拍也多。\\ |
<code verilog> | <code verilog> | ||
+ | 5: begin | ||
+ | case (q_w[15:11]) | ||
+ | 5'b00010: begin //add <= 1; | ||
+ | da <= a+b; //相加结果送累加器da | ||
+ | pc <= pc+1; | ||
+ | add <=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | | ||
+ | 5'b00110: begin //str <= 1; | ||
+ | dwren <= 0; //结束写存储器信号 | ||
+ | pc <= pc+1; | ||
+ | str <=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b00111: begin //sub <= 1; | ||
+ | da <= a-b; //将减法运算结果送累加器da | ||
+ | pc <= pc+1; | ||
+ | sub<=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b01011: begin //call <= 1; | ||
+ | swren <= 0; //结束写堆栈信号 | ||
+ | call<=0; | ||
+ | jp<=0; | ||
+ | end | ||
+ | 5'b01101: begin //mult <= 1; | ||
+ | da <= a*b; //将乘法运算结果送累加器 | ||
+ | pc <= pc+1; | ||
+ | mult <=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | 5'b01110: begin //divi <= 1; | ||
+ | da <= a/b; //将除法运算结果送累加器 | ||
+ | pc <= pc+1; | ||
+ | divi <=0; | ||
+ | jp <= 0; | ||
+ | end | ||
+ | default: jp <= 0; | ||
+ | endcase | ||
+ | end | ||
</code> | </code> | ||
- | + | ====让CPU运行软件程序==== | |
+ | 如何验证我们设计的CPU是成功的呢?\\ | ||
+ | 办法只有用CPU指令系统编写程序,运行在这个CPU上面,如果结果是正确的,那说明我们的设计是成功的。\\ | ||
+ | 这个CPU支持的指令有 | ||
+ | <code verilog> | ||
+ | //指令: | ||
+ | lda, //取数:从数据单元取数到da | ||
+ | add, //加:da与数据单元相加,结果放入da | ||
+ | out, //输出:将数据单元内容输出到输出寄存器 | ||
+ | sdal, //低8位立即数:将8位立即数扩充为16位送da | ||
+ | sdah, //高8位立即数:将8位立即数作为高8位,与原da低8位连接成16位放在da中 | ||
+ | str, //da送数据存储单元: | ||
+ | sub, //减:da与数据单元相减,结果放入da | ||
+ | jmp, //跳转 | ||
+ | jz, //da为0跳转 | ||
+ | jn, //da为负跳转 | ||
+ | call, //调用子程序 | ||
+ | ret, //返回 | ||
+ | mult, //乘:da与数据单元相乘,结果放入da | ||
+ | divi, //除:da除以数据单元,结果放入da | ||
+ | stp; //停止 | ||
+ | </code> | ||
+ | ===设计用于检验的汇编程序=== | ||
+ | 我们用该CPU的指令系统编写一个能够求出8!(8的阶乘)的汇编程序 | ||
+ | <code c> | ||
+ | start: sdal 1 ;将1送到累加器da的低8位 | ||
+ | str one ;累加器内容送到数据存储器的one单元 | ||
+ | str result ;将1送到数据存储器的result单元 | ||
+ | sdal 8 ;将8送到累加器da的低8位 | ||
+ | str x ;将累加器内容送数据存储器x单元 | ||
+ | loop: lda x ;将x单元的数据送到累加器da | ||
+ | jz exit ;如果da=0则跳转到exit地址取指令执行 | ||
+ | mult result ;da的值乘以result的值,结果送到da | ||
+ | str result ;将da的值回送到result | ||
+ | lda x ;将x单元的值送到da | ||
+ | sub one ;da-1送到da | ||
+ | str x ;再将da值送回x | ||
+ | jmp loop ;转到loop地址取指令执行 | ||
+ | exit: out result ;输出最终结果 | ||
+ | stp ;停止CPU运行 | ||
+ | </code> | ||
+ | ===用表来编译汇编程序=== | ||
+ | 现在程序的编译一般都有专门的汇编器,这里我们以人工绘表的方式来编译,这种程序编译表格是最基本的编译工具。\\ | ||
+ | {{::自己设计cpu_编译表.png|}}\\ | ||
+ | 左边“地址”一栏是程序存储器或数据存储器的地址编号,“标号”和“汇编程序”两栏是汇编程序,“二进制编码”一栏是二进制数的机器指令,“编译”一栏是十六进制的机器指令,“数据”一栏是数据变量的位置分配。最后两栏是该设计中的CPU指令和编码。\\ | ||
+ | 这个汇编程序使用了3个16位的数据变量 one、result、x。它们在存储器中的位置被安排在1,2,3号存储单元。\\ | ||
+ | 程序计数器pc的初始值是0,我们将标号start定位0号存储单元,依次往下排可以得到loop标注5号存储单元,exit是13号存储单元。\\ | ||
+ | 二进制编译一栏是对指令操作码和操作数的译码。左面5位是对应指令的编码,如sdal 编码是00100,右面的11位数是对应操作数的编码,其编码依据指令格式来确定。如果操作数是变量型操作数即one,result,x,则对应变量的存储单元地址1,2,3。如果操作数是立即数,则直接使用立即数本身。\\ | ||
+ | ====仿真检验CPU设计==== |