fpga_feixiang 发表于 2020-6-30 15:14:06

RISC CPU结构

RISC_CPU是一个复杂的数字逻辑电路,但是它的基本部件的逻辑并不复杂。可把它分成八个基本部件:
时钟发生器
指令寄存器
累加器
RISC CPU算术逻辑运算单元
数据控制器
状态控制器
程序计数器
地址多路器
其中时钟发生器利用外来时钟信号进行分频生成一系列时钟信号,送往其他部件用作时钟信号。各部件之间的相互操作关系则由状态控制器来控制。下面逐一介绍各部件的具体结构和逻辑关系。
1 时钟发生器

         时钟发生器 clkgen 利用外来时钟信号clk 来生成一系列时钟信号clk1、fetch、alu_clk 送往CPU的其他部件。其中fetch是外来时钟 clk 的八分频信号。利用fetch的上升沿来触发CPU控制器开始执行一条指令,同时fetch信号还将控制地址多路器输出指令地址和数据地址。clk1信号用作指令寄存器、累加器、状态控制器的时钟信号。alu_clk 则用于触发算术逻辑运算单元。时钟发生器clkgen的波形见下图所示:

其VerilogHDL 程序见下面的模块:
module clk_gen (clk,reset,clk1,clk2,clk4,fetch,alu_clk);input clk,reset;output clk1,clk2,clk4,fetch,alu_clk;wire clk,reset;reg clk2,clk4,fetch,alu_clk;reg state;parameter S1 = 8'b00000001, S2 = 8'b00000010, S3 = 8'b00000100, S4 = 8'b00001000, S5 = 8'b00010000, S6 = 8'b00100000, S7 = 8'b01000000, S8 = 8'b10000000, idle = 8'b00000000;assign clk1 = ~clk;always @(negedge clk) if(reset)  begin   clk2 <= 0;   clk4 <= 1;   fetch <= 0;   alu_clk <= 0;   state <= idle;  end else  begin   case(state)   S1:    begin     clk2 <= ~clk2;     alu_clk <= ~alu_clk;     state <= S2;    end   S2:    begin     clk2 <= ~clk2;     clk4 <= ~clk4;     alu_clk <= ~alu_clk;     state <= S3;    end   S3:    begin     clk2 <= ~clk2;     state <= S4;    end   S4:    begin     clk2 <= ~clk2;     clk4 <= ~clk4;     fetch <= ~fetch;     state <= S5;    end   S5:    begin     clk2 <= ~clk2;     state <= S6;    end   S6:    begin     clk2 <= ~clk2;     clk4 <= ~clk4;     state <= S7;    end   S7:    begin     clk2 <= ~clk2;     state <= S8;    end   S8:    begin     clk2 <= ~clk2;     clk4 <= ~clk4;     fetch <= ~fetch;     state <= S1;    end   idle: state <= S1;   default: state <= idle;  endcase endendmodule//--------------------------------------------------------------------------------
          由于在时钟发生器的设计中采用了同步状态机的设计方法,不但使clk_gen模块的源程序可以被各种综合器综合,也使得由其生成的clk1、clk2、clk4、fetch、alu_clk 在跳变时间同步性能上有明显的提高,为整个系统的性能提高打下了良好的基础。
2 指令寄存器

       顾名思义,指令寄存器用于寄存指令。
       指令寄存器的触发时钟是clk1,在clk1的正沿触发下,寄存器将数据总线送来的指令存入高8位或低8位寄存器中。但并不是每个clk1的上升沿都寄存数据总线的数据,因为数据总线上有时传输指令,有时传输数据。什么时候寄存,什么时候不寄存由CPU状态控制器的load_ir信号控制。load_ir信号通过ena 口输入到指令寄存器。复位后,指令寄存器被清为零。
       每条指令为2个字节,即16位。高3位是操作码,低13位是地址。(CPU的地址总线为13位,寻址空间为8K字节。)本设计的数据总线为8位,所以每条指令需取两次。先取高8位,后取低8位。而当前取的是高8位还是低8位,由变量state记录。state为零表示取的高8位,存入高8位寄存器,同时将变量state置为1。下次再寄存时,由于state为1,可知取的是低8位,存入低8位寄存器中。
        其VerilogHDL 程序见下面的模块:
