Skip to content

第3回 プロセッサの設計2

本日の講義内容

  • 汎用プロセッサの設計 (2)
  • Design Compilerを用いた論理合成

汎用プロセッサの設計 (2)

ALUの設計の続き

プロセッサの全体図。
ALU。

ALUの設計を続けていきます。ALUでは、加減算、論理演算、比較、シフトその他の演算が実行されます。命令デコーダによって生成された制御信号alu_code[4:0]に応じた演算を、2つの入力信号op1op2に対して実行するのでした。命令に応じた演算結果alu_result[31:0]を出力するとともに、分岐の有無についても判定しbr_takenとして出力します。RV32I仕様を参考にして、各命令でどのような演算が実施されるか、各命令においてどのように分岐の有無が判定されるか、考えてみましょう。

演習: ALUの設計

以下のコードを追記修正して、ALUを設計しましょう。

module alu (
    input logic [4:0] alu_code,
    input logic [31:0] op1,
    input logic [31:0] op2,
    output logic [31:0] alu_result,
    output logic br_taken
);

    import cpu_pkg::*;

    always_comb begin
        unique case (alu_code)
            ALU_ADD: alu_result = ;
            ALU_SUB: alu_result = ;
            ALU_XOR: alu_result = ;
            ALU_OR: alu_result = ;
            ALU_AND: alu_result = ;
            ALU_SLT: alu_result = ;
            ALU_SLTU: alu_result = ;
            ALU_SLL: alu_result = ;
            ALU_SRL: alu_result = ;
            ALU_SRA: alu_result = ;
            ALU_LUI: alu_result = op2;
            ALU_JAL, ALU_JALR: alu_result = op1 + 32'd4;
            default: alu_result = 32'd0;
        endcase
    end

    always_comb begin
        unique case (alu_code)
            ALU_BEQ: br_taken = ;
            ALU_BNE: br_taken = ;
            ALU_BLT: br_taken = ;
            ALU_BGE: br_taken = ;
            ALU_BLTU: br_taken = ;
            ALU_BGEU: br_taken = ;
            ALU_JAL, ALU_JALR: br_taken = ENABLE;
            default: br_taken = DISABLE;
        endcase
    end

endmodule
なお、以下のようなパッケージを別ファイルとして併せて作成しておきましょう。
cpu_pkg.sv
package cpu_pkg;

    // control signals
    parameter logic ENABLE = 1'b1;
    parameter logic DISABLE = 1'b0;

    // alu_code
    typedef logic [4:0] alu_code_t;

    parameter alu_code_t ALU_ADD  = 5'd0;
    parameter alu_code_t ALU_SUB  = 5'd1;
    parameter alu_code_t ALU_XOR  = 5'd2;
    parameter alu_code_t ALU_OR   = 5'd3;
    parameter alu_code_t ALU_AND  = 5'd4;
    parameter alu_code_t ALU_SLT  = 5'd5;
    parameter alu_code_t ALU_SLTU = 5'd6;
    parameter alu_code_t ALU_SLL  = 5'd7;
    parameter alu_code_t ALU_SRL  = 5'd8;
    parameter alu_code_t ALU_SRA  = 5'd9;
    parameter alu_code_t ALU_LUI  = 5'd10;
    parameter alu_code_t ALU_BEQ  = 5'd11;
    parameter alu_code_t ALU_BNE  = 5'd12;
    parameter alu_code_t ALU_BLT  = 5'd13;
    parameter alu_code_t ALU_BGE  = 5'd14;
    parameter alu_code_t ALU_BLTU = 5'd15;
    parameter alu_code_t ALU_BGEU = 5'd16;
    parameter alu_code_t ALU_JAL  = 5'd17;
    parameter alu_code_t ALU_JALR = 5'd18;

endpackage

こたえ
alu.sv
module alu (
    input logic [4:0] alu_code,
    input logic [31:0] op1,
    input logic [31:0] op2,
    output logic [31:0] alu_result,
    output logic br_taken
);

    import cpu_pkg::*;

    always_comb begin
        unique case (alu_code)
            ALU_ADD: alu_result = op1 + op2;
            ALU_SUB: alu_result = op1 - op2;
            ALU_XOR: alu_result = op1 ^ op2;
            ALU_OR: alu_result = op1 | op2;
            ALU_AND: alu_result = op1 & op2;
            ALU_SLT: alu_result = (signed'(op1) < signed'(op2)) ? 32'd1 : 32'd0;
            ALU_SLTU: alu_result = (op1 < op2) ? 32'd1 : 32'd0;
            ALU_SLL: alu_result = op1 << op2[4:0];
            ALU_SRL: alu_result = op1 >> op2[4:0];
            ALU_SRA: alu_result = signed'(op1) >>> op2[4:0];
            ALU_LUI: alu_result = op2;
            ALU_JAL, ALU_JALR: alu_result = op1 + 32'd4;
            default: alu_result = 32'd0;
        endcase
    end

    always_comb begin
        unique case (alu_code)
            ALU_BEQ: br_taken = (op1 == op2);
            ALU_BNE: br_taken = (op1 != op2);
            ALU_BLT: br_taken = (signed'(op1) < signed'(op2));
            ALU_BGE: br_taken = (signed'(op1) >= signed'(op2));
            ALU_BLTU: br_taken = (op1 < op2);
            ALU_BGEU: br_taken = (op1 >= op2);
            ALU_JAL, ALU_JALR: br_taken = ENABLE;
            default: br_taken = DISABLE; 
        endcase
    end

endmodule

演習: ALUの検証

テストベンチを作成して、ALUを検証しましょう。 assertを使うと良いと思います。

こたえ

以下のalu_tb.svのようなファイルを作成して、

