LoginSignup
1
2

More than 3 years have passed since last update.

シンプルALUの作り方

Posted at

皆さんALUが必要な時はどうやって実装しているでしょうか? マクロが入手可能ならそれを使うのが一番ですが、諸事情によりマクロが使えないこともあります。以下のように必要な演算をべた書きで書いてもいいのですが、少しでも回路規模を小さくしたい場合にちょっとしたテクニックがありますので、今回はそれを紹介します。

case(opcode)
  4'b0: f = a & b;
  4'h1: f = a | b;
  4'h2: f = a ^ b;
  4'h3: f = a + b;
  4'h4: f = a - b;
  ...

なお、この方法はFPGAとは相性が良くなく、試したことはないのですがあまり回路規模は小さくならないと思います。あと、簡単なテストしかしていないのでもしかしたら回路に不具合があるかもしれません。もし不具合が見つかったら教えていただけると幸いです。

74181知っていますか?

皆さん74181という集積回路をご存知でしょうか? 4ビットスライスのALUなのですが、昔はこのチップを使用してCPUのALU部を構成していたこともあったそうです。
このチップはゲートレベルでの論理回路が公開されているのですが、非常に最適化されているので、これをそのまま拝借するとコンパクトなALUを実現することができます。ただしスペックを見ればわかる通り、AND/OR/XOR/ADD/SUBなどごく基本的な演算のみで、シフトや乗除算はサポートされません。これらが必要な場合別に回路を組む必要があります。
74171のスペックに関しては現時点ではTIのデータシートを参照するのが最良と思います。

実装について

元は4bitスライスなのですが、ここでは1bitスライスに簡略化します。回路図から出力のf[0]に関係する部分と、f[1]につながるキャリーを出力してやればそれで終了です。

module alu(output logic f,
           output logic co_x,
           input logic a,
           input logic b,
           input logic ci_x,
           input logic [3:0] s,
           input logic m);

  wire g;
  wire h;

  assign g = !(a | (b & s[0]) | (!b & s[1]));
  assign h = !((!b & s[2] & a) | (a & s[3] & b));
  assign co_x = (g | (h & ci_x));

  assign f = !(!m & ci_x) ^ g ^ h;
endmodule

実際に使用する時はこれを必要なビット幅だけインスタンシエートしてキャリーを繋げます。以下のコードではparameterとgenerateを使って任意のビット幅を構成できるようにしています。
本回路を論理合成する場合は、ungroupして下位モジュールの境界をなくしてしまったほうが良い結果が得やすいようです。

module alu_n
  #(parameter WIDTH=4)
  (output logic [WIDTH-1:0] f,
   output logic co_x,
   input logic [WIDTH-1:0] a,
   input logic [WIDTH-1:0] b,
   input logic ci_x,
   input logic [3:0] s,
   input logic m);
  genvar i;

  logic [WIDTH:0] c_x;
  assign c_x[0] = ci_x;
  assign co_x = c_x[WIDTH];

  generate
    for (i = 0; i < WIDTH; i = i + 1) begin : alu_slice
      alu alu_ins(.f(f[i]), .co_x(c_x[i + 1]), .a(a[i]), .b(b[i]), .ci_x(c_x[i]), .s(s), .m(m));
    end
  endgenerate
endmodule

EDA playgroundに上記コードと簡単なテストコードを登録しました。

さらにパイプライン化

この回路、加減算のキャリーが1ビットずつ上位に伝搬するので、ここがクリティカルパスになります。スピードが欲しい場合、がんばってキャリールックアヘッドの回路を組んでこのパスを最適化しても良いのですが、パイプライン化することによりこのクリティカルパスをFFで分割することもできます。
ただ、この方法ではFFが必要になりますので、最初のコンセプトの回路規模の削減とは合わない結果になるかもしれません。慎重な検討が必要です。
8bit ALUの4bitスライスの1段パイプラインの場合のイメージは以下のようになります。

<---PL0------><---PL1-------->

a[7:4]--------FF---ALU--f[7:4]
b[7:4]      ^
            | Carry
            |
a[3:0]--ALU-+-FF--------f[3:0]
b[3:0]

12bit ALUの4bitスライスの2段パイプラインの場合は以下のようになります。このように、レイテンシは伸びますが、必要なぶんだけいくらでもパイプライン化してクリティカルパスを分割することが可能です。

<---PL0------><---PL1--------><--PL2--------->

a[11:8]-------FF--------------FF--ALU--f[11:8]
b[11:8]                    ^
                           | Carry
                           |
a[7:4]--------FF---ALU-----+--FF--------f[7:4]
b[7:4]      ^
            | Carry
            |
a[3:0]--ALU-+-FF--------------FF--------f[3:0]
b[3:0]

8bit ALUを4bitスライス1段パイプラインで構成した場合のRTLが以下になります。

module alu_pipe(output logic [7:0] f,
                input logic [7:0] a,
                input logic [7:0] b,
                input logic ci_x,
                input logic [3:0] s,
                input logic m,
                input logic clk);
  wire [3:0] f_p0;
  wire c_p0;
  logic [7:0] f_p1;
  logic [7:4] a_p1;
  logic [7:4] b_p1;
  logic ci_x_p1;
  logic [3:0] s_p1;
  logic m_p1;

  // PL0
  alu_n alu_p0(.f(f_p0[3:0]), .co_x(c_p0), .a(a[3:0]), .b(b[3:0]), .ci_x(ci_x), .s(s), .m(m));

  // PL1
  always_ff @(posedge clk) begin
    a_p1 <= a[7:4];
    b_p1 <= b[7:4];
    ci_x_p1 <= c_p0;
    s_p1 <= s;
    m_p1 <= m;
  end

  alu_n alu_p1(.f(f_p1[7:4]), .co_x(), .a(a_p1), .b(b_p1), .ci_x(ci_x_p1), .s(s_p1), .m(m_p1));

  always_ff @(posedge clk) begin
    f_p1[3:0] <= f_p0[3:0];
  end

  assign f = f_p1;
endmodule

こちらもEDA playgroundに簡単なテストベクタとともに登録してあります。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2