差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
自己设计一款cpu [2018/09/18 16:09]
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>
行 43: 行 44:
  
 ====内部器件和导线描述==== ====内部器件和导线描述====
-CPU内部一般都包含运算器、各种标志寄存器、通用寄存器、累计器、存储器、指令寄存器、程序计数器、通用指针、堆栈指针、节拍器、地址寄存器、输出寄存器,还包括连接这些部件的各种导线,既有单条线,也有成组的线,也称总线。这些内部部件和导线的定义,直接反映了CPU的组成。 +CPU内部一般都包含运算器、各种标志寄存器、通用寄存器、累计器、存储器、指令寄存器、程序计数器、通用指针、堆栈指针、节拍器、地址寄存器、输出寄存器,还包括连接这些部件的各种导线,既有单条线,也有成组的线,也称总线。这些内部部件和导线的定义,直接反映了CPU的组成。\\ 
-存储类型的设备一般用关键字“reg”来定义,属于导线类型的用“wire”关键字来定义。+存储类型的设备一般用关键字“reg”来定义,属于导线类型的用“wire”关键字来定义。\\
 <code verilog> <code verilog>
 +//​CPU内部器件和导线 ​   ​
 +    //​定义16位的程序存储器输出连接导线和数据存储器输入导线
 +    wire [15:0] q_w,​q_data; ​
 +    //​定义保持写数据存储器和写堆栈存储器控制信号的寄存器
 +    reg         ​dwren,​swren;​
 +    //​定义16位用于保持取出指令进行分析的指令寄存器
 +    reg  [15:0] ir;
 +    //​定义16位的运算器前端寄存器a、b,​累加器da,输出寄存器oo和暂存输入数据寄存器ddata
 +    reg  [15:0] b,​a,​da,​oo,​ddata;​
 +    //​定义11位的程序计数器pc,地址残存寄存器pc_back,数据存储地址寄存器mar,堆栈指针sp和堆栈输出寄存器q_s
 +    reg  [10:0] pc,​pc_back,​mar,​sp,​q_s;​
 +    //​定义3位的CPU节拍寄存器
 +    reg  [2:0]  jp;    ​
  
 </​code>​ </​code>​
行 52: 行 66:
 在这个简易CPU中,我们采用程序存储器和数据存储器分开的设计架构,也就是哈佛结构,而且我们将堆栈存储器单独出来,这样该设计中一共有3个存储器,我们通过FPGA厂商提供的Memory IP来生成这些存储器。 在这个简易CPU中,我们采用程序存储器和数据存储器分开的设计架构,也就是哈佛结构,而且我们将堆栈存储器单独出来,这样该设计中一共有3个存储器,我们通过FPGA厂商提供的Memory IP来生成这些存储器。
 <code verilog> <code verilog>
 +//​指令存储器: ​  
 +    lpm_rom iram(.address(pc),​.inclock(clock),​.q(q_w)); ​ //​程序存储器
 +    defparam iram.lpm_width = 16;
 +    defparam iram.lpm_widthad = 11;
 +    defparam iram.lpm_outdata = "​UNREGISTERED";​
 +    defparam iram.lpm_indata = "​REGISTERED";​
 +    defparam iram.lpm_address_control = "​REGISTERED";​
 +    defparam iram.lpm_file = "​imem16_2013.mif"; ​ //​初始化文件,​放置程序
 +//​数据存储器:  ​
 +    lpm_ram_dq dram(.data(ddata),​.address(mar),​.we(dwren),​.inclock(clock),​.q(q_data));​ //​数据存储器
 +    defparam dram.lpm_width = 16;
 +    defparam dram.lpm_widthad = 10;
 +    defparam dram.lpm_outdata = "​UNREGISTERED";​
 +    defparam dram.lpm_indata = "​REGISTERED";​
 +    defparam dram.lpm_address_control = "​REGISTERED";​
 +    ​
 +    lpm_ram_dq sram(.data(pc_back),​.address(sp),​.we(swren),​.inclock(clock),​.q(q_s));​ //堆栈
 +    defparam sram.lpm_width = 11;
 +    defparam sram.lpm_widthad = 10;
 +    defparam sram.lpm_outdata = "​UNREGISTERED";​
 +    defparam sram.lpm_indata = "​REGISTERED";​
 +    defparam sram.lpm_address_control = "​REGISTERED";​
  
 </​code>​ </​code>​
行 59: 行 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设计====