$ xrun alu_tb.sv alu.sv cpu_pkg.sv
alu_tb.sv
`timescale 1ns/1ps

module alu_tb;

    import cpu_pkg::*;

    logic [4:0] alu_code;
    logic [31:0] op1, op2;
    logic [31:0] alu_result;
    logic br_taken;

    alu dut (
        .alu_code(alu_code),
        .op1(op1),
        .op2(op2),
        .alu_result(alu_result),
        .br_taken(br_taken)
    );

    initial begin
        // ---- ALU_ADD ----
        alu_code = ALU_ADD; op1 = 10; op2 = 20;
        #1;
        assert (alu_result == 32'd30) else $fatal("ADD failed");

        // ---- ALU_SUB ----
        alu_code = ALU_SUB; op1 = 50; op2 = 20;
        #1;
        assert (alu_result == 32'd30) else $fatal("SUB failed");

        // ---- ALU_XOR ----
        alu_code = ALU_XOR; op1 = 32'hF0F0F0F0; op2 = 32'h0F0F0F0F;
        #1;
        assert (alu_result == 32'hFFFFFFFF) else $fatal("XOR failed");

        // ---- ALU_OR ----
        alu_code = ALU_OR; op1 = 32'hF0000000; op2 = 32'h0000F000;
        #1;
        assert (alu_result == 32'hF000F000) else $fatal("OR failed");

        // ---- ALU_AND ----
        alu_code = ALU_AND; op1 = 32'hFF00FF00; op2 = 32'h0F0F0F0F;
        #1;
        assert (alu_result == 32'h0F000F00) else $fatal("AND failed");

        // ---- ALU_SLT ---- (signed)
        alu_code = ALU_SLT; op1 = -5; op2 = 3;
        #1;
        assert (alu_result == 32'd1) else $fatal("SLT failed");

        // ---- ALU_SLTU ---- (unsigned)
        alu_code = ALU_SLTU; op1 = 32'hFFFFFFFF; op2 = 0;
        #1;
        assert (alu_result == 32'd0) else $fatal("SLTU failed");

        // ---- ALU_SLL ----
        alu_code = ALU_SLL; op1 = 1; op2 = 4;
        #1;
        assert (alu_result == 32'd16) else $fatal("SLL failed");

        // ---- ALU_SRL ----
        alu_code = ALU_SRL; op1 = 32'hF0000000; op2 = 4;
        #1;
        assert (alu_result == 32'h0F000000) else $fatal("SRL failed");

        // ---- ALU_SRA ----
        alu_code = ALU_SRA; op1 = 32'hFFFFFFF0; op2 = 4;
        #1;
        assert (alu_result == 32'hFFFFFFFF) else $fatal("SRA failed");

        // ---- ALU_LUI ----
        alu_code = ALU_LUI; op1 = 0; op2 = 32'h12345000;
        #1;
        assert (alu_result == 32'h12345000) else $fatal("LUI failed");

        // ---- ALU_BEQ ----
        alu_code = ALU_BEQ; op1 = 42; op2 = 42;
        #1;
        assert (br_taken == 1'b1) else $fatal("BEQ failed");

        // ---- ALU_BNE ----
        alu_code = ALU_BNE; op1 = 1; op2 = 2;
        #1;
        assert (br_taken == 1'b1) else $fatal("BNE failed");

        // ---- ALU_BLT ----
        alu_code = ALU_BLT; op1 = -10; op2 = 0;
        #1;
        assert (br_taken == 1'b1) else $fatal("BLT failed");

        // ---- ALU_BGE ----
        alu_code = ALU_BGE; op1 = 5; op2 = -5;
        #1;
        assert (br_taken == 1'b1) else $fatal("BGE failed");

        // ---- ALU_BLTU ----
        alu_code = ALU_BLTU; op1 = 1; op2 = 2;
        #1;
        assert (br_taken == 1'b1) else $fatal("BLTU failed");

        // ---- ALU_BGEU ----
        alu_code = ALU_BGEU; op1 = 5; op2 = 4;
        #1;
        assert (br_taken == 1'b1) else $fatal("BGEU failed");

        // ---- ALU_JAL ----
        alu_code = ALU_JAL; op1 = 100; op2 = 0;
        #1;
        assert (alu_result == 32'd104) else $fatal("JAL result failed");
        assert (br_taken == 1'b1) else $fatal("JAL branch failed");

        $display("✅ All ALU tests passed!");
        $finish;
    end

endmodule

命令デコーダの設計

命令デコーダ。

続いて、命令デコーダの設計をおこないます。命令デコーダでは、32bitの機械語命令列insnを入力として

  • 符号拡張した即値: imm[31:0]
  • ALUの演算内容選択信号: alu_code[4:0]
  • ALUへの入力選択信号: alu_op1_sel, alu_op2_sel
  • ロード命令フラグ: is_load[2:0]
  • ストア命令フラグ: is_store[1:0]

といったものを生成するのでした。

RV32I命令フォーマット。

まず、imm[31:0]について考えてみましょう。37命令のそれぞれについて見てみると、即値には次のようなパターンがあります。

  • insn[31:20]imm[11:0]として符号拡張:
    • ADDI, XORI, ORI, ANDI, SLTI, SLTIU, SLLI, SRLI, SRAI, LB, LH, LW, LBU, LHU, JALR
  • insn[31:12]imm[31:12]として下位を0で埋める:
    • LUI, AUIPC
  • {insn[31:25], insn[11:7]}imm[11:0]として符号拡張:
    • SB, SH, SW
  • {insn[31], insn[7], insn[30:25], insn[11:8]}imm[12:1]として符号拡張、下位1bitは0:
    • BEQ, BNE, BLT, BGE, BLTU, BGEU
  • {insn[31], insn[19:12], insn[20], insn[30:21]}imm[20:1]として符号拡張、下位1bitは0:
    • JAL

こうした分類をもとに考えて、まずはimm[31:0]を算出する組み合わせ回路を作ってみましょう。命令の下位7bitをop_code[6:0]として定義して、判別の手がかりにすると良いです。

演習: 命令デコーダの即値算出部分の設計

以下のコードを追記修正して、命令デコーダのimm[31:0]算出部分を設計しましょう。

module decoder (
    input logic [31:0] insn,
    output logic [31:0] imm,
    output logic [4:0] alu_code,
    output logic alu_op1_sel,
    output logic alu_op2_sel,
    output logic reg_we,
    output logic [2:0] is_load,
    output logic [1:0] is_store
);

    logic [6:0] op_code;

    assign op_code = insn[6:0];

    always_comb begin
        unique case (op_code)
            OPIMM: imm = {{20{insn[31]}}, insn[31:20]};
        endcase
    end
なお、以下のようなパラメータを利用すると良いです。
// op_code
typedef logic [6:0] op_code_t;

parameter op_code_t OPIMM  = 7'b0010011;
parameter op_code_t OPREG  = 7'b0110011;
parameter op_code_t LUI    = 7'b0110111;
parameter op_code_t AUIPC  = 7'b0010111;
parameter op_code_t LOAD   = 7'b0000011;
parameter op_code_t STORE  = 7'b0100011;
parameter op_code_t BRANCH = 7'b1100011;
parameter op_code_t JAL    = 7'b1101111;
parameter op_code_t JALR   = 7'b1100111;

こたえ
module decoder (
    input logic [31:0] insn,
    output logic [31:0] imm,
    output logic [4:0] alu_code,
    output logic alu_op1_sel,
    output logic alu_op2_sel,
    output logic reg_we,
    output logic [2:0] is_load,
    output logic [1:0] is_store
);

    logic [6:0] op_code;

    assign op_code = insn[6:0];

    always_comb begin
        unique case (op_code)
            OPIMM, LOAD, JALR: imm = {{20{insn[31]}}, insn[31:20]};
            LUI, AUIPC: imm = {insn[31:12], 12'd0};
            STORE: imm = {{20{insn[31]}}, insn[31:25], insn[11:7]};
            BRANCH: imm = {{19{insn[31]}}, insn[31], insn[7], insn[30:25], insn[11:8], 1'd0};
            JAL: imm = {{11{insn[31]}}, insn[31], insn[19:12], insn[20], insn[30:21], 1'd0};
            default: imm = 32'd0;
        endcase
    end

次に、alu_code[4:0]について考えてみましょう。これは、それぞれの命令毎にALUにどのような演算を実行させるか、という制御信号でした。さきほどと同様にop_code[6:0]を手がかりにしつつ、それに加えて、funct3[2:0]といった信号を定義してこれを用いて分類すると見通しが良いです。RV32I仕様を参考にしてみると良いでしょう。

演習: 命令デコーダのALU制御信号算出部分の設計

以下のコードを追記修正して、命令デコーダのalu_code[4:0]算出部分を設計しましょう。

logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

always_comb begin
    unique case (op_code)
        OPREG: begin
            unique case (funct3)
                3'b000: alu_code = (insn[30] == 1'b1) ? ALU_SUB : ALU_ADD;
            endcase
        end
        OPIMM: begin
            unique case (funct3)
                3'b000: alu_code = ALU_ADD;
            endcase
        end
        LUI: alu_code = ALU_LUI;
        AUIPC: alu_code = ALU_ADD;
        BRANCH: begin
            unique case (funct3)
            endcase
        end
        JAL: alu_code = ALU_JAL;
        JALR: alu_code = ALU_JALR;
        default: alu_code = ALU_ADD;
    endcase
end
なお、以下のようなパラメータを利用すると良いです。
// op_code
typedef logic [6:0] op_code_t;

parameter op_code_t OPIMM  = 7'b0010011;
parameter op_code_t OPREG  = 7'b0110011;
parameter op_code_t LUI    = 7'b0110111;
parameter op_code_t AUIPC  = 7'b0010111;
parameter op_code_t LOAD   = 7'b0000011;
parameter op_code_t STORE  = 7'b0100011;
parameter op_code_t BRANCH = 7'b1100011;
parameter op_code_t JAL    = 7'b1101111;
parameter op_code_t JALR   = 7'b1100111;

// alu_code
typedef logic [4:0] alu_code_t;

parameter alu_code_t ALU_ADD  = 5'd0;
parameter alu_code_t ALU_SUB  = 5'd1;
parameter alu_code_t ALU_XOR  = 5'd2;
parameter alu_code_t ALU_OR   = 5'd3;
parameter alu_code_t ALU_AND  = 5'd4;
parameter alu_code_t ALU_SLT  = 5'd5;
parameter alu_code_t ALU_SLTU = 5'd6;
parameter alu_code_t ALU_SLL  = 5'd7;
parameter alu_code_t ALU_SRL  = 5'd8;
parameter alu_code_t ALU_SRA  = 5'd9;
parameter alu_code_t ALU_LUI  = 5'd10;
parameter alu_code_t ALU_BEQ  = 5'd11;
parameter alu_code_t ALU_BNE  = 5'd12;
parameter alu_code_t ALU_BLT  = 5'd13;
parameter alu_code_t ALU_BGE  = 5'd14;
parameter alu_code_t ALU_BLTU = 5'd15;
parameter alu_code_t ALU_BGEU = 5'd16;
parameter alu_code_t ALU_JAL  = 5'd17;
parameter alu_code_t ALU_JALR = 5'd18;

こたえ
logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

always_comb begin
    unique case (op_code)
        OPREG: begin
            unique case (funct3)
                3'b000: alu_code = (insn[30] == 1'b1) ? ALU_SUB : ALU_ADD;
                3'b001: alu_code = ALU_SLL;
                3'b010: alu_code = ALU_SLT;
                3'b011: alu_code = ALU_SLTU;
                3'b100: alu_code = ALU_XOR;
                3'b101: alu_code = (insn[30] == 1'b1) ? ALU_SRA : ALU_SRL;
                3'b110: alu_code = ALU_OR;
                3'b111: alu_code = ALU_AND;
                default: alu_code = ALU_ADD;
            endcase
        end
        OPIMM: begin
            unique case (funct3)
                3'b000: alu_code = ALU_ADD;
                3'b001: alu_code = ALU_SLL;
                3'b010: alu_code = ALU_SLT;
                3'b011: alu_code = ALU_SLTU;
                3'b100: alu_code = ALU_XOR;
                3'b101: alu_code = (insn[30] == 1'b1) ? ALU_SRA : ALU_SRL;
                3'b110: alu_code = ALU_OR;
                3'b111: alu_code = ALU_AND;
                default: alu_code = ALU_ADD;
            endcase
        end
        LUI: alu_code = ALU_LUI;
        AUIPC: alu_code = ALU_ADD;
        BRANCH: begin
            unique case (funct3)
                3'b000: alu_code = ALU_BEQ;
                3'b001: alu_code = ALU_BNE;
                3'b100: alu_code = ALU_BLT;
                3'b101: alu_code = ALU_BGE;
                3'b110: alu_code = ALU_BLTU;
                3'b111: alu_code = ALU_BGEU;
                default: alu_code = ALU_ADD;
            endcase
        end
        JAL: alu_code = ALU_JAL;
        JALR: alu_code = ALU_JALR;
        default: alu_code = ALU_ADD;
    endcase
end
ALUへの入力信号選択部分。

続いて、alu_op1_selalu_op2_selの生成部についても記述していきましょう。これらはALUでの演算のためにrs1rs2immpcといった値のどれを入力するのか決めるための信号でした。

ここでは、

  • alu_op1_sel == 1'b0 のとき: rs1
  • alu_op1_sel == 1'b1 のとき: pc
  • alu_op2_sel == 1'b0 のとき: rs2
  • alu_op2_sel == 1'b1 のとき: imm

としてみましょう。RV32I仕様で各種命令での演算に用いられる値や、先ほどのALUの仕様を思い出しながら記述してみましょう。

演習: 命令デコーダのALU入力選択信号算出部分の設計

以下のコードを追記修正して、命令デコーダのalu_op1_selalu_op2_sel算出部分を設計しましょう。

logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

always_comb begin
    unique case (op_code)
        : begin
            alu_op1_sel = ;
            alu_op2_sel = ;
        end
        : begin
            alu_op1_sel = ALU_OP1_RS1;
            alu_op2_sel = ALU_OP2_RS2;
        end
        AUIPC, JAL, JALR: begin
            alu_op1_sel = ;
            alu_op2_sel = ;
        end
        default: begin
            alu_op1_sel = ALU_OP1_RS1;
            alu_op2_sel = ALU_OP2_RS2;
        end
    endcase
end
なお、以下のようなパラメータを利用すると良いです。
// op_code
typedef logic [6:0] op_code_t;
parameter op_code_t OPIMM  = 7'b0010011;
parameter op_code_t OPREG  = 7'b0110011;
parameter op_code_t LUI    = 7'b0110111;
parameter op_code_t AUIPC  = 7'b0010111;
parameter op_code_t LOAD   = 7'b0000011;
parameter op_code_t STORE  = 7'b0100011;
parameter op_code_t BRANCH = 7'b1100011;
parameter op_code_t JAL    = 7'b1101111;
parameter op_code_t JALR   = 7'b1100111;

// alu_code
typedef logic [4:0] alu_code_t;
parameter alu_code_t ALU_ADD  = 5'd0;
parameter alu_code_t ALU_SUB  = 5'd1;
parameter alu_code_t ALU_XOR  = 5'd2;
parameter alu_code_t ALU_OR   = 5'd3;
parameter alu_code_t ALU_AND  = 5'd4;
parameter alu_code_t ALU_SLT  = 5'd5;
parameter alu_code_t ALU_SLTU = 5'd6;
parameter alu_code_t ALU_SLL  = 5'd7;
parameter alu_code_t ALU_SRL  = 5'd8;
parameter alu_code_t ALU_SRA  = 5'd9;
parameter alu_code_t ALU_LUI  = 5'd10;
parameter alu_code_t ALU_BEQ  = 5'd11;
parameter alu_code_t ALU_BNE  = 5'd12;
parameter alu_code_t ALU_BLT  = 5'd13;
parameter alu_code_t ALU_BGE  = 5'd14;
parameter alu_code_t ALU_BLTU = 5'd15;
parameter alu_code_t ALU_BGEU = 5'd16;
parameter alu_code_t ALU_JAL  = 5'd17;
parameter alu_code_t ALU_JALR = 5'd18;

// alu_op1
typedef logic [0:0] alu_op1_t;
parameter alu_op1_t ALU_OP1_RS1 = 1'b0;
parameter alu_op1_t ALU_OP1_PC  = 1'b1;

// alu_op2
typedef logic [0:0] alu_op2_t;
parameter alu_op1_t ALU_OP2_RS2 = 1'b0;
parameter alu_op1_t ALU_OP2_IMM = 1'b1;

こたえ
logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

always_comb begin
    unique case (op_code)
        OPIMM, LUI, LOAD, STORE, JALR: begin
            alu_op1_sel = ALU_OP1_RS1;
            alu_op2_sel = ALU_OP2_IMM;
        end
        OPREG, BRANCH: begin
            alu_op1_sel = ALU_OP1_RS1;
            alu_op2_sel = ALU_OP2_RS2;
        end
        AUIPC, JAL, JALR: begin
            alu_op1_sel = ALU_OP1_PC;
            alu_op2_sel = ALU_OP2_IMM;
        end
        default: begin
            alu_op1_sel = ALU_OP1_RS1;
            alu_op2_sel = ALU_OP2_RS2;
        end
    endcase
end

残りの出力信号として、reg_weis_loadis_storeが存在します。こういった信号についても記述してみます。reg_weはデスティネーションレジスタに値を書き込むかどうか決定する信号です。デスティネーションレジスタの存在する命令についてはこれを用いて書き込むことを示したいです。is_loadis_storeはそれぞれロード命令、ストア命令によるメモリ操作の有無や単位 (8bit、16bit、32bit) を示します。この信号に応じてデータメモリの読み書きが実施されます。

ここでは、

  • reg_we == 1'b0 のとき: デスティネーションレジスタへの書き込み無し
  • reg_we == 1'b1 のとき: デスティネーションレジスタへの書き込み有り

  • is_load == 3'b000 のとき: ロード無し

  • is_load == 3'b001 のとき: ロードバイト (8bit)、読み込んだデータは符号拡張
  • is_load == 3'b010 のとき: ロードハーフ (16bit)、読み込んだデータは符号拡張
  • is_load == 3'b011 のとき: ロードワード (32bit)、読み込んだデータは符号拡張
  • is_load == 3'b101 のとき: ロードバイト (8bit)、読み込んだデータはゼロ拡張
  • is_load == 3'b110 のとき: ロードハーフ (16bit)、読み込んだデータはゼロ拡張

  • is_store == 2'b00 のとき: ストア無し

  • is_store == 2'b01 のとき: ストアバイト (8bit)
  • is_store == 2'b10 のとき: ストアハーフ (16bit)
  • is_store == 2'b11 のとき: ストアワード (32bit)

となるように記述してみましょう。やはり、RV32I仕様が参考になると思います。

演習: 命令デコーダの各種フラグ信号算出部分の設計

以下のコードを追記修正して、命令デコーダのreg_weis_loadis_store算出部分を設計しましょう。

logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

// reg_we
always_comb begin
    unique case (op_code)
        OPIMM, : reg_we = ENABLE;
        default: reg_we = DISABLE;
    endcase
end

// is_load
always_comb begin
    unique case (op_code)
        LOAD: begin
            unique case (funct3)
                default: is_load = LOAD_DISABLE;
            endcase
        end
        default: is_load = LOAD_DISABLE;
    endcase
end

// is_store
always_comb begin
    unique case (op_code)
        STORE: begin
            unique case (funct3)
                3'b000: is_store = ;
                default: is_store = STORE_DISABLE;
            endcase
        end
        default: is_store = STORE_DISABLE;
    endcase
end
なお、以下のようなパラメータを利用すると良いです。
// control signals
parameter logic ENABLE = 1'b1;
parameter logic DISABLE = 1'b0;

// op_code
typedef logic [6:0] op_code_t;
parameter op_code_t OPIMM  = 7'b0010011;
parameter op_code_t OPREG  = 7'b0110011;
parameter op_code_t LUI    = 7'b0110111;
parameter op_code_t AUIPC  = 7'b0010111;
parameter op_code_t LOAD   = 7'b0000011;
parameter op_code_t STORE  = 7'b0100011;
parameter op_code_t BRANCH = 7'b1100011;
parameter op_code_t JAL    = 7'b1101111;
parameter op_code_t JALR   = 7'b1100111;

// alu_code
typedef logic [4:0] alu_code_t;
parameter alu_code_t ALU_ADD  = 5'd0;
parameter alu_code_t ALU_SUB  = 5'd1;
parameter alu_code_t ALU_XOR  = 5'd2;
parameter alu_code_t ALU_OR   = 5'd3;
parameter alu_code_t ALU_AND  = 5'd4;
parameter alu_code_t ALU_SLT  = 5'd5;
parameter alu_code_t ALU_SLTU = 5'd6;
parameter alu_code_t ALU_SLL  = 5'd7;
parameter alu_code_t ALU_SRL  = 5'd8;
parameter alu_code_t ALU_SRA  = 5'd9;
parameter alu_code_t ALU_LUI  = 5'd10;
parameter alu_code_t ALU_BEQ  = 5'd11;
parameter alu_code_t ALU_BNE  = 5'd12;
parameter alu_code_t ALU_BLT  = 5'd13;
parameter alu_code_t ALU_BGE  = 5'd14;
parameter alu_code_t ALU_BLTU = 5'd15;
parameter alu_code_t ALU_BGEU = 5'd16;
parameter alu_code_t ALU_JAL  = 5'd17;
parameter alu_code_t ALU_JALR = 5'd18;

// alu_op1
typedef logic [0:0] alu_op1_t;
parameter alu_op1_t ALU_OP1_RS1 = 1'b0;
parameter alu_op1_t ALU_OP1_PC  = 1'b1;

// alu_op2
typedef logic [0:0] alu_op2_t;
parameter alu_op1_t ALU_OP2_RS2 = 1'b0;
parameter alu_op1_t ALU_OP2_IMM = 1'b1;

// load
typedef logic [2:0] load_type_t;
parameter load_type_t LOAD_DISABLE = 3'b000;
parameter load_type_t LOAD_LB      = 3'b001;
parameter load_type_t LOAD_LH      = 3'b010;
parameter load_type_t LOAD_LW      = 3'b011;
parameter load_type_t LOAD_LBU     = 3'b101;
parameter load_type_t LOAD_LHU     = 3'b110;

// store
typedef logic [1:0] store_type_t;
parameter store_type_t STORE_DISABLE = 2'b00;
parameter store_type_t STORE_SB      = 2'b01;
parameter store_type_t STORE_SH      = 2'b10;
parameter store_type_t STORE_SW      = 2'b11;

こたえ
logic [6:0] op_code;
logic [2:0] funct3;

assign op_code = insn[6:0];
assign funct3 = insn[14:12];

// reg_we
always_comb begin
    unique case (op_code)
        OPIMM, OPREG, LUI, AUIPC, LOAD, JAL, JALR: reg_we = ENABLE;
        default: reg_we = DISABLE;
    endcase
end

// is_load
always_comb begin
    unique case (op_code)
        LOAD: begin
            unique case (funct3)
                3'b000: is_load = LOAD_LB;
                3'b001: is_load = LOAD_LH;
                3'b010: is_load = LOAD_LW;
                3'b100: is_load = LOAD_LBU;
                3'b101: is_load = LOAD_LHU;
                default: is_load = LOAD_DISABLE;
            endcase
        end
        default: is_load = LOAD_DISABLE;
    endcase
end

// is_store
always_comb begin
    unique case (op_code)
        STORE: begin
            unique case (funct3)
                3'b000: is_store = STORE_SB;
                3'b001: is_store = STORE_SH;
                3'b010: is_store = STORE_SW;
                default: is_store = STORE_DISABLE;
            endcase
        end
        default: is_store = STORE_DISABLE;
    endcase
end

最後に、デコーダ全体の検証を実施しましょう。完成したデコーダにRV32Iの命令列を入力して、所望の結果が得られるかテストベンチで検証しましょう。命令列については、RISC-Vアセンブラを利用して生成すると簡単です。

演習: 命令デコーダの検証

以下の命令デコーダとテストベンチを用いて、いくつかの命令について検証してみましょう。

decoder.sv
module decoder (
    input logic [31:0] insn,
    output logic [31:0] imm,
    output logic [4:0] alu_code,
    output logic alu_op1_sel,
    output logic alu_op2_sel,
    output logic reg_we,
    output logic [2:0] is_load,
    output logic [1:0] is_store
);

    import cpu_pkg::*;

    logic [6:0] op_code;
    logic [2:0] funct3;

    assign op_code = insn[6:0];
    assign funct3 = insn[14:12];

    // imm
    always_comb begin
        unique case (op_code)
            OPIMM, LOAD, JALR: imm = {{20{insn[31]}}, insn[31:20]};
            LUI, AUIPC: imm = {insn[31:12], 12'd0};
            STORE: imm = {{20{insn[31]}}, insn[31:25], insn[11:7]};
            BRANCH: imm = {{19{insn[31]}}, insn[31], insn[7], insn[30:25], insn[11:8], 1'd0};
            JAL: imm = {{11{insn[31]}}, insn[31], insn[19:12], insn[20], insn[30:21], 1'd0};
            default: imm = 32'd0;
        endcase
    end

    // alu_code
    always_comb begin
        unique case (op_code)
            OPREG: begin
                unique case (funct3)
                    3'b000: alu_code = (insn[30] == 1'b1) ? ALU_SUB : ALU_ADD;
                    3'b001: alu_code = ALU_SLL;
                    3'b010: alu_code = ALU_SLT;
                    3'b011: alu_code = ALU_SLTU;
                    3'b100: alu_code = ALU_XOR;
                    3'b101: alu_code = (insn[30] == 1'b1) ? ALU_SRA : ALU_SRL;
                    3'b110: alu_code = ALU_OR;
                    3'b111: alu_code = ALU_AND;
                    default: alu_code = ALU_ADD;
                endcase
            end
            OPIMM: begin
                unique case (funct3)
                    3'b000: alu_code = ALU_ADD;
                    3'b001: alu_code = ALU_SLL;
                    3'b010: alu_code = ALU_SLT;
                    3'b011: alu_code = ALU_SLTU;
                    3'b100: alu_code = ALU_XOR;
                    3'b101: alu_code = (insn[30] == 1'b1) ? ALU_SRA : ALU_SRL;
                    3'b110: alu_code = ALU_OR;
                    3'b111: alu_code = ALU_AND;
                    default: alu_code = ALU_ADD;
                endcase
            end
            LUI: alu_code = ALU_LUI;
            AUIPC: alu_code = ALU_ADD;
            BRANCH: begin
                unique case (funct3)
                    3'b000: alu_code = ALU_BEQ;
                    3'b001: alu_code = ALU_BNE;
                    3'b100: alu_code = ALU_BLT;
                    3'b101: alu_code = ALU_BGE;
                    3'b110: alu_code = ALU_BLTU;
                    3'b111: alu_code = ALU_BGEU;
                    default: alu_code = ALU_ADD;
                endcase
            end
            JAL: alu_code = ALU_JAL;
            JALR: alu_code = ALU_JALR;
            default: alu_code = ALU_ADD;
        endcase
    end

    // alu_op_sel
    always_comb begin
        unique case (op_code)
            OPIMM, LUI, LOAD, STORE, JALR: begin
                alu_op1_sel = ALU_OP1_RS1;
                alu_op2_sel = ALU_OP2_IMM;
            end
            OPREG, BRANCH: begin
                alu_op1_sel = ALU_OP1_RS1;
                alu_op2_sel = ALU_OP2_RS2;
            end
            AUIPC, JAL, JALR: begin
                alu_op1_sel = ALU_OP1_PC;
                alu_op2_sel = ALU_OP2_IMM;
            end
            default: begin
                alu_op1_sel = ALU_OP1_RS1;
                alu_op2_sel = ALU_OP2_RS2;
            end
        endcase
    end

    // reg_we
    always_comb begin
        unique case (op_code)
            OPIMM, OPREG, LUI, AUIPC, LOAD, JAL, JALR: reg_we = ENABLE;
            default: reg_we = DISABLE;
        endcase
    end

    // is_load
    always_comb begin
        unique case (op_code)
            LOAD: begin
                unique case (funct3)
                    3'b000: is_load = LOAD_LB;
                    3'b001: is_load = LOAD_LH;
                    3'b010: is_load = LOAD_LW;
                    3'b100: is_load = LOAD_LBU;
                    3'b101: is_load = LOAD_LHU;
                    default: is_load = LOAD_DISABLE;
                endcase
            end
            default: is_load = LOAD_DISABLE;
        endcase
    end

    // is_store
    always_comb begin
        unique case (op_code)
            STORE: begin
                unique case (funct3)
                    3'b000: is_store = STORE_SB;
                    3'b001: is_store = STORE_SH;
                    3'b010: is_store = STORE_SW;
                    default: is_store = STORE_DISABLE;
                endcase
            end
            default: is_store = STORE_DISABLE;
        endcase
    end

endmodule

cpu_pkg.sv
package cpu_pkg;

    // control signals
    parameter logic ENABLE = 1'b1;
    parameter logic DISABLE = 1'b0;

    // op_code
    typedef logic [6:0] op_code_t;
    parameter op_code_t OPIMM  = 7'b0010011;
    parameter op_code_t OPREG  = 7'b0110011;
    parameter op_code_t LUI    = 7'b0110111;
    parameter op_code_t AUIPC  = 7'b0010111;
    parameter op_code_t LOAD   = 7'b0000011;
    parameter op_code_t STORE  = 7'b0100011;
    parameter op_code_t BRANCH = 7'b1100011;
    parameter op_code_t JAL    = 7'b1101111;
    parameter op_code_t JALR   = 7'b1100111;

    // alu_code
    typedef logic [4:0] alu_code_t;
    parameter alu_code_t ALU_ADD  = 5'd0;
    parameter alu_code_t ALU_SUB  = 5'd1;
    parameter alu_code_t ALU_XOR  = 5'd2;
    parameter alu_code_t ALU_OR   = 5'd3;
    parameter alu_code_t ALU_AND  = 5'd4;
    parameter alu_code_t ALU_SLT  = 5'd5;
    parameter alu_code_t ALU_SLTU = 5'd6;
    parameter alu_code_t ALU_SLL  = 5'd7;
    parameter alu_code_t ALU_SRL  = 5'd8;
    parameter alu_code_t ALU_SRA  = 5'd9;
    parameter alu_code_t ALU_LUI  = 5'd10;
    parameter alu_code_t ALU_BEQ  = 5'd11;
    parameter alu_code_t ALU_BNE  = 5'd12;
    parameter alu_code_t ALU_BLT  = 5'd13;
    parameter alu_code_t ALU_BGE  = 5'd14;
    parameter alu_code_t ALU_BLTU = 5'd15;
    parameter alu_code_t ALU_BGEU = 5'd16;
    parameter alu_code_t ALU_JAL  = 5'd17;
    parameter alu_code_t ALU_JALR = 5'd18;

    // alu_op1
    typedef logic [0:0] alu_op1_t;
    parameter alu_op1_t ALU_OP1_RS1 = 1'b0;
    parameter alu_op1_t ALU_OP1_PC  = 1'b1;

    // alu_op2
    typedef logic [0:0] alu_op2_t;
    parameter alu_op1_t ALU_OP2_RS2 = 1'b0;
    parameter alu_op1_t ALU_OP2_IMM = 1'b1;

    // load
    typedef logic [2:0] load_type_t;
    parameter load_type_t LOAD_DISABLE = 3'b000;
    parameter load_type_t LOAD_LB      = 3'b001;
    parameter load_type_t LOAD_LH      = 3'b010;
    parameter load_type_t LOAD_LW      = 3'b011;
    parameter load_type_t LOAD_LBU     = 3'b101;
    parameter load_type_t LOAD_LHU     = 3'b110;

    // store
    typedef logic [1:0] store_type_t;
    parameter store_type_t STORE_DISABLE = 2'b00;
    parameter store_type_t STORE_SB      = 2'b01;
    parameter store_type_t STORE_SH      = 2'b10;
    parameter store_type_t STORE_SW      = 2'b11;

endpackage
decoder_tb.sv
`timescale 1ns/1ps

