2011年05月17日

CPLD ALU

TTL ALU 74281』の記事の続き。

前記事はグダグダになってしまったので、仕切りなおし。

経緯 :

    もともと ALU の勉強として、TTL の ALU 78281 を作り、それを元にしてなにか作ろうともくろんでいたのだが、どんどん興味が変わっていって、結局 自作コアの rtavr の ALU になった。

    その後、rtavr の ALU は、機能拡張をして、機能的に過不足のないものが出来た。

    rtavr には関係ないのだが、可変ビット長への対応もいれている。

で、この ALU に アキュームレータを付けたものを作ってみたら、CoolRunner II CPLD (XC2C64A-VQ44) にうまく収まった。これをベースになにか作るのも面白いかも知れないので紹介。

インターフェイスと機能:

    module rtavr_alu_reg # (
    parameter ALU_WIDTH = 8
    ) (
    // ALU
    input C_IN // Carry Input
    , input Z_IN // zero Input
    , input [ALU_WIDTH-1:0] B // DATA IN
    , output [ALU_WIDTH-1:0] F // DATA OUT
    , output C_OUT // carry-look-ahead
    , output H_OUT // half carry-look-ahead
    , output Z_OUT // zero
    , output OVR // overflow
    , input [2:0] S // Function Select
    , input [1:0] ES // Function Select (Extended )
    // アキュームレータ
    , input [1:0] RS // Reg. Select
    , input CP // Clock
    , inout SIO0 // RI/LO
    , inout SIO3 // LI/RO

    ALU の A 入力は、アキュームレータの出力に接続されている。基本的には、

    F = A 演算 B;
    A <= F;

    という使い方をする。フラグは、次の 4 種類で、AVR で必要なもの。

    C : キャリーフラグ
    H : ハーフキャリー
    Z : ゼロ
    OVR : オーバフロー

    S は演算の指定。ES は、入力に定数を使うオプションで、INC/DEC や 2 の補数に変換する NEG などに使う。

    // S == 0 : ADD (wo carry)
    // S == 1 : ADC (with carry)
    // S == 2 : SUB (wo carry)
    // S == 3 : SBC (with carry)
    // S == 4 : AND
    // S == 5 : XOR
    // S == 6 : OR
    // S == 7 : MOV
    //
    // S = 0 1 2 3 4 5 6 7
    // C_out : Carry[8] o o o o x x x x
    // H_out : Carry[4] o o o o x x x x
    // V_out : Over Flow o o o o 0 0 0 x
    // Z_out : Zero o o o (o) - - - x
    // (o) if (Z_in == 1)
    // - no change
    // x don't care
    // EXTENDED Ops
    // ES[0] : -1 を B の代わりに使う
    // ES[1] : 0 を A の代わりに使う
    // (※ A を B の代わりに使う)
    // NEG : 0 - B (A == B) : S = 2, ES[1] = 1
    // COM : A ^ -1 : S = 5, ES[0] = 1
    // INC : A - -1 : S = 2, ES[0] = 1
    // DEC : A + -1 : S = 0, ES[0] = 1


      ※ AVR では、 ES[1] は、NEG を実装するときにしか使わない。ALU単独で使っているから B に A と同じ値を 入力している。アキュームレータ付きでは、 ES[1]のとき、 A を B の代わりに使うように仕様を変更した方が良い。ただし、XC2C64A に入らない。

      Implement の結果を書いておく。(変更後 のものは、XC2C128 で算出)

      変更前 変更後 トータル
      Macrocells Used 45 49 64
      Pterms Used 204 249 224
      Registers Used 8 8 64
      Pins Used 32 32 33
      Function Block Inputs Used 117 134 160


    アキュームレータは操作は RS を使い posedge CP で駆動する。

    RS == 0 : F の値を A にロード
    RS == 1 : F を 左シフト LSB は、SIO0 から入力して、MSB を SIO3 に出力
    RS == 2 : F を 右シフト LSB は、SIO0 に出力して、MSB を SIO3 から入力
    RS == 3 : HOLD
     (※)シフトは、F をシフトしたものを A にロードする。


処理コード :


    wire WC = (S[0] & C_in);
    wire [ALU_WIDTH-1:0] AX = (ES[1]) ? 0 : A;
    wire [ALU_WIDTH-1:0] BX = (ES[0]) ? -1 : B;
    ( ※ wire [ALU_WIDTH-1:0] BX = (ES[1]) ? A :(ES[0]) ? -1 : B; )
    wire [ALU_WIDTH:0] WCX = WC; // change bit width
    wire [ALU_WIDTH:0] ALU_OUT = ((S == 0)|(S == 1))
    ? ({1'b0, AX} + ({1'b0, BX } + WCX))
    : ((S == 2)|(S == 3))
    ? ({1'b0, AX} - ({1'b0, BX } + WCX))
    : (S == 4) ? {1'b0, (AX & BX)}
    : (S == 5) ? {1'b0, (AX ^ BX)}
    : (S == 6) ? {1'b0, (AX | BX)}
    : {1'b0, BX } ;
    assign Z_out = (S == 3) ? (Z_in & (ALU_OUT[ALU_WIDTH-1:0] == 0))
    : (ALU_OUT[ALU_WIDTH-1:0] == 0);

    assign C_out = ALU_OUT[ALU_WIDTH];

    assign F = ALU_OUT[ALU_WIDTH-1:0];

    // ((S == 4)|(S == 5)|(S == 6)) ? 0 // (S ==7) Not care
    assign V_out = (S[2]) ? 0:
    ( ~(S[1]) & (F[ALU_WIDTH-1] & ~AX[ALU_WIDTH-1] & ~BX[ALU_WIDTH-1]
    | ~F[ALU_WIDTH-1] & AX[ALU_WIDTH-1] & BX[ALU_WIDTH-1])
    | (S[1]) & (F[ALU_WIDTH-1] & ~AX[ALU_WIDTH-1] & BX[ALU_WIDTH-1]
    | ~F[ALU_WIDTH-1] & AX[ALU_WIDTH-1] & ~BX[ALU_WIDTH-1]) )
    ;
    assign H_out =
    ( ~(S[1]) & ( ~F[(ALU_WIDTH-1>>1)] & A[(ALU_WIDTH-1>>1)]
    | ~F[(ALU_WIDTH-1>>1)] & BX[(ALU_WIDTH-1>>1)]
    | AX[(ALU_WIDTH-1>>1)] & BX[(ALU_WIDTH-1>>1)] )
    | (S[1]) & ( F[(ALU_WIDTH-1>>1)] & ~AX[(ALU_WIDTH-1>>1)]
    | F[(ALU_WIDTH-1>>1)] & BX[(ALU_WIDTH-1>>1)]
    | ~AX[(ALU_WIDTH-1>>1)] & BX[(ALU_WIDTH-1>>1)] ) )
    ;

    中身は意外と短い。ALU を全部載せてもこれだけ。

    wire SO0;
    wire SO3;
    wire SI0;
    wire SI3;

    assign SO0 = F[0];
    assign SO3 = F[ALU_WIDTH-1];

    always @(posedge CP)
    begin
    acc <= (RS == 0) ? F
    : (RS == 1) ? { F[ALU_WIDTH-2:0] , SI0 }
    : (RS == 2) ? { SI3 , F[ALU_WIDTH-2:0] }
    : acc;
    end
    assign SIO0 = (RS == 2) ? SO0 : 1b'Z ;
    assign SIO3 = (RS == 1) ? SO3 : 1b'Z ;

    assign SI0 = SIO0;
    assign SI3 = SIO3;

    こっちはアキュームレータ。ALU は確認済みだが、こちらは書いただけ。

これを使って、なにか簡単なプロセッサを設計してみようと思う。

    実をいうと、大昔に設計したものを再現しようと思っている。同じものにはなるはずもないが、形にして残しておきたい。

    詳細は思い出せないのだが、こんな感じだった。

    • マイクロプログラミング方式で、8 bit の INST を INDEX にして マイクロプログラム用ROM(以下 μROM) に接続。

    • μROMの出力 は、各 bit が ES,S, RS や その他の制御線に接続されている。(全部で 24bit 以内)。

    • マイクロプログラム用の PC(以下 μPC) は、次の INST を読み込むまでインクリメント。この制御のための制御線も μROM の出力に接続されている。

    • 16bit の インデックスレジスタ、PC が 1 つづつ。それらの制御線も μROMの出力が接続されている。

    • B 入力は、バスのラッチで、制御線は同様になっている。

    • 総命令数は、16 前後で、マイクロプログラムは、64 前後だった。

    アドレス 6bit x 24bit 幅なら 分散RAM で 96 LUT 。ブロック RAM に割り付ければコストは無視できる。レジスタ用の FF は、PC 16 , INDEX 16, LATCH 8, と μPC 8 (6 ?) で、40 。

    FPGA なら、それなりにコンパクトなものになるようだ。

設計してみよう。



    wire [7:0] load_data;
    reg [5:0] UPC ;
    reg [23:0] UROM [0:63];
      wire [23:0] urom_out = UROM[UPC];

    というのをまず考えてみる。


    wire u_loadpc = urom_out[7];
    always @(negedge CLK)
    begin
    if (RESET)
    begin
    UPC <= 0;
    end
    else
    begin
    if (u_loadpc) UPC <= load_data[5:0];
    else UPC <= UPC + 1;
    end
    end

    マイクロプログラムの制御はこれだけ。
    メモリから LATCH に INST を ロードするには、

    wire c_bus_rd = urom_out[8];
    wire c_bus_wr = urom_out[9];
    wire c_addr_sel = urom_out[10]; // 0: PC , 1: INDEX
    wire c_load_latch = urom_out[11];
    wire c_load_index_l = urom_out[12];
    wire c_load_index_h = urom_out[13];
    wire c_load_pc_l = urom_out[14];
    wire c_load_pc_h = urom_out[15];
    wire c_inc_pc = urom_out[16];

    reg [7:0] LATCH ;
    reg [15:0] PC ;
    reg [15:0] INDEX ;

    wire [15:0] BUS_ADDR = (c_addr_sel) ? INDEX: PC;
    always @(negedge CLK)
    begin
    if (RESET)
    begin
    LATCH <= 0;
    PC <= 0;
    INDEX <= 0;
    end
    else
    begin
    if (c_load_latch) LATCH[7:0] <= BUS_DATA;
    if (c_load_index_l) INDEX[7:0] <= BUS_DATA;
    if (c_load_index_h) INDEX[15:8] <= BUS_DATA;
    if (c_load_pc_l) PC[7:0] <= BUS_DATA;
    else if (c_load_pc_h) PC[15:8] <= BUS_DATA;
    else if (c_inc_pc) PC <= PC + 1;
    end
    end
    // +pPiIlswr
    UROM[n+0] <= 24'bxxxxxxx1000010010xxxxxxx;
    UROM[n+1] <= 24'bxxxxxxx0000000001xxxxxxx;

    こんな風に組み立てていく。
    ... 思い出した。INST の読み込みは、各命令の実行後必要だし、μプログラムにも JUMP ぐらいは作ったのだった。


    wire u_loadpc = urom_out[7];
    wire u_jump = urom_out[6];
    always @(negedge CLK)
    begin
    if (RESET)
    begin
    UPC <= 0;
    end
    else
    begin
    if (u_loadpc) UPC <= load_data[5:0];
    else if (u_jump) UPC <= urom_out[5:0];
    else UPC <= UPC + 1;
    end
    end

    こうだったような。ちなみに、urom_out[5:0] は、u_jump == 1 のときだけ使っているから、それ以外のときは、別の目的に使える。何に使っていたか思い出せないが 分岐命令とかの 制御だったような...

    条件分岐命令をインプリメントするときに、μ命令の方も 条件による分岐が必要になる。が、u_jump の条件をいじれば良い。... これは 後で考えよう。

    次は、ALU を組み込む。

    wire [7:0] alu_b = LATCH;
    wire [7:0] alu_f;
    assign store_data = alu_f[7:0];
    wire [1:0] alu_rs = urom_out[23:22];
    wire [1:0] alu_es = urom_out[21:20];
    wire [2:0] alu_s = urom_out[19:17];

    reg C;
    reg Z;
    reg V;
    reg H;

    rtavr_alu ALU (
    .B(alu_b), .F(alu_f)
    , .S(alu_s) , .ES(alu_es)
    , .CP(CLK) , .RS(alu_rs)
    , .C_in(C) , .Z_in(Z)
    , .C_out(C_OUT) , .H_out(H_OUT)
    , .V_out(OVR) , .Z_out(Z_OUT)
    );
    always @(negedge CLK)
    begin
    if (RESET)
    begin
    C <= 0;
    Z <= 0;
    end
    else
    begin
    C <= C_OUT;
    Z <= Z_OUT;
    end
    end

    全部割り当てたら 24 bit 使いきってしまった。条件分岐のために、あと 1 bit 欲しい。24bit にこだわる必要はないのだが ... 整理して浮かそう。

    ... 思い出した。ALU の S (演算選択) を 8 bit ある命令空間の下位 3 bit にしたのだった。(以下 INST_ATTR )

    で、UPC を 上位にもっていく。UPC[0] は、重なるから 常に 0 をロード。-- 要するに 奇数番地から始まる命令は作れない。

    このINST_ATTR は他の目的にも使える。ひとつが、分岐命令の条件選択。最大 8 通り作れるわけだ。フラグは 4 つあるから、0 のとき 分岐 と 1 のとき分岐の組み合わせで 合計 8 通り。

    これで、3 bit 減って 1 bit の追加で、合計 22 bit 。

    もうひとつは、シフトのときの動作の指定。シフト動作は、RS として UROM に割り当てているが、C になにをロードするかの指定と、SHIFT_IN するデータを何にするかの組み合わせがある。INST_ATTR から 3 bit 割り当てて、足りなければ UROM を割り当てることにする。

zsp8-0.1

  • zsp8-0.1.tar.gz

    ここまでの成果を zsp8-0.1 として置いておく。μプログラムが出来ていないが Implement だけはやってみた。


        Macrocells Used Pterms Used Registers Used Pins Used Function
    Block Inputs Used
    ベース 135/256 565/896 60/256 36/80 322/640
    拡張版 140/256 586/896 60/256 36/80 361/640


    これは、CPLD XC2C256-VQ100 への Implement 結果。拡張版は、ES[1] == 1 のとき A 入力を B に持っていく拡張。

    ひょっとしたら XC2C128 に入るかもと思ったがちょっと無理のようだ。


しばらく、この記事はお休み。気が向いたら続きを書くかも。
posted by すz at 21:12| Comment(0) | TrackBack(0) | CPLD
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/45244422
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック