シンプルALUの作り方

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


```verilog
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のデータシート](https://www.ti.com/lit/ds/symlink/sn54ls181.pdf)を参照するのが最良と思います。

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

```verilog
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して下位モジュールの境界をなくしてしまったほうが良い結果が得やすいようです。

```verilog
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](https://www.edaplayground.com/x/MLNa)に上記コードと簡単なテストコードを登録しました。

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

```plaintext
<---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段パイプラインの場合は以下のようになります。このように、レイテンシは伸びますが、必要なぶんだけいくらでもパイプライン化してクリティカルパスを分割することが可能です。

```plaintext
<---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が以下になります。

```verilog
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](https://www.edaplayground.com/x/LR48)に簡単なテストベクタとともに登録してあります。