module decoder_tb;

    logic [31:0] insn;
    logic [31:0] imm;
    logic [4:0]  alu_code;
    logic alu_op1_sel;
    logic alu_op2_sel;
    logic        reg_we;
    logic [2:0]  is_load;
    logic [1:0]  is_store;

    decoder dut (
        .insn(insn),
        .imm(imm),
        .alu_code(alu_code),
        .alu_op1_sel(alu_op1_sel),
        .alu_op2_sel(alu_op2_sel),
        .reg_we(reg_we),
        .is_load(is_load),
        .is_store(is_store)
    );

    task print_outputs();
        $display("insn        = 0x%08h", insn);
        $display("imm         = 0x%08h", imm);
        $display("alu_code    = %0d", alu_code);
        $display("alu_op1_sel = %0d", alu_op1_sel);
        $display("alu_op2_sel = %0d", alu_op2_sel);
        $display("reg_we      = %b", reg_we);
        $display("is_load     = %0d", is_load);
        $display("is_store    = %0d", is_store);
        $display("");
    endtask

    initial begin

        insn = 32'h00a10113;
        #1; print_outputs();

        $finish;
    end

endmodule

命令デコーダの記述スタイル

今回の命令デコーダは各出力信号のそれぞれについて組み合わせ回路のブロックを形成し、その内部で各命令毎の条件分岐をおこなっていました。一方で、命令それぞれについて組み合わせ回路のブロックを形成し、その中で各出力信号の値を設定するという方針もあり得ます。RISC-Vはモジュラ型のISAなので、命令を拡張する際には後者の方が記述しやすい点も多いでしょう。RISC-Vに基づくプロセッサのHDL記述はオープンなものが数多く存在するので、それぞれの記述を見比べてみるのも面白いです。たとえば、cve2cva6があります。

