21
\$\begingroup\$

This is a simple 4-stage pipeline that partially implements the RV32I ISA.

All instructions are supported, except jalr, those relating to memory (l*, l*u, s*, fence and fence.i), or system calls (sbreak and scall).

The pipeline stages are more or less the classic RISC ones without the memory access stage (i.e. fetch instruction, decode and fetch operands, calculate result, write result).

My ultimate goal is to have a simple CPU with somewhat decent performance for synthesis to an FPGA (I'd like to reach 150-200MHz eventually). This is the first major hardware design project that I have attempted, so I'm fairly sure that I have made a bunch of beginner mistakes.

`define ALU_ADD   0
`define ALU_SUB   1
`define ALU_AND   2
`define ALU_OR    3
`define ALU_XOR   4
`define ALU_SLL   5
`define ALU_SRL   6
`define ALU_SRA   7
`define ALU_SEQ   8
`define ALU_SNE   9
`define ALU_SLT  10
`define ALU_SGE  11
`define ALU_SLTU 12
`define ALU_SGEU 13

`define OPCODE_OP     7'b0110011
`define OPCODE_OP_IMM 7'b0010011
`define OPCODE_LUI    7'b0110111
`define OPCODE_AUIPC  7'b0010111
`define OPCODE_JAL    7'b1101111
`define OPCODE_JALR   7'b1100111
`define OPCODE_BRANCH 7'b1100011
`define OPCODE_SYSTEM 7'b1110011

`define FUNCT3_ADD_SUB 3'b000
`define FUNCT3_SLL     3'b001
`define FUNCT3_SLT     3'b010
`define FUNCT3_SLTU    3'b011
`define FUNCT3_XOR     3'b100
`define FUNCT3_SRL_SRA 3'b101
`define FUNCT3_OR      3'b110
`define FUNCT3_AND     3'b111

`define FUNCT3_BEQ  3'b000
`define FUNCT3_BNE  3'b001
`define FUNCT3_BLT  3'b100
`define FUNCT3_BGE  3'b101
`define FUNCT3_BLTU 3'b110
`define FUNCT3_BGEU 3'b111

`define SYSTEM_RDCYCLE    20'b11000000000000000010
`define SYSTEM_RDCYCLEH   20'b11001000000000000010
`define SYSTEM_RDTIME     20'b11000000000100000010
`define SYSTEM_RDTIMEH    20'b11001000000100000010
`define SYSTEM_RDINSTRET  20'b11000000001000000010
`define SYSTEM_RDINSTRETH 20'b11001000001000000010

module alu (input [3:0]       operation,
            input [31:0]      s1,
            input [31:0]      s2,
            output reg [31:0] d
            );
   wire [4:0]                 shamt = s2[4:0];
   always @ *
     case(operation)
       `ALU_ADD:  d = s1 + s2;
       `ALU_SUB:  d = s1 - s2;
       `ALU_AND:  d = s1 & s2;
       `ALU_OR:   d = s1 | s2;
       `ALU_XOR:  d = s1 ^ s2;
       `ALU_SLL:  d = s1 << shamt;
       `ALU_SRL:  d = s1 >> shamt;
       `ALU_SRA:  d = $signed(s1) >>> shamt;
       `ALU_SEQ:  d = s1 == s2 ? 1 : 0;
       `ALU_SNE:  d = s1 == s2 ? 0 : 1;
       `ALU_SLT:  d = $signed(s1) < $signed(s2) ? 1 : 0;
       `ALU_SGE:  d = $signed(s1) < $signed(s2) ? 0 : 1;
       `ALU_SLTU: d = s1 < s2 ? 1 : 0;
       `ALU_SGEU: d = s1 < s2 ? 0 : 1;
       default:   d = 0;
     endcase
endmodule

module decoder(input [31:0]      insn,
               input [31:0]      pc,
               input [63:0]      cycle,
               input [63:0]      instret,

               output [4:0]      rd,
               output reg        s1_is_imm,
               output [4:0]      rs1,
               output reg [31:0] s1_imm,
               output reg        s2_is_imm,
               output [4:0]      rs2,
               output reg [31:0] s2_imm,
               output reg [3:0]  op_alu,
               output reg        is_jump,
               output reg        is_branch,
               output [31:0]     jump_target
               );
   wire [6:0]                    opcode = insn[ 6: 0];
   wire [2:0]                    funct3 = insn[14:12];
   wire [6:0]                    funct7 = insn[31:25];
   wire [31:0]                   imm12  = {{21{insn[31]}}, insn[30:20]};
   wire [31:0]                   imm20  = {insn[31:12], 12'b0};
   wire [31:0]                   imm12b = {{20{insn[31]}}, insn[7], insn[30:25], insn[11:8], 1'b0};
   wire [31:0]                   imm20j = {{12{insn[31]}}, insn[19:12], insn[20], insn[30:21], 1'b0};
   reg                           rd_write_disable;

   assign rd = rd_write_disable ? 0 : insn[11:7];
   assign rs2 = insn[24:20];
   assign rs1 = insn[19:15];
   assign jump_target = pc + (is_branch ? imm12b : imm20j);

   always @ * begin
      rd_write_disable = 0;
      s1_imm = 0;
      s1_is_imm = 0;
      s2_imm = 0;
      s2_is_imm = 0;
      is_jump = 0;
      is_branch = 0;
      op_alu = `ALU_ADD;
      case(opcode)
        `OPCODE_OP: begin
           case(funct3)
             `FUNCT3_ADD_SUB: op_alu = funct7[5] ? `ALU_SUB : `ALU_ADD;
             `FUNCT3_SLL:     op_alu = `ALU_SLL;
             `FUNCT3_SLT:     op_alu = `ALU_SLT;
             `FUNCT3_SLTU:    op_alu = `ALU_SLTU;
             `FUNCT3_XOR:     op_alu = `ALU_XOR;
             `FUNCT3_SRL_SRA: op_alu = funct7[5] ? `ALU_SRA : `ALU_SRL;
             `FUNCT3_OR:      op_alu = `ALU_OR;
             `FUNCT3_AND:     op_alu = `ALU_AND;
           endcase
        end
        `OPCODE_OP_IMM: begin
           s2_imm = imm12;
           s2_is_imm = 1;
           case(funct3)
             `FUNCT3_ADD_SUB: op_alu = `ALU_ADD;
             `FUNCT3_SLT:     op_alu = `ALU_SLT;
             `FUNCT3_SLTU:    op_alu = `ALU_SLTU;
             `FUNCT3_XOR:     op_alu = `ALU_XOR;
             `FUNCT3_OR:      op_alu = `ALU_OR;
             `FUNCT3_AND:     op_alu = `ALU_AND;
             `FUNCT3_SLL:     op_alu = `ALU_SLL;
             `FUNCT3_SRL_SRA: op_alu = funct7[5] ? `ALU_SRA : `ALU_SRL;
           endcase
        end
        `OPCODE_LUI: begin
           s1_imm = imm20;
           s1_is_imm = 1;
           s2_is_imm = 1;
        end
        `OPCODE_AUIPC: begin
           s1_imm = imm20;
           s1_is_imm = 1;
           s2_imm = pc;
           s2_is_imm = 1;
        end
        `OPCODE_JAL: begin
           s1_is_imm = 1;
           s1_imm = pc;
           s2_is_imm = 1;
           s2_imm = 4;
           is_jump = 1;
        end
        // `OPCODE_JALR: TODO
        `OPCODE_BRANCH: begin
           is_jump = 1;
           is_branch = 1;
           rd_write_disable = 1;
           case(funct3)
             `FUNCT3_BEQ:  op_alu = `ALU_SEQ;
             `FUNCT3_BNE:  op_alu = `ALU_SNE;
             `FUNCT3_BLT:  op_alu = `ALU_SLT;
             `FUNCT3_BGE:  op_alu = `ALU_SGE;
             `FUNCT3_BLTU: op_alu = `ALU_SLTU;
             `FUNCT3_BGEU: op_alu = `ALU_SGEU;
           endcase
        end
        `OPCODE_SYSTEM: begin
           s1_is_imm = 1;
           s2_is_imm = 1;
           case(insn[31:12])
             `SYSTEM_RDCYCLE:    s1_imm = cycle[31:0];
             `SYSTEM_RDCYCLEH:   s1_imm = cycle[63:32];
             `SYSTEM_RDTIME:     s1_imm = cycle[31:0];
             `SYSTEM_RDTIMEH:    s1_imm = cycle[63:32];
             `SYSTEM_RDINSTRET:  s1_imm = instret[31:0];
             `SYSTEM_RDINSTRETH: s1_imm = instret[63:32];
           endcase
        end
      endcase
   end
endmodule

module ezpipe (input         clk,
               input         reset,
               output [31:0] ibus_addr,
               input [31:0]  ibus_data
               // output reg [31:0] dbus_addr,
               // output reg [31:0] dbus_data_wr,
               // input [31:0]      dbus_data_rd,
               // input [31:0]      dbus_data_ready,
               // output reg        dbus_rd,
               // output reg        dbus_wr
               );
   /* registers and counters */
   reg [31:0]                regs [1:31];
   reg [31:0]                pc;
   reg [63:0]                cycle;
   reg [63:0]                instret;
   // There is no counter for RDTIME/RDTIMEH, those instructions just use the cycle register.

   /* pipeline registers */
   // from FETCH to DECODE
   reg [31:0]                f_insn;
   reg [31:0]                f_pc;
   reg                       f_valid;
   // from DECODE to EXECUTE
   reg [31:0]                d_s1;
   reg [31:0]                d_s2;
   reg [3:0]                 d_op_alu;
   reg [4:0]                 d_rd;
   reg                       d_is_jump;
   reg                       d_is_branch;
   reg [31:0]                d_jump_target;
   reg                       d_valid;
   // from EXECUTE to WRITE
   reg [4:0]                 e_rd;
   reg [31:0]                e_d;
   reg                       e_is_jump;
   reg                       e_is_branch;
   reg [31:0]                e_jump_target;
   reg                       e_valid;

   /* instances */
   wire [4:0]                dec_rd;
   wire [4:0]                dec_rs1;
   wire [31:0]               dec_s1_imm;
   wire                      dec_s1_is_imm;
   wire [4:0]                dec_rs2;
   wire [31:0]               dec_s2_imm;
   wire                      dec_s2_is_imm;
   wire [3:0]                dec_op_alu;
   wire                      dec_is_jump;
   wire                      dec_is_branch;
   wire [31:0]               dec_jump_target;
   decoder dec(.pc(f_pc),
               .insn(f_insn),
               .cycle(cycle),
               .instret(instret),
               .op_alu(dec_op_alu),
               .rd(dec_rd),
               .rs1(dec_rs1),
               .s1_is_imm(dec_s1_is_imm),
               .s1_imm(dec_s1_imm),
               .rs2(dec_rs2),
               .s2_is_imm(dec_s2_is_imm),
               .s2_imm(dec_s2_imm),
               .is_jump(dec_is_jump),
               .is_branch(dec_is_branch),
               .jump_target(dec_jump_target)
               );

   wire [31:0]               alu_d;
   alu alu(.s1(d_s1),
           .s2(d_s2),
           .operation(d_op_alu),
           .d(alu_d)
           );

   assign ibus_addr = pc;

   /* the actual pipeline */
   reg                       jumping;
   reg                       stall;
   always @ * begin
      // does the decoded instruction depend on a instruction in the d_* or e_* registers?
      stall = 0;
      if(d_valid && |d_rd) begin
         if(|dec_rs1 && !dec_s1_is_imm && dec_rs1==d_rd)
           stall = 1;
         if(|dec_rs2 && !dec_s2_is_imm && dec_rs2==d_rd)
           stall = 1;
      end
      if(e_valid && |e_rd) begin
         if(|dec_rs1 && !dec_s1_is_imm && dec_rs1==e_rd)
           stall = 1;
         if(|dec_rs2 && !dec_s2_is_imm && dec_rs2==e_rd)
           stall = 1;
      end
      // is there a taken branch/jump sitting in the e_* registers?
      jumping = 0;
      if(e_valid)
        if(e_is_jump) begin
           if(e_is_branch)
             jumping = e_d[0];
           else
             jumping = 1;
        end
   end

   always @(posedge clk) begin
      if(reset) begin
         pc <= 0;
         f_valid <= 0;
         d_valid <= 0;
         e_valid <= 0;
         cycle <= 0;
         instret <= 0;
      end else begin
         cycle <= cycle + 1;

         /* FETCH */
         f_valid <= !jumping;
         if(!stall) begin
            f_insn <= ibus_data;
            f_pc <= pc;
            pc <= pc + 4;
         end else begin
            // don't fetch a new instruction when we can't complete the one in the D stage
         end

         /* DECODE */
         if(!stall) begin
            // fetch operands
            if(dec_s1_is_imm) d_s1 <= dec_s1_imm;
            else              d_s1 <= |dec_rs1 ? regs[dec_rs1] : 0;
            if(dec_s2_is_imm) d_s2 <= dec_s2_imm;
            else              d_s2 <= |dec_rs2 ? regs[dec_rs2] : 0;
            // store decoded instruction
            d_rd <= dec_rd;
            d_op_alu <= dec_op_alu;
            d_jump_target <= dec_jump_target;
            d_is_branch <= dec_is_branch;
            d_is_jump <= dec_is_jump;
            d_valid <= f_valid && !jumping;
         end else begin
            // can't issue this instruction yet; send a bubble down the pipeline
            d_valid <= 0;
         end

         /* EXECUTE */
         // store ALU result
         e_d <= alu_d;
         // send remaining info down the pipeline
         e_rd <= d_rd;
         e_is_jump <= d_is_jump;
         e_is_branch <= d_is_branch;
         e_jump_target <= d_jump_target;
         e_valid <= d_valid && !jumping;

         /* WRITE */
         if(e_valid) begin
            if(jumping)
              pc <= e_jump_target;
            if(|e_rd)
              regs[e_rd] <= e_d;
            instret <= instret + 1;
         end
      end
   end
endmodule

My main questions are:

  • Have I made any rookie mistakes or committed major sins against Verilog style?
  • Are there any ways I can improve the maximum clock/overall performance, without complicating the code too much?
  • Will explicitly using the undefined value (x) in places where the values do not matter actually help the synthesis tool to generate less logic? (An example would be the default case in the ALU.)
\$\endgroup\$
  • 1
    \$\begingroup\$ Which FPGA are you considering? There are usually many good Verilog and VHDL examples bundled with an FPGA if you have one. AFAIK you can also draw these circuits and compile the drawing in a CAD/CAM program such as Quartus II that supports Verilog. \$\endgroup\$ – Niklas Rosencrantz May 8 '16 at 7:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.