에피소드 IX: 이상한 명령어 LUI, AUIPC, JAL, JALR 추가하기

사실 이 명령어들은 그렇게 복잡하지 않습니다. 이 명령어들이 무엇을 해야 하는지 다시 살펴봅시다:

명령어 설명 알고리즘
LUI 상위 즉시값 로드 reg <- (im << 12)
AUIPC PC에 상위 즉시값 더하기 reg <- PC+(im << 12)
JAL 점프 후 링크 reg <- PC+4 ; PC <- PC+imm
JALR 레지스터 기준 점프 후 링크 reg <- PC+4 ; PC <- reg+imm

먼저 대입문의 우변을 살펴봅시다. PC와 즉시값 사이의 덧셈을 계산해야 합니다. 이를 위한 ALU 입력용 mux 두 개가 이미 있으므로 따로 할 일은 없습니다. 다만 LUI는 즉시값을 레지스터에 바로 복사하므로 특별한 처리가 필요합니다. 이는 두 번째 레지스터의 선택 신호(sel2)를 디코더에서 나오는 추가 신호 regId1Sel과 AND 게이트로 묶으면 쉽게 처리할 수 있습니다. 이 신호가 0이면 해당 레지스터가 zero로 대체되어 즉시값에 0을 더하게 됩니다. 1비트 신호 하나를 추가하는 다소 우회적인 방법이지만, 이전 설계에 대한 변경을 최소화하고 / 복잡도도 최소한으로만 늘립니다. 이제 JAL과 JALR을 살펴봅시다. 이 두 명령어는 PC+4를 레지스터 파일에 다시 기록해야 합니다. 이를 위해 레지스터 파일의 라이트백(write back) 입력에 ALU 출력과 PC+4 중 하나를 선택하는 mux를 추가합니다. 이 두 가지 수정 사항은 위 회로도에 나와 있습니다.

이제 (거의 완성된) 명령어 디코더를 살펴볼 차례입니다. 복잡해 보이지만, 사실 7가지 명령어에 필요한 모든 신호를 생성하는 커다란 switch 문으로 작성된 큰 조합 함수에 불과합니다(load와 store는 아직 없으며, 다음 절에서 살펴보겠습니다). 명령어 디코더의 Verilog 소스를 보기 전에, 6502(80년대에 매우 널리 쓰인 8비트 프로세서)의 플로어 플랜(floor plan)을 살펴봅시다:

이 이미지는 위키백과에서 가져왔습니다. visual6502 프로젝트도 참고하십시오. 그곳에 많은 정보가 있습니다. 80년대 프로세서는 설계가 충분히 단순하므로 살펴볼 만합니다. 명령어 디코더는 이미지 상단에 있는 격자 모양의 구조입니다. 가로 방향으로는 현재 디코딩 중인 명령어의 8비트와 이를 반전한 8비트가 있고, 세로 방향으로는 이 비트들 사이에서 계산되는 모든 ANDs가 있으며(격자 위의 각 '점'이 하나의 AND에 해당합니다), 이 세로선들이 다시 ORs로 결합되어 프로세서의 내부 신호를 만듭니다.

다시 우리의 RISC-V 프로세서로 돌아갑시다. 둘보다 많은 소스 중 하나를 선택하는 신호는 소스마다 선을 하나씩 사용한다는 점에 유의하십시오(예를 들어 4개의 소스 중 하나를 선택할 때는 0001, 0010, 0100, 1000을 사용합니다). 이른바 원핫 인코딩(one hot encoding)이라 불리는 이 방식은 (00, 01, 10, 11)을 대신 쓸 수도 있으니 어리석어 보일 수 있지만, 실제로는 (당연히) 논리 깊이를 줄여 주고, 더 놀랍게도 결국 LUT를 절약하므로 이득만 있습니다.