//---------------------------------------------------------------module register(opc_iraddr,data,ena,clk1,rst);output opc_iraddr;input data;input ena, clk1, rst;reg opc_iraddr;reg state;always @(posedge clk1)begin if(rst)  begin   opc_iraddr<=16'b0000_0000_0000_0000;   state<=1'b0;  end else  begin   if(ena) //如果加载指令寄存器信号load_ir到来,    begin //分两个时钟每次8位加载指令寄存器     casex(state) //先高字节,后低字节      1’b0: begin       opc_iraddr<=data;       state<=1;      end      1’b1: begin       opc_iraddr<=data;       state<=0;      end      default: begin       opc_iraddr<=16'bxxxxxxxxxxxxxxxx;       state<=1'bx;      end     endcase    end   else    state<=1'b0;  endendendmodule//--------------------------------------------------------
3.累加器

           累加器用于存放当前的结果,它也是双目运算其中一个数据来源。复位后,累加器的值是零。当累加器通过ena口收到来自CPU状态控制器load_acc信号时,在clk1时钟正跳沿时就收到来自于数据总线的数据。
           其VerilogHDL 程序见下面的模块:
//--------------------------------------------------------------module accum( accum, data, ena, clk1, rst);outputaccum;inputdata;input ena,clk1,rst;regaccum;always@(posedge clk1) begin  if(rst)   accum<=8'b0000_0000; //Reset  else   if(ena) //当CPU状态控制器发出load_acc信号    accum<=data; //Accumulate endendmodule
4.算术运算器

       算术逻辑运算单元 根据输入的8种不同操作码分别实现相应的加、与、异或、跳转等8种基本操作运算。利用这几种基本运算可以实现很多种其它运算以及逻辑判断等操作。
       其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------module alu (alu_out, zero, data, accum, alu_clk, opcode);output alu_out;output zero;input data, accum;input opcode;input alu_clk;reg alu_out;parameter HLT =3’b000, SKZ =3’b001, ADD =3’b010, ANDD =3’b011, XORR =3’b100, LDA =3’b101, STO =3’b110, JMP =3’b111;assign zero = !accum;always @(posedgealu_clk) begin //操作码来自指令寄存器的输出opc_iaddr<15..0>的低3位  casex (opcode)   HLT: alu_out<=accum;   SKZ: alu_out<=accum;   ADD: alu_out<=data+accum;   ANDD: alu_out<=data&accum;   XORR: alu_out<=data^accum;   LDA: alu_out<=data;   STO: alu_out<=accum;   JMP: alu_out<=accum;   default: alu_out<=8'bxxxx_xxxx;  endcase endendmodule//----------------------------------------------------------------------------
5.数据控制器

         数据控制器的作用是控制累加器数据输出,由于数据总线是各种操作时传送数据的公共通道,不同的情况下传送不同的内容。有时要传输指令,有时要传送RAM区或接口的数据。累加器的数据只有在需要往RAM区或端口写时才允许输出,否则应呈现高阻态,以允许其它部件使用数据总线。 所以任何部件往总线上输出数据时,都需要一控制信号。而此控制信号的启、停,则由CPU状态控制器输出的各信号控制决定。数据控制器何时输出累加器的数据则由状态控制器输出的控制信号datactl_ena决定。
        其VerilogHDL 程序见下面的模块:
//--------------------------------------------------------------------module datactl (data,in,data_ena);output data;input in;input data_ena;assign data = (data_ena)? In : 8'bzzzz_zzzz;endmodule//--------------------------------------------------------------------
6.地址多路器

        地址多路器用于选择输出的地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM中读取指令,输出的应是PC地址。后4个时钟周期用于对RAM或端口的读写,该地址由指令中给出。地址的选择输出信号由时钟信号的8分频信号fetch提供。
        其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------module adr(addr,fetch,ir_addr,pc_addr);output addr;input ir_addr, pc_addr;input fetch;assign addr = fetch? pc_addr : ir_addr;endmodule//------------------------------------------------------------------------------
7.程序计数器

       程序计数器用于提供指令地址。以便读取指令,指令按地址顺序存放在存储器中。有两种途径可形成指令地址:其一是顺序执行的情况,其二是遇到要改变顺序执行程序的情况,例如执行JMP指令后,需要形成新的指令地址。复位后,指令指针为零,即每次CPU重新启动将从ROM的零地址开始读取指令并执行。每条指令执行完需2个时钟,这时pc_addr已被增2,指向下一条指令。(因为每条指令占两个字节。)如果正执行的指令是跳转语句,这时CPU状态控制器将会输出load_pc信号,通过load口进入程序计数器。程序计数器(pc_addr)将装入目标地址(ir_addr),而不是增2。
       其VerilogHDL 程序见下面的模块:
//------------------------------------------------------------------------------module counter ( pc_addr, ir_addr, load, clock, rst);output pc_addr;input ir_addr;input load, clock, rst;reg pc_addr;always @( posedge clock or posedge rst ) begin  if(rst)   pc_addr<=13'b0_0000_0000_0000;  else   if(load)    pc_addr<=ir_addr;   else    pc_addr <= pc_addr + 1; endendmodule//------------------------------------------------------------------------------
8.状态控制器

     状态控制器由两部分组成:
状态机(上图中的MACHINE部分)
状态控制器(上图中的MACHINECTL部分)
状态机控制器接受复位信号RST,当RST有效时通过信号ena使其为0,输入到状态机中停止状态机的工作。
         状态控制器的VerilogHDL程序见下面模块:
//------------------------------------------------------------------------------module machinectl( ena, fetch, rst);output ena;input fetch, rst;reg ena;always @(posedge fetch or posedge rst)begin if(rst)  ena<=0; else  ena<=1;endendmodule//------------------------------------------------------------------------------
       状态机是CPU的控制核心,用于产生一系列的控制信号,启动或停止某些部件。CPU何时进行读指令读写I/O端口,RAM区等操作,都是由状态机来控制的。状态机的当前状态,由变量state记录,state的值就是当前这个指令周期中已经过的时钟数(从零计起)。
指令周期是由8个时钟周期组成,每个时钟周期都要完成固定的操作。
第0个时钟,因为CPU状态控制器的输出:rd和load_ir为高电平,其余均为低电平。指令寄存器寄存由ROM送来的高8位指令代码。
第1个时钟,与上一时钟相比只是inc_pc从0变为1故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位代码。
第2个时钟,空操作。
第3个时钟,PC增1,指向下一条指令。若操作符为HLT,则输出信号HLT为高。如果操作符不为HLT,除了PC增一外(指向下一条指令),其它各控制线输出为零。
第4个时钟,若操作符为AND、ADD、XOR或LDA,读相应地址的数据;若为JMP,将目的地址送给程序计数器;若为STO,输出累加器数据。
第5个时钟,若操作符为ANDD、ADD或XORR,算术运算器就进行相应的运算;若为LDA,就把数据通过算术运算器送给累加器;若为SKZ,先判断累加器的值是否为0,如果为0,PC就增1,否则保持原值;若为JMP,锁存目的地址;若为STO,将数据写入地址处。
第6个时钟,空操作。
第7个时钟,若操作符为SKZ且累加器值为0,则PC值再增1,跳过一条指令,否则PC无变化。
状态机的VerilogHDL 程序见下面模块:
//------------------------------------------------------------------------------module machine( inc_pc, load_acc, load_pc, rd,wr, load_ir,datactl_ena, halt, clk1, zero, ena, opcode );output inc_pc, load_acc, load_pc, rd, wr, load_ir;output datactl_ena, halt;input clk1, zero, ena;input opcode;reg inc_pc, load_acc, load_pc, rd, wr, load_ir;reg datactl_ena, halt;reg state;parameter HLT = 3 'b000, SKZ = 3 'b001, ADD = 3 'b010, ANDD = 3 'b011, XORR = 3 'b100, LDA = 3 'b101, STO = 3 'b110, JMP = 3 'b111;always @( negedge clk1 ) begin  if ( !ena ) //接收到复位信号RST,进行复位操作   begin    state<=3'b000;    {inc_pc,load_acc,load_pc,rd}<=4'b0000;    {wr,load_ir,datactl_ena,halt}<=4'b0000;   end  else   ctl_cycle; end//-----------------begin of task ctl_cycle---------task ctl_cycle;begin casex(state)  3’b000: //load high 8bits in struction   begin    {inc_pc,load_acc,load_pc,rd}<=4'b0001;    {wr,load_ir,datactl_ena,halt}<=4'b0100;    state<=3’b001;   end  3’b001: //pc increased by one then load low 8bits instruction   begin    {inc_pc,load_acc,load_pc,rd}<=4'b1001;    {wr,load_ir,datactl_ena,halt}<=4'b0100;    state<=3’b010;   end  3’b010: //idle   begin    {inc_pc,load_acc,load_pc,rd}<=4'b0000;    {wr,load_ir,datactl_ena,halt}<=4'b0000;    state<=3’b011;   end  3’b011: //next instruction address setup 分析指令从这里开始   begin    if(opcode==HLT) //指令为暂停HLT     begin      {inc_pc,load_acc,load_pc,rd}<=4'b1000;      {wr,load_ir,datactl_ena,halt}<=4'b0001;     end    else     begin      {inc_pc,load_acc,load_pc,rd}<=4'b1000;      {wr,load_ir,datactl_ena,halt}<=4'b0000;     end    state<=3’b100;   end  3’b100: //fetch oprand   begin    if(opcode==JMP)     begin      {inc_pc,load_acc,load_pc,rd}<=4'b0010;      {wr,load_ir,datactl_ena,halt}<=4'b0000;     end    else    if( opcode==ADD || opcode==ANDD ||     opcode==XORR || opcode==LDA)      begin       {inc_pc,load_acc,load_pc,rd}<=4'b0001;       {wr,load_ir,datactl_ena,halt}<=4'b0000;      end    else     if(opcode==STO)      begin       {inc_pc,load_acc,load_pc,rd}<=4'b0000;       {wr,load_ir,datactl_ena,halt}<=4'b0010;      end    else     begin      {inc_pc,load_acc,load_pc,rd}<=4'b0000;      {wr,load_ir,datactl_ena,halt}<=4'b0000;    end    state<=3’b101;   end  3’b101: //operation   begin    if ( opcode==ADD||opcode==ANDD||opcode==XORR||opcode==LDA )     begin //过一个时钟后与累加器的内容进行运算      {inc_pc,load_acc,load_pc,rd}<=4'b0101;      {wr,load_ir,datactl_ena,halt}<=4'b0000;     end    else     if( opcode==SKZ && zero==1)      begin       {inc_pc,load_acc,load_pc,rd}<=4'b1000;       {wr,load_ir,datactl_ena,halt}<=4'b0000;      end    else     if(opcode==JMP)      begin       {inc_pc,load_acc,load_pc,rd}<=4'b1010;       {wr,load_ir,datactl_ena,halt}<=4'b0000;      end    else     if(opcode==STO)      begin       //过一个时钟后把wr变1就可写到RAM中       {inc_pc,load_acc,load_pc,rd}<=4'b0000;       {wr,load_ir,datactl_ena,halt}<=4'b1010;      end    else     begin      {inc_pc,load_acc,load_pc,rd}<=4'b0000;      {wr,load_ir,datactl_ena,halt}<=4'b0000;     end    state<=3’b110;   end 3’b110: //idle  begin   if ( opcode==STO )    begin     {inc_pc,load_acc,load_pc,rd}<=4'b0000;     {wr,load_ir,datactl_ena,halt}<=4'b0010;    end   else    if ( opcode==ADD||opcode==ANDD||opcode==XORR||opcode==LDA)     begin      {inc_pc,load_acc,load_pc,rd}<=4'b0001;      {wr,load_ir,datactl_ena,halt}<=4'b0000;     end   else    begin     {inc_pc,load_acc,load_pc,rd}<=4'b0000;     {wr,load_ir,datactl_ena,halt}<=4'b0000;    end  state<=3’b111;  end 3’b111: //  begin   if( opcode==SKZ && zero==1 )    begin     {inc_pc,load_acc,load_pc,rd}<=4'b1000;     {wr,load_ir,datactl_ena,halt}<=4'b0000;    end   else    begin     {inc_pc,load_acc,load_pc,rd}<=4'b0000;     {wr,load_ir,datactl_ena,halt}<=4'b0000;    end   state<=3’b000;  end default:  begin   {inc_pc,load_acc,load_pc,rd}<=4'b0000;   {wr,load_ir,datactl_ena,halt}<=4'b0000;   state<=3’b000;  end endcaseendendtask//-----------------end of task ctl_cycle---------endmodule//------------------------------------------------------------------------------
9.外围模块
为了对RISC_CPU进行测试,需要有存储测试程序的ROM和装载数据的RAM、地址译码器。下面来简单介绍一下:
地址译码器
module addr_decode( addr, rom_sel, ram_sel);output rom_sel, ram_sel;input addr;reg rom_sel, ram_sel;always @( addr )begin casex(addr)  13'b1_1xxx_xxxx_xxxx:{rom_sel,ram_sel}<=2'b01;  13'b0_xxxx_xxxx_xxxx:{rom_sel,ram_sel}<=2'b10;  13'b1_0xxx_xxxx_xxxx:{rom_sel,ram_sel}<=2'b10;  default:{rom_sel,ram_sel}<=2'b00; endcaseendendmodule
地址译码器用于产生选通信号,选通ROM或RAM。
FFFFH---1800H RAM
1800H---0000H ROM
RAM
module ram( data, addr, ena, read, write );inout data;input addr;input ena;input read, write;reg ram ;assign data = ( read && ena )? ram : 8'hzz;always @(posedge write)begin ram<=data;endendmodule

ROM
module rom( data, addr, read, ena );output data;input addr;input read, ena;reg memory ;wire data;assign data= ( read && ena )? memory : 8'bzzzzzzzz;endmodule

ROM用于装载测试程序,可读不可写。RAM用于存放数据,可读可写。
————————————————
页: [1]
查看完整版本: RISC CPU结构