レジスタファイルの設計

レジスタファイル。

レジスタファイルは32bit幅の32個のレジスタから成り、そのうち一つはゼロレジスタで常に0を保持するのでした。そのため、実装上は31個の書き込み可能なレジスタを用意します。

rs1rs2に応じたデータを取り出す必要があるので、それぞれのレジスタは2ポートの読み出し回路を持ちます。また、rdに応じたデータの書き込みを実現するために、1ポートの書き込み回路を持ちます。ここで書き込みについてはクロック同期でおこないます。rs1rs2でゼロレジスタが選択された際についても注意しましょう。

演習: レジスタファイルの設計

以下のコードを追記修正して、レジスタファイルを設計しましょう。

module regfile (
    input logic clk,
    input logic reg_we,
    input logic [4:0] rs1,
    input logic [4:0] rs2,
    input logic [4:0] rd,
    input logic [31:0] rd_value,
    output logic [31:0] rs1_value,
    output logic [31:0] rs2_value
);

こたえ
regfile.sv
module regfile (
    input logic clk,
    input logic reg_we,
    input logic [4:0] rs1,
    input logic [4:0] rs2,
    input logic [4:0] rd,
    input logic [31:0] rd_value,
    output logic [31:0] rs1_value,
    output logic [31:0] rs2_value
);

    logic [31:0] regfile [1:31];

    always_ff @(posedge clk) begin
        if (reg_we) regfile[rd] <= rd_value;
    end

    assign rs1_value = (rs1 == 5'd0) ? 32'd0 : regfile[rs1];
    assign rs2_value = (rs2 == 5'd0) ? 32'd0 : regfile[rs2];

endmodule

プログラムカウンタの設計

program_counter.sv
module program_counter (
    input logic clk,
    input logic rst_n,
    input logic [31:0] next_pc,
    output logic [31:0] pc
);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            pc <= 32'd0;
        end else begin
            pc <= next_pc;
        end
    end

endmodule

Design Compilerを用いた論理合成

論理合成の基本

簡易的に表現した論理合成フロー (再掲)。

RTL設計を実際に論理合成してみましょう。論理合成では、ツールを利用してこれを論理ゲートレベル設計へと変換するのでした。ツールとしては、Synopsys Design Compilerを利用します。

論理合成にあたって、論理ゲートの具体的なデータが格納されたスタンダードセルライブラリの情報や、タイミング等の制約が必要になるのでした。より具体的には、

  • .dbファイル: 標準化された形式で、スタンダードセルライブラリ内の各セル (各論理ゲートやフリップフロップなど) の論理的な動作、遅延情報、消費電力、入出力特性 (遷移時間や容量) が記述されたファイル。Libertyファイルと呼ぶ。
  • .tclファイル: .dbファイルのパス指定、タイミング制約の記述、実行する論理合成ツールのコマンド等が含まれるtclユーザスクリプト。

といったものが必要になります。

$ cp /home/resources/syn_alu.tcl ${自分の作業ディレクトリ}

として、論理合成用のスクリプトを自分の作業ディレクトリへとコピーし閲覧してみましょう。冒頭は、今回論理合成に利用するFinFET製造プロセスのスタンダードセル固有の記述です。なお、このスタンダードセルはオープンに公開されているものです (コラム参照のこと)。以降で具体的な記述内容について説明していきます。

syn_alu.tcl
set symbol_library [list "EXD.sdb"]
set target_library [list "${library_file}"]
set link_library   [list ${library_file} ${synthetic_library}]

set symbol_library [list "EXD.sdb"]では、GUIで論理ゲートを表示する際の記号ライブラリを指定しています。

set target_library [list "${library_file}"]では、最終的に利用するスタンダードセルライブラリ (.db) を指定しています。ここではNanGate_15nm_OCL_typical_conditional_ccs.dbが指定されており、15nm FinFET製造プロセスを想定したライブラリを利用する設定になっています。

ファイル名にtypical_conditionalとあるように標準的な条件の性能のライブラリとなっています。他のパターンとして、コーナーケースの性能を見積もるためにそうした設定のライブラリを読み込むこともあります。ccsはComposite Current Sourceのことで、電流源を利用したセルのモデリング方式です。他に、NLDMもよく見られる方式です。こちらはccsと比較して精度が低くファイルサイズも小さくなっています。

set link_library [list ${library_file} ${synthetic_library}]では、target_libraryに加えて、リンクに利用するライブラリとして合成済みIP (DesignWare) ライブラリの指定をおこなっています。これは効率的な合成を実現するために予め用意された回路部品を集めたSynopsys DesignWareというものになっています。

syn_alu.tcl
read_file -autoread -top ${top_module} -format sverilog ${rtl_src}
check_design
elaborate ${top_module}

read_file -autoread -top ${top_module} -format sverilog ${rtl_src}では、RTL設計を読み込んでいます。ファイルの形式としてはSystemVerilogを指定しています。

check_designでは、読み込んだRTL設計が構文的・構造的に正しいかをチェックしています。

elaborate ${top_module}では、RTL設計内の各モジュールをインスタンスとして展開し、top_moduleを最上層とした階層構造を構築します。

syn_alu.tcl
set_driving_cell -lib_cell ${drv_cell_name} -pin ${drv_pin_name} [all_inputs]
set_load [expr 4 * [load_of [get_lib_pins ${lib_name}/${load_cell_name}/${load_pin_name}]]] [all_outputs]

set_driving_cell -lib_cell ${drv_cell_name} -pin ${drv_pin_name} [all_inputs]では、入力ポートを駆動するセルについて設定しています。入力ポートすべてが、指定したドライバセルの指定したピンによって駆動されると仮定してタイミング検証を実行するよう設定しています。

set_load [expr 4 * [load_of [get_lib_pins ${lib_name}/${load_cell_name}/${load_pin_name}]]] [all_outputs]では逆に出力の負荷について設定しています。出力ポートすべてが指定セルの負荷の4倍を駆動すると仮定してタイミング検証を実行するよう設定しています。

syn_alu.tcl
set_max_area 0
set_max_fanout 48 [current_design]

compile_ultra

set_max_area 0では、タイミング制約を満たしたあと、できるだけ面積を小さくする、という最適化方針を示しています。

set_max_fanout 48 [current_design]では、1つの出力信号がドライブできるファンアウトを最大48に制限しています。

最後に、compile_ultraで、論理合成を実行します。

syn_alu.tcl
report_timing -path full_clock -nets -transition_time -capacitance -attributes -sort_by slack > ${top_module}_timing.txt
report_area  > ${top_module}_area.txt
report_qor   > ${top_module}_qor.txt
report_port  > ${top_module}_port.txt
report_power > ${top_module}_power.txt
ここでは論理合成のレポートをタイミングや面積など、項目ごとにテキストファイルとして保存しています。

syn_alu.tcl
write -f verilog -hier -o ${top_module}.vnet

write -f verilog -hier -o ${top_module}.vnetではそうした論理合成の結果として生成されるゲートレベル設計をVerilogの形式で出力しています。

なお、スクリプトはTcl形式ですので、Tclの構文を使って様々な機能を実現することができます。複数の条件を変化させながらの論理合成が必要な場合など利用してみるとよいでしょう。

一通り論理合成フローを見てみたところで、実際に論理合成されるスタンダードセルのレイアウトを見てみましょう。ここではklayoutというレイアウトファイル (.gds) 閲覧ソフトウェアを利用します。

$ /usr/bin/klayout /home/resources/gds/NanGate_15nm_OCL.gds
スタンダードセル (バッファセル) のレイアウト。

各種論理ゲートがサイズの揃ったかたちで整理されています。左のセルリストの上で右クリックし、Show As New Topとすることで各種セルのレイアウトを閲覧することができます。

おさらいしておくと、こうしたセルの論理動作やタイミングをモデリングしたデータが.dbファイルとして整理されており、論理合成では、SystemVerilogによるRTL設計を、この.dbファイルの中に存在するセルを複数組み合わせた論理ゲートレベル設計へと変換していくのでした。

Process design kit (PDK)

今回論理合成にLibertyファイル (.db) を利用するほか、スタンダードセルのレイアウトファイル (.gds) も見てみました。こうしたスタンダードセルライブラリをはじめ、特定の製造プロセスに向けたVLSI設計用データが集積されたものをPDKと呼びます。半導体製造企業が自社の製造プロセスの利用者向けに準備するもののほか、今回論理合成用に利用する15nm FinFETプロセスのように、オープンなPDKも存在します。たとえば、7nm FinFETプロセス, ASAP7といったものが存在します。これらは実際には存在しない製造プロセスを仮定して設計されたPredictiveなものです。他方、130nm CMOSプロセス, SKY130は、オープンかつ、実際に製造可能なプロセスとなっています。こういったPDKを利用して論理合成や配置配線を実施し、RTL設計をVLSI化した際の電力、動作速度、面積といった値を見積もることができます。

ALUの論理合成

実際に、自分で記述してきたRTL設計を論理合成してみましょう。

論理合成スクリプトsyn_alu.tclをコピーしてきていることを確認してください。また、先ほど記述したalu.svcpu_pkg.svについて、

${自分の作業ディレクトリ}/WORK/alu.sv
${自分の作業ディレクトリ}/WORK/cpu_pkg.sv

となるようにWORKというディレクトリを作成して格納してみてください。

さて、それぞれのファイルが揃った状態で、作業ディレクトリで

$ dc_shell-xg-t -f syn_alu.tcl

とすると、論理合成が実行されます。

レジスタファイルの論理合成

クロック信号の存在する順序回路についても論理合成してみましょう。

$ cp /home/resources/syn_regfile.tcl ${自分の作業ディレクトリ}

として、論理合成用スクリプトを自分の作業ディレクトリへとコピーしてきてください。また、先ほど記述したregfile.sv

${自分の作業ディレクトリ}/WORK/regfile.sv

となるようディレクトリへ格納してみてください。

$ dc_shell-xg-t -f syn_regfile.tcl

とすると、論理合成が実行されます。

それぞれのレポートファイルを見てみると色々なことがわかると思います。たとえば、aluはそれなりに複雑な組み合わせ回路で記述も面倒だったと思いますが、最適化されると面積は小さくなっています。一方で記述はシンプルだったものの、regfilealuと比較して非常に大きな面積になっていることがわかると思います。記憶素子としてのDフリップフロップが大きいだけでなく、それが大量に呼び出されているのでこうした結果となっています。

このようにメモリ回路は非常に大きな面積を消費するので、多くのVLSIにおいて事前に設計・レイアウト済みのSRAMブロック (SRAMマクロ) として実装されています。このようなメモリやSRAMについては次回以降また触れることにします。

論理合成結果の面積

論理合成ではRTLをタイミング制約を満たすような論理ゲートの組み合わせに変換するだけで、この時点で実際にVLSI上のレイアウトが定まったわけではありません。論理合成結果として表示される面積は、合成された回路を構成する各スタンダードセルの面積をシンプルに足し合わせたものです。実際には、スタンダードセル間の配線のためにある程度の隙間を空けてセルを配置していくことになり、たとえばこれが典型的には60-70%程度の密度になります。したがって、実際のVLSI上の面積は論理合成結果の1.4-1.7倍の値になります。

次回予告

本日はここまでです。次回は、汎用プロセッサの設計を完了します。また、設計環境の準備が順調であれば配置配線を実施します。興味のある方はCMOS集積回路のレイアウトについて復習してみてください。