module NrvDecoder(
    input wire [31:0] instr,
    output wire [4:0] writeBackRegId,
    output reg        writeBackEn,
    output reg [3:0]  writeBackSel, // 0001: ALU  0010: PC+4  (and also, later, 0100:RAM 1000:counters)
    output wire [4:0] inRegId1,
    output wire [4:0] inRegId2,
    output reg        aluSel, // 0: force aluOp,aluQual to zero (ADD)  1: use aluOp,aluQual from instr field
    output reg        aluInSel1, // 0: reg  1: pc
    output reg        aluInSel2, // 0: reg  1: imm
    output [2:0]      aluOp,
    output reg        aluQual,
    output reg [2:0]  nextPCSel, // 001: PC+4  010: ALU  100: (predicate ? ALU : PC+4)
    output reg [31:0] imm,
    output reg        error
);

   reg inRegId1Sel; // 0: force inRegId1 to zero 1: use inRegId1 instr field

   // The control signals directly deduced from (fixed pos) fields
   assign writeBackRegId = instr[11:7];
   assign inRegId1       = instr[19:15] & {5{inRegId1Sel}}; // Internal sig InRegId1Sel used to force zero in reg1
   assign inRegId2       = instr[24:20];             
   assign aluOp          = instr[14:12];  

   // The five immediate formats, see the RiscV reference, Fig. 2.4 p. 12
   wire [31:0] Iimm = {{21{instr[31]}}, instr[30:20]};
   wire [31:0] Simm = {{21{instr[31]}}, instr[30:25], instr[11:7]};
   wire [31:0] Bimm = {{20{instr[31]}}, instr[7], instr[30:25], instr[11:8], 1'b0};
   wire [31:0] Jimm = {{12{instr[31]}}, instr[19:12], instr[20], instr[30:21], 1'b0};   
   wire [31:0] Uimm = {instr[31], instr[30:12], {12{1'b0}}};

   // The rest of instruction decoding, for the following signals:
   // writeBackEn
   // writeBackSel   0001: ALU  0010: PC+4 (two additional bits for RAM and COUNTERS)
   // inRegId1Sel    0: zero   1: regId
   // aluInSel1      0: reg    1: PC 
   // aluInSel2      0: reg    1: imm
   // aluQual        +/- SRLI/SRAI
   // aluSel         0: force aluOp,aluQual=00  1: use aluOp/aluQual
   // nextPCSel      001: PC+4  010: ALU   100: (pred ? ALU : PC+4)
   // imm (select one of Iimm,Simm,Bimm,Jimm,Uimm)

   // We need to distingish shifts, because
   //   for ALU ops with immediates, aluQual is 0, except
   //   for shifts (then it is instr[30]). Had a hard time
   //   debugging / understanding that.
   wire aluOpIsShift = (aluOp == 3'b001) || (aluOp == 3'b101);
   
   always @(*) begin

       error = 1'b0;
       nextPCSel = 3'b001;  // default: PC <- PC+4
       inRegId1Sel = 1'b1; // reg 1 Id from instr
       aluQual = 1'b0;
      
       (* parallel_case, full_case *)
       case(instr[6:0])
       7'b0110111: begin // LUI
          writeBackEn  = 1'b1;    // enable write back
          writeBackSel = 4'b0001; // write back source = ALU
          inRegId1Sel = 1'b0;     // reg 1 Id = 0
          aluInSel1 = 1'b0;       // ALU source 1 = reg       
          aluInSel2 = 1'b1;       // ALU source 2 = imm
          aluSel = 1'b0;          // ALU op = ADD
          imm = Uimm;             // imm format = U
       end
     
       7'b0010111: begin // AUIPC
          writeBackEn  = 1'b1;    // enable write back
          writeBackSel = 4'b0001; // write back source = ALU
          inRegId1Sel = 1'bx;     // reg 1 Id : don't care (we use PC)        
          aluInSel1 = 1'b1;       // ALU source 1 = PC        
          aluInSel2 = 1'b1;       // ALU source 2 = imm
          aluSel = 1'b0;          // ALU op = ADD
          imm = Uimm;             // imm format = U
       end
     
       7'b1101111: begin // JAL
          writeBackEn  = 1'b1;    // enable write back
          writeBackSel = 4'b0010; // write back source = PC+4
          inRegId1Sel = 1'bx;     // reg 1 Id : don't care (we use PC)                
          aluInSel1 = 1'b1;       // ALU source 1 = PC        
          aluInSel2 = 1'b1;       // ALU source 2 = imm
          aluSel = 1'b0;          // ALU op = ADD
          nextPCSel = 3'b010;     // PC <- ALU        
          imm = Jimm;             // imm format = J
       end
     
       7'b1100111: begin // JALR
          writeBackEn  = 1'b1;    // enable write back
          writeBackSel = 4'b0010; // write back source = PC+4
          aluInSel1 = 1'b0;       // ALU source 1 = reg       
          aluInSel2 = 1'b1;       // ALU source 2 = imm
          aluSel = 1'b0;          // ALU op = ADD
          nextPCSel = 3'b010;     // PC <- ALU        
          imm = Iimm;             // imm format = I
       end
     
       7'b1100011: begin // Branch
          writeBackEn = 1'b0;     // disable write back
          writeBackSel = 4'bxxxx; // write back source = don't care
          aluInSel1 = 1'b1;       // ALU source 1 : PC
          aluInSel2 = 1'b1;       // ALU source 2 : imm
          aluSel = 1'b0;          // ALU op = ADD
          nextPCSel = 3'b100;     // PC <- pred ? ALU : PC+4           
          imm = Bimm;             // imm format = B
       end
       
       7'b0010011: begin // ALU operation: Register,Immediate
          writeBackEn = 1'b1;     // enable write back
          writeBackSel = 4'b0001; // write back source = ALU
          aluInSel1 = 1'b0;       // ALU source 1 : reg
          aluInSel2 = 1'b1;       // ALU source 2 : imm
                                  // Qualifier for ALU op: SRLI/SRAI
          aluQual = aluOpIsShift ? instr[30] : 1'b0;
          aluSel = 1'b1;         // ALU op : from instr
          imm = Iimm;            // imm format = I
       end
       
       7'b0110011: begin // ALU operation: Register,Register
          writeBackEn = 1'b1;     // enable write back
          writeBackSel = 4'b0001; // write back source = ALU
          aluInSel1 = 1'b0;       // ALU source 1 : reg
          aluInSel2 = 1'b0;       // ALU source 2 : reg
          aluQual = instr[30];    // Qualifier for ALU op: +/- SRL/SRA
          aluSel = 1'b1;          // ALU op : from instr
          error = instr[25];
          imm = 32'bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; // don't care
       end
       
           default: begin
          writeBackEn = 1'b0;
          error = 1'b1;
          writeBackSel = 4'bxxxx;   
          inRegId1Sel = 1'bx; 
          aluInSel1 = 1'bx;      
          aluInSel2 = 1'bx;      
          aluSel = 1'bx;      
          nextPCSel = 3'bxxx;  
          imm = 32'bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
       end
       endcase
   end

endmodule

다음