コンテンツにスキップ

条件分岐

条件分岐

ここまでで、簡単な計算はできるようになりましたが、処理自体は命令メモリの中身を上から順に実行するだけでした。 そのため、条件分岐や繰り返し文も実現できず、実用的な計算ができません。 これらの問題を解決すべく、分岐命令を導入していきます。

条件分岐は計算結果をもとに、次に実行する命令を切り替えることと言い換えられます。 つまり、計算結果に従ってプログラムカウンタの値を変更できるようにすれば、条件分岐を実現できるでしょう。

分岐命令

これまでに作成したアキュムレータ・マシンで条件分岐を行う(=計算結果に従ってプログラムカウンタの値を変更する)には、アキュムレータの結果に従ってプログラムカウンタの内容を変更する必用があります。

計算結果が0か1かで分岐を判断するシンプルな形式の場合、条件分岐の具体的な挙動は以下のようになるでしょう。 このとき、アキュムレータの結果は更新しません。

  1. アキュムレータの結果が0であれば分岐が成立。次のプログラムカウンタの値は、実行したいアドレス
  2. アキュムレータの結果が1であれば分岐が不成立。次のプログラムカウンタの値は、いつもどおり現在の次のアドレス

この様に、0か1かで分岐を判断し、分岐が成立すれば所定のアドレスを実行する命令をBEZ (Branch on Equal Zero) 命令と一般的に呼びます。 その命令のフォーマット(アセンブラ表記)は以下の様な形で、引数に実行したい命令メモリのアドレスを指定します。

BEZ 0010 //アキュムレータの結果が0であれば、次に0010番地の命令を実行する

分岐の成立条件を逆にして、結果が0以外であれば分岐が成立したとする命令も同時に用意されることがよくあります。 この命令は、BNZ (Branch on Not equal Zero)命令と一般的に呼ばれます。

BNZ 0010 //アキュムレータの結果が1であれば、次に0010番地の命令を実行する

分岐命令はここまでに説明した命令と異なり、プログラムカウンタの値を直接変更し、命令の引数として次に実行したい命令メモリのアドレスを指定するものです。 これまでに説明した命令(加算など)の引数は、データメモリのアドレスであり、命令メモリのアドレスではありませんでした。

他のISAのBEZ/BNZ命令について

RISC-VやARMなど、実際に利用されているCPUの命令セットにもBEQやBNZと行った命令が存在します2。 これらの命令は今回説明したBEZ命令と少し異なり、ある2つのレジスタの値が同値かどうかで処理を分岐させます。 本講義で扱っているアキュムレータ・マシンはもう少し原始的な計算機であるため、このような機能はありません。 RISC-Cや命令セットについては、コンピュータ・アーキテクチャに関する講義で聞いてみてください。

構成概要

分岐命令をアキュムレータ・マシンに組み込むことを考えます。 組み込んだ際に必要な動作は、分岐命令が実行され、なおかつ、アキュムレータの結果によって分岐が成立したときに、指定された命令のアドレスをプログラムカウンタに書き込むというものになります。 また、分岐命令が実行された時は、ALUからの計算結果をシンプルデータ・パス回路のレジスタに保存してはいけないという点も必用です。

具体的な動作は、以下のようになります。

  1. 分岐命令実行 && アキュムレータの結果が0の場合:プログラムカウンタには、実行したい命令のアドレスを書き込む。また、アキュムレータに計算結果を保存しない。
  2. 分岐命令実行 && アキュムレータの結果が1の場合:プログラムカウンタには、現在の値に+1した値を書き込む。また、アキュムレータに計算結果を保存しない。
  3. それ以外の命令の場合:これまで通りの動作。プログラムカウンタには現在の値に+1した値を書き込み、アキュムレータに計算結果を保存。

命令メモリ

条件分岐対応に対応した命令メモリの動作イメージは、マルチプレクサを利用して以下のように表せます。 図中の"BEZ?"は「selector信号がBEZ命令かどうかを判定する」という意味です。

分岐に対応した命令メモリは、以下の図のような回路になります。 命令メモリのモジュールの左上に、上記のマルチプレクサが追加されています。

シンプル・データパス回路

アキュムレータに計算結果を保存しない、という動作はレジスタにwrite enable(ここではBEZ信号)信号が付く形になります。

データメモリ

