コンテンツにスキップ

アキュムレータ・マシンとデータパス

ここからは、簡単な計算器を作成してみます。

ALU (Arithmetic Logic Unit)

教科書 P.52 に示されている ALU (Arithmetic Logic Unit) は、セレクタSを変えてさまざまな計算が行えます。 教科書の ALU は、信号S (Selector)で 8 種類の演算の 1 つを指定します。 また、この ALU にほかの演算を追加することで、ほかの演算を行うこともできます。なお、この ALU は単なる組み合わせ回路ですので、クロックの概念はありません。

以下に教科書の ALU を少し改変したものを示します。

alu4.v 4 ビット ALU (教科書 P.53 リスト 2.16 を改変したもの)
 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
/* -----
* 4-bit ALU(Arithmetic Logic Unit)
* (alu4.v) designed Based on Shinya KIMURA
* ---- */

`include "parameters.vh"

module alu4(S, A, B, F);
    input [`DATA_W-1:0] A, B;
    input [`SEL_W-1:0] S;
    output [`DATA_W-1:0] F;

    function [`DATA_W-1:0] operation;
        input [`SEL_W-1:0] s;
        input [`DATA_W-1:0] a, b;

        begin
            case(s)
            `OP_THROUGH_A : operation = a;
            `OP_THROUGH_B : operation = b;
            `OP_AND       : operation = a & b;
            `OP_OR        : operation = a | b;
            `OP_L_SHIFT   : operation = a << 1;
            `OP_R_SHIFT   : operation = a >> 1;
            `OP_ADD       : operation = a + b;
            `OP_SUB       : operation = a - b;
            endcase
        end
    endfunction

    assign F = operation(S, A, B);

endmodule
parameters.vh パラメータをまとめたファイル(このあとも使う)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
`define DATA_W 16  // bit width
`define SEL_W 4 //selector width
`define OP_NOT_USED  `SEL_W'b0000
`define OP_THROUGH_A `SEL_W'b0001
`define OP_THROUGH_B `SEL_W'b0010
`define OP_AND       `SEL_W'b0011
`define OP_OR        `SEL_W'b0100
`define OP_L_SHIFT   `SEL_W'b0101
`define OP_R_SHIFT   `SEL_W'b0110
`define OP_ADD       `SEL_W'b0111
`define OP_SUB       `SEL_W'b1000
`define OP_ST        `SEL_W'b1001
`define OP_BEZ       `SEL_W'b1010
`define OP_BNZ       `SEL_W'b1011

`define ENABLE 1'b1
`define DISABLE 1'b0
`define ENABLE_N 1'b0
`define DISABLE_N 1'b1

`define ADDR_W 8
`define DEPTH 16
`define IMEM_W 13
testbench.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
`include "parameters.vh"

`define PERIOD 100

module test;
    reg [`DATA_W-1:0] input_a, input_b;
    reg [`SEL_W-1:0] selector;
    wire [`DATA_W-1:0] result;

    alu4 alu4_1(.A(input_a), .B(input_b), .S(selector), .F(result));

    initial begin
        $dumpfile("alu4_1.vcd");
        $dumpvars(0, alu4_1);

        input_a <= `DATA_W'h9999;
        input_b <= `DATA_W'h1111;
        #`PERIOD
        $display("A:%h B:%h", input_a, input_b);

        selector <= `OP_SUB;
        #`PERIOD
        $display("S:%h F:%h", selector, result);

        selector <= `OP_ADD;
        #`PERIOD
        $display("S:%h F:%h", selector, result);

        selector <= `OP_OR;
        #`PERIOD
        $display("S:%b F:%b", selector, result);

        selector <= `OP_AND;
        #`PERIOD
        $display("S:%b F:%b", selector, result);

        $finish;
    end
endmodule

ビルド & 実行コマンド

iverilog -o alu4_1.out ./alu4.v ./testbench.v 
vvp ./alu4_1.out
gtkwave alu4_1.vcd
回路合成結果


図:alu4.vのDigitalJS Onlineによる合成結果。各演算子に対応する計算回路が作成されて、その結果がマルチプレクサで選択されて出力されている。結果はこちら

ALU とテストベンチのブロック図は、こんな感じになります。

この ALU では、2 つの入力の計算ができます。細かく言うと、以下の式(1)のような 2 つの入力に対して 1 回しか計算ができません。また、ALU が 1 つ存在するだけでは式(2)のような計算はできません。

F=A+BF=X+YW+Z \begin{align} F &= A + B\\ F &= X + Y - W + Z \end{align}

1 つの ALU で複数の入力の計算を行うにはどうすれば良いでしょうか?たくさんの ALU を使う方法や、レジスタを複数利用する方法などが考えられますが、本講義では、1 つの ALU を使いまわして上記の計算を行う方法を考えてみます。

ALUを使い回す

ここからは、1 つの ALU を使いまわして上記の計算を行う方法を考えてみます。 1 つの ALU を使い回すためには、レジスタに計算結果を保持すると良さそうです。

以下のような簡単な回路が考えられます。 これをシンプル・データパス回路と呼ぶことにします。 シンプル・データパス回路では、ALU の演算結果をレジスタに保持し、それを ALU の入力Aに接続しています。 このレジスタには計算の途中の結果が次々に積み重なっていく様に見えるため、アキュムレータ (Accumulator) と呼ばれています。

シンプル・データパス回路の全体構成

input_bに加算したい値を順に入れて、selectorで行いたい演算を順に指定し、途中結果をAccumulatorに保持しながら、計算を進めていきます。 このように、レジスタと ALU を組み合わせて計算を行える様にした回路は計算機の基本的な要素であり、これをデータパスと呼びます。

シンプル・データパス回路には、レジスタの部分にクロックが入力されているため、同期式順序回路と言えます。クロックに同期して ALU の出力結果がレジスタに書き込まれる形です。

この回路で、F = X + Y - W + Zの計算を行った際の計算過程を以下の表にまとめます。処理している式と、右辺の ACC が保持する中間結果を示します。

順番 処理 右辺の ACC の値
1 ACC = X OP_THROUGH_B N/A
2 ACC = ACC + Y OP_ADD X
3 ACC = ACC - W OP_SUB X + Y
4 ACC = ACC + Z OP_ADD X + Y - W
5 result = ACC OP_THROUGH_A X + Y - W + Z

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

それでは、シンプル・データパス回路を Verilog HDL で実装していきます。 ここまで説明したように、alu4.vを流用して、そこにアキュムレータを取り付けて、それぞれを配線で接続します。

シンプル・データパス回路 simple_datapath.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
`include "parameters.vh"
module simple_datapath(
    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
    );

    // "alu_result"という名前の信号線を宣言。ALUの出力に接続
    reg [`DATA_W-1:0] accumulator;
        wire [`DATA_W-1:0] alu_result;

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

    // posedgeごとに、aluの計算結果を"alu_out"レジスタに保持
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) // rst_nがローの時に成立。clkに非同期な非同期リセット
            // レジスタをゼロクリア
            accumulator <= `DATA_W'b0;
        else
            accumulator <= alu_result;
    end
    assign result = accumulator;
endmodule
シンプル・データパス回路用テストベンチ testbench_simple_datapath.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
`timescale 1ns/1ps
`include "parameters.vh"

module test;
    parameter STEP = 10;

    reg clk, rst_n;
    reg [`SEL_W-1:0] test_sel;
    reg [`DATA_W-1:0] test_input ;
    wire [`DATA_W-1:0] result;

    simple_datapath simple_datapath_1(.clk(clk),
                                    .rst_n(rst_n),
                                    .selector(test_sel),
                                    .input_b(test_input),
                                    .result(result)
                                    );

    initial begin
        $dumpfile("simple_datapath.vcd");
        $dumpvars(0, test);

        clk <= 0;
        rst_n <= 0;
        #STEP rst_n <= 1;

        test_sel<= `OP_THROUGH_B; test_input<=10'h4;
        #STEP clk <=1; #STEP clk <=0;
        $display("test_sel:%b test_input:%h result:%h", test_sel, test_input, result);

        test_sel<= `OP_ADD; test_input<=10'h3;
        #STEP clk <=1; #STEP clk <=0;
        $display("test_sel:%b test_input:%h result:%h", test_sel, test_input, result);

        test_sel<= `OP_SUB; test_input<=10'h2;
        #STEP clk <=1; #STEP clk <=0;
        $display("test_sel:%b test_input:%h result:%h", test_sel, test_input, result);

        test_sel<= `OP_ADD; test_input<=10'h1;
        #STEP clk <=1; #STEP clk <=0;
        $display("test_sel:%b test_input:%h result:%h", test_sel, test_input, result);

        test_sel<= `OP_THROUGH_A;
        #STEP clk <=1; #STEP clk <=0;
        $display("test_sel:%b test_input:%h result:%h", test_sel, test_input, result);

        $finish;
     end

endmodule

回路合成結果


図:simple_datapath.vのDigitalJS Onlineによる合成結果。左上あたりにalu4_1があり、その結果を$procdff$28という名前になったAccumulatorレジスタに入れている。レジスタの結果をalu4_1のAにもどしている。またレジスタは出力信号resultにも繋げている。結果はこちら

ビルド & 実行コマンド

iverilog -o simple_datapath.o ./testbench_simple_datapath.v ./simple_datapath.v ./alu4.v
vvp ./simple_datapath.o
gtkwave simple_datapath.vcd

演習

  1. シンプル・データパス回路のテストベンチのブロック図を描いてみましょう。特にwireに注意しましょう。

  2. シンプル・データパス回路をビルド&実行し、計算が正しいかを確認してみましょう。

  3. (22 + 7) * 6 = 174ができるように、ALU とテストベンチを改造してみましょう。