これらの変更に伴いデータメモリにも変更を加えなければなりません。 具体的には、分岐を行うとレジスタを挟むため、単純に接続したものとタイミングが2サイクルだけずれてしまいます。 これを調整するために、データメモリの出力部分に2サイクル分の遅延を挿入します。 理由は後述します。

alt text

全体像

分岐に対応した各モジュールを接続した全体像は以下の通りです。 接続の関係はこれまでと同様ですが、各モジュールが分岐対応に従い変化しています。 なお、"BEZ?"はselector信号がBEZ命令かどうかを判定するという意味で、実際にはselector(オペレータであり、dataout信号の[12:9]ビット目)です。 それらの接続関係は図が複雑になるため省略しています。

alt text

実装

ここまでで示した分岐命令に関する処理を実装していきます。 分岐命令の動作は、プログラムカウンタとアキュムレータへのデータの保存に関する処理でした。 2つの処理はシンプル・データパス回路と命令メモリのモジュールにあるので、それぞれを分岐命令が必要とする形に変更します。

なお、imem.datdmem.datはソフトウェアに相当しますので、次の節で改めて説明します。

命令メモリ

分岐判定を行うファンクションis_branchを作成し、命令実行(BEZ,BNZ) && アキュムレータの結果を判定します。 その結果をもとに、プログラムカウンタに+1を入れるか、実行したいアドレスを代入するかを決定します。

simple_instruction_memory_w_branch.v 命令メモリの条件分岐対応
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
`include "parameters.vh"

module simple_instruction_memory_w_branch(
    input clk, input rst_n,
    input [`SEL_W-1:0] opcode,
    input [`ADDR_W-1:0] operand,
    input [`DATA_W-1:0] accumulator,
    // output reg [`IMEM_W-1:0] dataout
    output wire [`IMEM_W-1:0] dataout
    );

    reg [`ADDR_W-1:0] program_counter;
    reg [`IMEM_W-1:0] imem [0:`DEPTH-1];

    // read raw data from file (only for simulation)
    initial begin
        $readmemb("imem.dat", imem);
    end

    // 分岐命令実行(BEZ,BNZ) && アキュムレータの結果を判定
    function is_branch;
        input [`SEL_W-1:0] _opcode;
        input [`DATA_W-1:0] _accumulator;
        is_branch = ((_opcode == `OP_BEZ) & (_accumulator == 0)) | ((_opcode == `OP_BNZ) & (_accumulator != 0));
    endfunction

    // プログラムカウンタ
    always @(posedge clk or negedge rst_n)
    begin
       //ここに処理を追加してね
       if(!rst_n) program_counter <= 0;
       // 分岐が成立する時はprogram_counterに実行したい命令のアドレスを書き込む
       // 一つ前の計算結果を基に分岐を判断するため、ikkomaeを利用
       else if (is_branch(dataout[12:9], ikkomae))
            program_counter <= operand;
       else program_counter <= program_counter + 1;
    end

    // 命令メモリの読み出し
    assign dataout = imem[program_counter];

    // タイミング調整用のレジスタの挿入。一つ前の演算結果を保持しておく。
    reg [`DATA_W-1:0] ikkomae;
    always @(posedge clk or negedge rst_n)
    begin
        if(!rst_n) ikkomae <= 0;
        else
            ikkomae <= accumulator;
    end

endmodule

回路合成結果


図:simple_instruction_memory_w_branch.vのDigitalJS Onlineによる合成結果。上半分が分岐判定関数で、その結果が右真ん中のマルチプレクサのセレクタ信号に入っている。分岐が成立した場合は、operandが出力される。上半分は既存のシンプル・データパス回路から変化なし。結果はこちら

シンプル・データパス回路

こちらにも分岐判定を行うファンクションis_branchを作成し、命令実行(BEZ,BNZ) && アキュムレータの結果を判定します。 その結果をもとに、レジスタに値を保持するかどうかを決定します。

simple_datapath_w_branch.v シンプル・データパス回路の条件分岐対応
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
`include "parameters.vh"
module simple_datapath_w_branch(
    input clk,
    input rst_n, // アクティブ・ローなリセット信号
    input [`DATA_W-1:0] input_b,
    input [`SEL_W-1:0] selector,
    output wire [`DATA_W-1:0] result
    );

    reg [`DATA_W-1:0] accumulator; // 計算結果を保持するレジスタ
        wire [`DATA_W-1:0] alu_result; // wireをALUの出力に接続

    alu4 alu4_1(.A(accumulator), // 入力Aに"accumulator"レジスタを接続
                .B(input_b),
                .S(selector),
                .F(alu_result) // ALUの出力に"alu_result"信号線を接続
                );

    // 分岐判定関数:命令実行(BEZ,BNZ)かどうかを判定
    function is_branch;
      input [`SEL_W-1:0] _opcode;
      is_branch = (_opcode != `OP_BEZ) & (_opcode != `OP_BNZ);
    endfunction

    // posedgeごとに、aluの計算結果を"alu_out"レジスタに保持
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) // rst_nがローの時に成立。clkに非同期な非同期リセット
            // レジスタをゼロクリア
            accumulator <= `DATA_W'b0;
                // 分岐命令判定を利用して値を格納するかを判定
        else if(is_branch(selector))
            accumulator <= alu_result;
    end

    // タイミング調整のために、wireを使って遅延なしで結果を出力
    assign result = alu_result;
endmodule

回路合成結果


図:simple_datapath_w_branch.vのDigitalJS Onlineによる合成結果。下半分が分岐判定関数で、AND回路が利用されている。その結果が命令メモリのレジスタのenable信号(en)に接続されている。上半分は既存の命令メモリから変化なし。結果はこちら

データメモリ

タイミングが2サイクルだけずれるのに対処するため、データメモリの出力部分にレジスタを2つ挿入します。

simple_memory_w_branch.v データメモリの条件分岐対応
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
`include "parameters.vh"

module simple_memory(
    input clk, input rst_n,
    input [`DATA_W-1:0] datain,
    input [`ADDR_W-1:0] address,
    input write_enable,
    output wire [`DATA_W-1:0] dataout
    );

    reg [`DATA_W-1:0] dmem [0:`DEPTH-1];
    reg [`DATA_W-1:0] chien1, chien2; // タイミング調整のためにレジスタを追加

    // read raw data from file (only for simulation)
    initial begin
        $readmemh("dmem.dat", dmem);
    end

    // write
    always @(posedge clk)
    begin
        // レジスタを2つ挟んで、2サイクルほど結果をずらす
        chien1 <= datain; 
        chien2 <= chien1;
        if(write_enable) dmem[address] <= chien2;
    end

    assign dataout = dmem[address];

endmodule

回路合成結果

alt text

図:simple_memory_w_branch.vのDigitalJS Onlineによる合成結果。上から2行目あたりにレジスタ(DQフリップフロップ回路)が追加されている。結果はこちら

全体像

上記のモジュールの変更の接続は、これまでと同様に、テストベンチで行ないます。 大きな変更はありませんが、opcodeoperandを伝えるためのwireが追加、利用されています。

なお、この時点で回路は完成しますが、テストベンチでも特に何もしていません。 これは、ソフトウェアであるdmemimemについては次の節で解説し、この時点ではどちらもまだ未完成なためです。

testbench_accumulator_w_branch.v 条件分岐対応したもののテストベンチ
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
/* test bench */
`timescale 1ns/1ps
`include "parameters.vh"

module testbench_accumulator_w_branch;

    parameter STEP = 10;
    reg clk, rst_n;

    wire [`DATA_W-1:0] alu2dmem;
    wire [`DATA_W-1:0] dmem2alu;
    wire [12:0] imem_dataout;

    wire [7:0] imem2dmem_address;
    wire [3:0] imem2alu_selector;
    wire imem2dmem_write_enable;
    wire [3:0] opcode; // opcodeを伝えるためのwire
    wire [8:0] operand; // operandを伝えるためのwire

    // 命令メモリの出力を、それぞれの信号に分解。wireでそのまま流す
    assign imem2dmem_address = imem_dataout[7:0];
    assign imem2dmem_write_enable = imem_dataout[8];
    assign imem2alu_selector = imem_dataout[12:9];
    // wire opcode,operandをそれぞれ接続
    assign opcode = imem2alu_selector;
    assign operand = imem_dataout[8:0];

    simple_datapath_w_branch simple_datapath_1(.clk(clk), .rst_n(rst_n),
                                    .selector(imem2alu_selector),
                                    .input_b(dmem2alu),
                                    .result(alu2dmem)
                                    );

    simple_memory simple_memory_1(.clk(clk), .rst_n(rst_n),
                                    .datain(alu2dmem),
                                    .address(imem2dmem_address),
                                    .write_enable(imem2dmem_write_enable),
                                    .dataout(dmem2alu)
                                    );

    simple_instruction_memory_w_branch simple_instruction_memory_1(.clk(clk), .rst_n(rst_n),
                                    .opcode(opcode),
                                    .operand(operand),
                                    .accumulator(alu2dmem),
                                    .dataout(imem_dataout)
                                    );

    // clockの生成
    always #(STEP/2) begin
             clk <= ~clk;
    end

    // データメモリを全て表示する
    task dump_dmem;
        integer i;
        begin
            $display("---DUMP DATA MEM---");
            for(i = 0; i < `DEPTH; i = i + 1) begin
                $display("%3d, %h", i, simple_memory_1.dmem[i]);
            end
            $display("-------------------");
        end
    endtask

    // 命令メモリを全て表示する
    task dump_imem;
        integer i;
        begin
            $display("---DUMP Instruction MEM---");
            for(i = 0; i < `DEPTH; i = i + 1) begin
                $display("%3d, %b", i, simple_instruction_memory_1.imem[i]);
            end
            $display("-------------------");
        end
    endtask


    initial begin
        $dumpfile("testbench_accumulator_w_branch.vcd");
        $dumpvars(0, testbench_accumulator_w_branch);
        clk <= `DISABLE;
        rst_n <= `ENABLE_N;

        dump_imem();
        dump_dmem();

        #STEP
        rst_n <= `DISABLE_N;
        $display("pc |  sel, we, data addr | alu2dmem, dmem2alu");
        $monitor("%h | %b, %2b, %9b | %8h, %8h",
                            simple_instruction_memory_1.program_counter,
                            imem2alu_selector,
                            imem2dmem_write_enable,
                            imem2dmem_address,
                            alu2dmem,
                            dmem2alu
                            );

        #(STEP*15)
        dump_dmem();
        rst_n <= `ENABLE_N;
        $finish;
    end
endmodule

細かい話

実はこの実装は実用的ではありません。 この実装は、命令メモリから命令がロードされた2サイクル後に演算が行なわれるためです。 そのため、ストア命令を発行すると、2サイクル前の値をストアすることになるので、一発NOP命令を入れないといけません。 どうしてNOP命令を入れなければならないかと言うと、chien1chien2を入れたせいです。 このせいで、計算結果をストアする時はストアする前にNOP命令を入れて、1サイクル分待たなければなりません。

これをかっこよく解決する方法としては、BNZで分岐した際には、その前の計算結果をどこかレジスタに保持しておいて、それをストアするものが考えられます。 しかし、全体に変更が及ぶと考えられるので、今回はこのままにしておきます。

条件分岐の使用例

BNZ命令を使って、簡単な繰り返し処理を考えてみましょう。

ビルド&実行コマンド

ここからのビルドと実行コマンドは全て以下の形となります。 これは、回路に変更はなく、命令メモリとデータメモリの中身だけが変わるためです。 なお、alu4.vparameters.vhは再利用できます。

iverilog testbench_accumulator_w_branch.v ./simple_datapath_w_branch.v ./simple_instruction_memory_w_branch.v ./simple_memory_w_branch.v ./alu4.v && vvp ./a.out

超簡単な例

ロードした値がゼロになるまで-1を行うという超簡単な処理は、以下のデータと命令で実現できます。

  1. 0x0番地の値をデータメモリからロード
  2. その値を-1する
  3. 結果が0でなければ、2に戻る
  4. 結果が0であれば、その結果をデータメモリにストア
imem.dat 命令メモリの中身
1
2
3
4
0010_0_0000_0000 // LD 0
1000_0_0000_0001 // SUB 1
1011_0_0000_0001 // BNZ 1
1001_1_0000_0010 // ST 2 | SUBした結果を保存。2つ前の結果をストアしているので、たまたまNOPはいらない
dmem.dat データメモリの中身
1
2
5
1
テストベンチの出力結果
$ iverilog testbench_accumulator_w_branch.v ./simple_datapath_w_branch.v ./simple_instruction_memory_w_branch.v ./simple_memory_w_branch.v ./alu4.v && ./a.out
WARNING: ./simple_memory_w_branch.v:16: $readmemh(dmem.dat): Not enough words in the file for the   requested range [0:7].
WARNING: ./simple_instruction_memory_w_branch.v:17: $readmemb(imem.dat): Not enough words in the file for   the requested range [0:7].
VCD info: dumpfile testbench_accumulator_w_branch.vcd opened for output.
---DUMP Instruction MEM---
  0, 0010000000000
  1, 1000000000001
  2, 1011000000001
  3, 1001100000010
  4, xxxxxxxxxxxxx
  5, xxxxxxxxxxxxx
  6, xxxxxxxxxxxxx
  7, xxxxxxxxxxxxx
-------------------
---DUMP DATA MEM---
  0, 0005
  1, 0001
  2, xxxx
  3, xxxx
  4, xxxx
  5, xxxx
  6, xxxx
  7, xxxx
-------------------
pc |  sel, we, data addr | alu2dmem, dmem2alu
00 | 0010,  0,  00000000 |     0005,     0005
01 | 1000,  0,  00000001 |     0004,     0001
02 | 1011,  0,  00000001 |     xxxx,     0001
01 | 1000,  0,  00000001 |     0003,     0001
02 | 1011,  0,  00000001 |     xxxx,     0001
01 | 1000,  0,  00000001 |     0002,     0001
02 | 1011,  0,  00000001 |     xxxx,     0001
01 | 1000,  0,  00000001 |     0001,     0001
02 | 1011,  0,  00000001 |     xxxx,     0001
01 | 1000,  0,  00000001 |     0000,     0001
02 | 1011,  0,  00000001 |     xxxx,     0001
03 | 1001,  1,  00000010 |     xxxx,     xxxx
04 | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
05 | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
06 | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
07 | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
---DUMP DATA MEM---
  0, 0005
  1, 0001
  2, 0000
  3, xxxx
  4, xxxx
  5, xxxx
  6, xxxx
  7, xxxx
-------------------
testbench_accumulator_w_branch.v:105: $finish called at 160000 (1ps)
テストベンチの波形

do-whileループ

以下の様なdo-whileループ(必ず一回は処理が実行されるループ)も実現できます。 できますが、データの出し入れが大変なうえにNOP命令が必用になり、けっこう複雑になってしまいます。

do{
    add;
    loop_count--;
} while (loop_count<=0);

NOP命令とは、計算結果を保持するだけ何もしない命令になります。 各信号への値は以下のようになり、OP_THROUGH_Aでデータを回しているだけになります。

selector write enable ニーモニック
OP_THROUGH_A DISABLE NOP
1001 0

以下の形のdo-whileループを実現する命令列を作ってみます。

  • whileの継続条件:規定回だけ処理が行われたか
  • doの処理:ただの足し算
imem.dat 命令メモリの中身
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0010_0_0000_0000 // LD 0 | doの処理はただの足し算。最初に足される値をロード
0111_0_0000_0001 // ADD 1 | 足す数を加算
1001_0_0000_0000 // NOP | ストアの前にNOPで1サイクル待つ
1001_1_0000_0000 // ST 0 | 加算結果をストア
0010_0_0000_0010 // LD 2 | whileの継続条件は、何回まわったかを開始。ループカウントをロード
1000_0_0000_0011 // SUB 3 | ループカウントを更新(-1する)
1001_0_0000_0000 // NOP | ストアの前にNOPで1サイクル待つ
1001_1_0000_0010 // ST 2 | 更新したループカウントをストア
0010_0_0000_0010 // LD 2 | 更新したループカウントをストア
1011_0_0000_0000 // BNZ 0 | ループカウントが0になったかを判定。BNZ命令で、値が0でなかったら0番地へジャンプ
0010_0_0000_1111 // LD 9 | 無意味な値をロード。処理が終わったことを示している
dmem.dat データメモリの中身
1
2
3
4
1 // value to be added and intermediate result
4 // value to add
3 // loop count
1 // value to decrement loop count
テストベンチの出力結果
$ iverilog ./testbench_accumulator_w_branch.v ./simple_datapath_w_branch.v ./simple_instruction_memory_w_branch.v ./simple_memory_w_branch.v ./alu4.v && vvp ./a.out
WARNING: ./simple_memory_w_branch.v:16: $readmemh(dmem.dat): Not enough words in the    file for the requested range [0:15].
WARNING: ./simple_instruction_memory_w_branch.v:17: $readmemb(imem.dat): Not enough     words in the file for the requested range [0:15].
VCD info: dumpfile testbench_accumulator_w_branch.vcd opened for output.
---DUMP Instruction MEM---
  0, 0010000000000
  1, 0111000000001
  2, 1001000000000
  3, 1001100000000
  4, 0010000000010
  5, 1000000000011
  6, 1001000000000
  7, 1001100000010
  8, 0010000000010
  9, 1011000000000
 10, 0010000001111
 11, xxxxxxxxxxxxx
 12, xxxxxxxxxxxxx
 13, xxxxxxxxxxxxx
 14, xxxxxxxxxxxxx
 15, xxxxxxxxxxxxx
-------------------
---DUMP DATA MEM---
  0, 0001
  1, 0004
  2, 0003
  3, 0001
  4, xxxx
  5, xxxx
  6, xxxx
  7, xxxx
  8, xxxx
  9, xxxx
 10, xxxx
 11, xxxx
 12, xxxx
 13, xxxx
 14, xxxx
 15, xxxx
-------------------
pc |  sel, we, data addr | alu2dmem, dmem2alu
00 | 0010,  0,  00000000 |     0001,     0001
01 | 0111,  0,  00000001 |     0005,     0004
02 | 1001,  0,  00000000 |     xxxx,     0001
03 | 1001,  1,  00000000 |     xxxx,     0001
04 | 0010,  0,  00000010 |     0003,     0003
05 | 1000,  0,  00000011 |     0002,     0001
06 | 1001,  0,  00000000 |     xxxx,     0005
07 | 1001,  1,  00000010 |     xxxx,     0003
08 | 0010,  0,  00000010 |     0002,     0002
09 | 1011,  0,  00000000 |     xxxx,     0005
00 | 0010,  0,  00000000 |     0005,     0005
01 | 0111,  0,  00000001 |     0009,     0004
02 | 1001,  0,  00000000 |     xxxx,     0005
03 | 1001,  1,  00000000 |     xxxx,     0005
04 | 0010,  0,  00000010 |     0002,     0002
05 | 1000,  0,  00000011 |     0001,     0001
06 | 1001,  0,  00000000 |     xxxx,     0009
07 | 1001,  1,  00000010 |     xxxx,     0002
08 | 0010,  0,  00000010 |     0001,     0001
09 | 1011,  0,  00000000 |     xxxx,     0009
00 | 0010,  0,  00000000 |     0009,     0009
01 | 0111,  0,  00000001 |     000d,     0004
02 | 1001,  0,  00000000 |     xxxx,     0009
03 | 1001,  1,  00000000 |     xxxx,     0009
04 | 0010,  0,  00000010 |     0001,     0001
05 | 1000,  0,  00000011 |     0000,     0001
06 | 1001,  0,  00000000 |     xxxx,     000d
07 | 1001,  1,  00000010 |     xxxx,     0001
08 | 0010,  0,  00000010 |     0000,     0000
09 | 1011,  0,  00000000 |     xxxx,     000d
0a | 0010,  0,  00001111 |     xxxx,     xxxx
0b | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
0c | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
0d | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
0e | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
0f | xxxx,  x,  xxxxxxxx |     xxxx,     xxxx
---DUMP DATA MEM---
  0, 000d
  1, 0004
  2, 0000
  3, 0001
  4, xxxx
  5, xxxx
  6, xxxx
  7, xxxx
  8, xxxx
  9, xxxx
 10, xxxx
 11, xxxx
 12, xxxx
 13, xxxx
 14, xxxx
 15, xxxx
-------------------
./testbench_accumulator_w_branch.v:125: $finish called at 360000 (1ps)
テストベンチの波形

まとめ

ここでわかることとして、分岐を入れたとしても、アキュムレータマシンでちょっと込み入ったことをしようとすると、プログラムが突然難しくなることです。

原因は以下の通りです。

  1. ALUに入れられるものが1つしかないため、いちいち値を出し入れしないといけない。これがデカい
  2. 計算結果のストアの前に1サイクル待たなければいけない
  3. 分岐命令に使う値は直接比較できない

これではどうにもならないので、解決策としてレジスタを複数持った計算機が登場し、命令セットなどの計算機アーキテクチャの話題に発展していきます。

演習

以下のfor文を実現するimem.datを作成してください。 なお、for文はイテレーションが実行されない場合にも動作するようにしてください。

for(i = 0; i <= 5; i++)
    n = n + 10;
for(i = 0; i <= 0; i++)
    n = n + 10;

参考文献など


  1. DigitalJSでうまく可視化するために、ここで示したコードそのままではありません。いろいろいじっています。 

  2. The RISC-V Instruction Set Manual