2012年06月28日

USBコントローラの設計

USBコントローラを設計してみたい。-- これは、FPGA を始めたときから思っていたこと。そろそろ、それが出来るレベルになったかも。少なくともどうやって設計すべきなのか見当も付かなかったのが、今は見当ぐらいは付くようになった。

最初は、規模が小さい MachXO2-256 で作れるものとして検討してみよう。もちろん、規模が小さいわけだから、一般的なコントローラの機能は作れない。では何を作るのか?

    レベル 0)
    パケットを送出し、受け取る。そういうものならまずは可能だろう。ホストから来た 0/1 のシーケンスをそのまま送るのは、難しくなさそうだ。

    受信を考えると、まず相手のクロックに同期させなければならない。4 倍クロックを使って、最初の 信号の立ち上がりに合わせようかと思う。ここのところは、少々難しそうだが、USART などと似た様なものだから、それほどでもないだろう。

      パケットの開始(SOP)は、送信側からみると、入力状態から、アイドル状態と同じ値を一旦出力することらしい。アイドル状態は、出力しない状態で Low-Speed では、D- がプルアップされているわけだから D- = H , D+ = L 。Full-Speed では、逆になる。

      続いて SYNC データを送出する。データは LSB first で 0x80 。これを NRZI (Non Return Zero Invert) エンコードすると H-L-H-L-H-L-H-H (Low-Speed での D+/Full-Speed D-)。 最初に L → H と変わるから ここを拾って 受信クロックを同期させるわけだ。 クロックが同期してなければ、1 つ遅延させる。 6 回チャンスがあるから、最後の H-H までに同期するはず。

      パケットの終了(EOP)は D- , D+ 共に L 出力 x 2クロックの後 、アイドル状態と同じ値を一旦出力して、入力状態。受信では、D- , D+ 共に Lを検出したら、1 クロック待って 受信を終了させれば良い。

    あと難しそうなのが、FIFO が必須ということ。途切れなく 送受信しなければならないので、データが来たからといって、すぐに 送信すれば良いものでもない。受信では、FULL にならないようにする配慮も必要。

    ちなみに、Full-Speed での最大パケットサイズは、512 Bytes だったと思う。MachXO2-256では、合計で 64B ぐらいしか取れそうにない。なかなか考えどころ。

    レベル1)
    USB では、NRZI (Non Return Zero Invert) エンコーディング というのを行なっている。レベル1では、エンコード/デコードする 。後で説明するが、NRZI を 論理レベルに変換すると、MCU 側の処理が楽になる。コントローラ側でも 送受信の単位が バイト単位になって FIFO まわりが少し楽になる。

    レベル2)
    パケットの形式を知った上で、いくつかの機能をサポート。パケット ID(PID) が正しいかどうかの検出や、CRC のチェック/生成など。

    このレベルより高度なものは規模的に無理。このレベルでも怪しい。一応目標にはするが、レベル1になるかも。

さて、こういうものを作るとして、MCU との 送受信インターフェイスはどうしよう。FIFO を使うから基本は、8bit パラレル。

ただ、パラレルでは、ピンを消費する上に AVR なんかだと SPI より遅くなる。やはり SPI かと考えている。まぁこれは、出来た後でも良い。

ところで、USB PHY のインターフェイスは、UTMI というものが定義されているらしい。SMSC では、これのピン数を減らした ULPI( UTMI + Low Pin Interface) の PHY を製品化している。これに合わせることは、考えていないが、出来た後、再度検討してみよう。まずは、上記のものを設計しなければ。

USBでの信号の扱い

    基本は、差動信号だが、上で出てきたように例外がある。(両方 L レベル)

    FPGA は一般に 差動出力も持っているのだが、これをサポートするため、シングルエンド x 2 で操作すれば良いだろう。受信側も 同じ事情。差動入力もできるが、シングルエンドも必要。FPGA は 1つのポートで 複数の入力の仕方ができるものなのだろうか? その上 出力にも切り替えられないといけないのだが ...

    多分無理だと考えて、差動入力をパラレルに接続することにしよう。ポートが勿体なければ、シングルエンドで 値を読むことにしても良い。

    さて、デバイスとして構成するなら 1.5K のプルアップが必要だ。ポートが余っていれば 2つのポートに抵抗を付けることで、設定可能にしても良いだろう。ホスト側は、15K のプルダウン。これは、FPGA のプルダウン機能でいけそう。

    こういうことなので、最大は D-,D+ にそれぞれ 3 つづつ 計 6 個のポートが必要。

LRZI について

    簡単に説明してしまうと 論理 0 出力は反転、論理 1 出力は 状態保持。
    そして、同じ状態が 6 クロック続くと 次に 0 を挿入する。-- 要するに論理 11111 は、0 または 1 の状態が 6 クロック続くので 0 が挿入される。

    Full-Speed は、物理的な差動出力が逆になるわけだが、受信側から見ると、同じ値になる。初期状態が違うだけで、反転-保持の関係は変わらないからだ。

以上の理解で、レベル1は設計できるはず。さあ設計してみよう。

クロックジェネレータ

    48 MHz を入力して、12 MHz を作る。Low-Speed の場合は、6MHz 入力で 1.5 MHz を作る。

    PLL のない MachXO2-256 で 48 MHz をどうするのか? という問題があるが、とりあえず考えない。あとクロックは、送信中、受信中のみ 生成することにしよう。

    module udrv_clkgen (
    input CLK_4X
    , input DP
    , input SYNC_REQ
    , output SYNC_ACK
    , output CLK
    );
    reg [1:0] clk_ph = 0;
    reg r_dp = 1'b0;
    reg r_done = 1'b0;

    assign CLK = clk_ph[1];
    assign SYNC_ACK = r_done;

    always @(posedge CLK_4X)
    begin
    r_dp <= DP;
    if (~SYNC_REQ | ~(r_dp ^ DP) | (clk_ph == 3) )
    clk_ph <= clk_ph + 1;
    if (~SYNC_REQ)
    r_done <= 1'b0;
    else if ((r_dp ^ DP) & (clk_ph == 3))
    r_done <= 1'b1;
    clk_ph <= clk_ph + 1;
    end
    endmodule

    // _______
    // CLK |_______|
    //
    // clk_ph 3 | 0 | 1 | 2 | 3 | 0
    // _____________
    // DP >< ><
    //
    // _________
    // SYNC_ACK __|
    //

    SYNC_REQ は、上位モジュールからの受信要求で、SYNC_ACK が 1 になると 受信準備が出来たということで、受信モジュールを enable するつもり。

    (clk_ph == 3) 以外で DP が変化すると 1/4 クロック遅らせる。OK になれば、SYNC_ACK が 1 になるので、(上位モジュールが) SYNC_REQ を 0 にする。

受信モジュール

    DP 以外に SE0 という入力信号を作ることにした。これは、DP-,DP+ を シングルエンドとして見て共に 0 のとき 1 にする。RECV_EN で受信開始だが、SYNC の途中なので、1 - 1 または 0 - 0 が来た次から 採取開始。データは、FIFO に入れることにする。FIFO が溢れても関知しない。

    module udrv_reciever (
    input CLK
    , input RECV_EN
    , input DP
    , input SE0
    , output [7:0] RECV_DATA
    , output RECV_DATA_RDY
    , input RECV_DATA_ACK
    );

    reg recv_stat = 0;
    reg r_dp = 0;
    reg [7:0] data_out;
    reg data_rdy = 0;
    reg [7:0] r_data;
    reg [2:0] data_count = 0;
    reg [2:0] one_count = 0;

    assign RECV_DATA[7:0] = r_data[7:0];
    assign RECV_DATA_RDY = data_rdy;

    always @(posedge CLK)
    begin
    r_dp <= DP;
    if (~RECV_EN | SE0)
    begin
    recv_stat <= 0;
    data_count <= 0;
    r_data <= 0;
    one_count <= 0;
    end
    else if (recv_stat == 0)
    begin
    if (~(r_dp ^ DP)) // end of SYNC
    recv_stat <= 1;
    end
    else
    begin
    if (~(r_dp ^ DP)) one_count <= one_count + 1;
    else one_count <= 0;

    if (one_count <= 5)
    begin
    r_data <= { ~(r_dp ^ DP), r_data[7:1] };
    data_count <= data_count + 1;

    if (data_count == 7)
    data_out <= { ~(r_dp ^ DP), r_data[7:1] };
    end
    end

    if ( RECV_EN & ~SE0 & (recv_stat == 1)
    & (one_count <= 5) & (data_count == 7) )
    data_rdy <= 1'b1;
    else if (RECV_DATA_ACK)
    data_rdy <= 1'b0;

    end
    endmodule

送信モジュール

    SEND_EN のとき送信開始。まず SYNC を送出し、次からデータを送る。データが途切れると送信終了。
    送信側は、シングルエンドとしてのポート状態を生成する。ちょっと変なのだが、(SEND_DONE != 0) のとき DP,DM を出力することを想定している。

    一応 受信モジュールとのペアで、パラレルの通信ができるようにした。送信が途切れると パケットを終了し、再開すると自動的に SYNC が付く。CRC などはないから、再送できないので実用的ではないが、ロジックのデバッグには役立ちそうだ。

    send_stat は、6 状態になった。シーケンスがあるので、そんなものだろう。

      send_stat = 0 : アイドル
      send_stat = 1 :
      send_stat = 2 : SYNC とデータ
      send_stat = 3 : SOP 1 (SE0)
      send_stat = 4 : SOP 2 (SE0)
      send_stat = 5 : SOP 3

    module sender (
    input CLK
    , input SEND_EN
    , output SEND_DONE
    , output DP
    , output DM
    , input [7:0] SEND_DATA
    , input SEND_DATA_RDY
    , output SEND_DATA_ACK
    );

    reg r_dp;
    reg r_dm;
    reg [2:0] send_stat = 0;
    reg r_out = 0;
    reg [6:0] data_in;
    reg data_ack = 0;
    reg [7:0] data_count = 0;
    reg [7:0] one_count = 0;

    assign DP = r_dp;
    assign DM = r_dm;
    assign SEND_DONE = (send_stat == 0);
    assign SEND_DATA_ACK = data_ack;

    always @(posedge CLK)
    begin
    if (~SEND_EN)
    begin
    send_stat <= 0;
    end
    if ( (send_stat == 0) & SEND_EN )
    begin
    send_stat <= 1;
    data_count <= 7;
    data_in[7:0] <= 7'b1000000;
    r_out <= 1'b0;
    end
    else if (send_stat == 1)
    send_stat <= 2;
    else if ( (send_stat == 2) & (one_count <= 5) & (data_count == 0) )
    if (SEND_DATA_RDY)
    begin
    data_count <= 7;
    data_in[6:0] <= SEND_DATA[7:1];
    r_out <= SEND_DATA[0];
    end
    else // exit sending
    send_stat <= 3;
    else if ( (send_stat == 2) & (one_count <= 5) )
    begin
    data_count <= data_count - 1;
    data_in[6:0] <= { 1'b0, data_in[6:1] };
    r_out <= data_in[0];
    end
    else if (send_stat == 3) send_stat <= 4;
    else if (send_stat == 4) send_stat <= 5;
    else if (send_stat == 5) send_stat <= 0;

    if ( (send_stat == 3) | (send_stat == 4) ) // SE0
    begin
    one_count <= 0;
    r_dp <= 1'b0;
    r_dm <= 1'b0;
    end
    else if (send_stat == 2)
    begin
    if ( (~r_out) | (one_count > 5) )
    begin
    one_count <= 0;
    r_dp <= ~r_dp;
    r_dm <= r_dp;
    end
    else one_count <= one_count + 1;
    end
    else // if ( (send_stat == 1) | (send_stat == 5) )
    begin
    one_count <= 0;
    r_dp <= (FS) ? 1'b1 : 1'b0;
    r_dm <= (FS) ? 1'b0 : 1'b1;
    end

    data_ack <= SEND_EN & SEND_DATA_RDY & ( (send_stat == 1)
    & (one_count <= 5) & (data_count == 0) );
    end
    endmodule

FIFOモジュール

    FIFOモジュールも自分で作る。Dual PORT の分散RAM ベース。32 バイトで 30 スライス 、64 バイトだと 50 スライス。たぶん 32 バイトしか選べない。

      32 バイト増えて 20 スライス増えている。2 バイトあたり 1 スライス だから 16bit x 1bit が 2 LUT の計算。Dual PORT ならこの値が適切。

      送受信は、同時には行わない。FIFO を 2 つ持つのは、もったいないので可能なら共用にしたい。

    module udrv_fifo # (
    parameter DEPTH = 5
    // parameter DEPTH = 6
    ) (
    input CLK
    , input RST
    , input [7:0] I_DATA
    , input I_DATA_RDY
    , output I_DATA_ACK
    , output I_DATA_FULL
    , output [7:0] O_DATA
    , output O_DATA_RDY
    , input O_DATA_ACK
    );
    reg [5:0] mem [0:2**DEPTH-1];

    reg [DEPTH-1:0] i_addr = 0;
    reg [DEPTH-1:0] o_addr = 0;
    wire [DEPTH-1:0] i_next = i_addr + 1;
    wire [DEPTH-1:0] o_next = o_addr + 1;
    reg i_ack = 0;
    reg o_rdy = 0;
    assign I_DATA_FULL = (i_addr != o_next);
    assign I_DATA_ACK = i_ack;
    assign O_DATA_RDY = o_rdy;

    always @(negedge CLK)
    begin
    if (RST)
    begin
    i_addr <= 0;
    o_addr <= 0;
    i_ack <= 1'b0;
    o_rdy <= 1'b0;
    end
    else if (I_DATA_RDY & ~i_ack)
    begin
    if (~I_DATA_FULL)
    begin
    i_addr <= i_next;
    mem[i_next] <= I_DATA;
    end
    i_ack <= 1'b1;
    end
    else
    i_ack <= 1'b0;

    if ( O_DATA_ACK & (i_addr != o_next))
    begin
    o_addr <= o_next;
    o_rdy <= 1'b1;
    end
    else if (i_addr != o_next)
    o_rdy <= 1'b1;
    else if ( O_DATA_ACK )
    o_rdy <= 1'b0;
    end
    assign O_DATA = mem[o_addr];

    endmodule

    以上が今回作ったもの。シミュレーションとかデバッグは未だ。あとインターフェイスに SPI を付けなければならない。そうしないとピンが多すぎて、QFN32 に収まらないのだ。

    で、専用の SPI モジュールを作ったのだが、問題がある。パケットより小さな FIFO で、Full-Speed に対応するには、12 M bps 前後で通信できないといけない。なかなかに厳しい条件だが、それは置いておく。問題は、SPI の SCK が CLK より早い場合にも対応しないといけないところ。FIFO との ハンドシェークが鍵なのだが、うまく行っていないかも。

    とりあえず QFN32 に収めるために、8bit データを inout に変更するのが良さそう。

    ところで、作ったは良いがどうやって使うのか? ... V-USB のコードを改造すればうまく制御できるのではないかと思っている。V-USB のなかで見たくもない部分は、通信のところで、アセンブラになっている。これをまるごと置き換えてしまえば 良さそうなのだ。ただ、Full-Speed に対応するには、48 MHz が必要になってしまう。Low-Speed で良いなら V-USB で良いし。結局のところ 規模の大きいFPGA のコアで使うぐらいか。

    なかなか使いドコロがないが、CRC のコードも作りたいし、それだけ追加してみて、シミュレータでチェックして、ひとまずの完成としよう。

    この後、送信をシミュレータでデバッグした。


    Full-Speed(FS) の例。FS では、D+(DP) がプルアップされているわけだから DP = H がアイドル。Hi-Z から DP = H なって、LHLHLHLL と SYNC コードを送出している。次に 0x33 を送るのだが、 ちゃんと LLHLLLHL となっている。

    終わりは、DP/DM 共に L を 2 クロックで、次に アイドルと同じ DP = H で最後に Hi-Z 。

    結構バグがあったが、なんとかデータを送ってくれるようになった。上で貼り付けたコードは、バグがあるままなので注意。正しいのは、最新のソースコードのみである。

      送信でインターフェイスを若干変更。ひとつは、送信STARTを指示するフラグ。FIFO にパケットを全部送ってから、送信というのが妥当だと思えたので。-- 以前に AT90USB162 のコードを作ったが 最大パケットサイズは、32 か 64 にしか出来なかった。それで十分でもあった。その程度ならば FIFOに全部入るのだ。

      もうひとつは、DP/DM を出力にするのを指示するフラグ。SEND 中というフラグで代用しようと思っていたのだが 1 クロックずれる。

    次は受信をデバッグしよう。送信データを受信側とつなぐのだが、CLK がずれるので、ちょっと工夫がいる。


    受信も一応動かせた。受信要求を出すと SYNC_REQ が 1 になって、CLK をずらす。確かに 180°ずれて SYNC が完了しているのだが、SYNC_REQ が ON/OFF を繰り返していて、ぎりぎりで 受信開始になっている。... まずそう。
    それは、おいおい直すとして ... 今回は、送信用、受信用 2 つのモジュールを接続したのだが、pull-up/pull-down が分からなかったので論理レベルで接続することにした。このため、LB_TEST という define を追加。


    0x33,0x34,0x35,0x36 を送信しているのだが、その 4つをちゃんと受け取って、受信終了。その後、RECV_REQ が ON/OFF を繰り返している。(受信前は、SYNC_REQ)。一応動きはしたが、やっぱりまずそうだ。

CRC について(基礎編)

    まずは基礎から。

    USBでは、CRC-5 と CRC-16 の2通りの CRC を使っている。CRC-5 は SYNC 込み 32bit のパケット専用で、それより大きいパケットでは CRC-16 を使う。CRC-16 と言っても、計算式はひと通りしかないわけではないらしい。USB での方法を知らないといけないのだ。

    (16bit) x^15 + x^14 + x^2 + 1
    (5bit) x^5 + x^2 + 1

    生成多項式にはこれを使う。初期値には all 1 を使い、出力は ビット反転した上で LSB と MSB も反転。

    コードで示した方が早そうだ。

    CRC-16:
    unsigned int r_crc = 0xffff;
    unsigned int In;
    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0x7fff;
    if (w) {
    r_crc ^= (1<<0);
    r_crc ^= (1<<13);
    r_crc ^= (1<<15); // w
    }
    In = (In >> 1);
    printf("%04x r %04x\n", r_crc, ~r_crc & 0xffff);
    }
    CRC-5:
    unsigned int r_crc = 0x1f;
    unsigned int In;

    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0xf;
    if (w) {
    r_crc ^= (1<<4);
    r_crc ^= (1<<2);
    }

    In = (In >> 1);
    printf("%02x r %02x\n", r_crc, ~r_crc & 0x1f);
    }

    出力は、~r_crc (bit反転したもの)なので注意。

    さて、受信で CRC値が含まれているが、これも含めて CRC 計算してしまうと ... 特定の値になる。CRC-5 では 5'b11001 , CRC-16 では 16'h4FFE 。(bit反転前の値(r_crc) なので注意)

CRC について(応用編)

    応用編というより実装編。

    module udrv_crc # (
    parameter WIDTH=16
    // parameter WIDTH=5
    , parameter SEQUENTIAL_MATCHING=0
    ) (
    input CLK
    , input [1:0] SEL
    , input I // LSB first
    , output O // LSB first
    , output MATCH
    , output [WIDTH-1:0] CRC
    );

    SEL で 動作を指定するのだが、基本的に 2 つのモードがある。ひとつは、01 で CRC 計算。もうひとつは、10 で、計算結果をビットストリームにして出力 -- 送信などで使う。
    00 は、なにもしない。-- USB では、0 が挿入される場合があるから、これが必要なのだ。あと、11 は、初期値にする 。
    パラレルの CRC は、シミュレータのチェック用で使わないのが基本。

    同時に CRC の一致検出 もつけた。やりかたには 2 通りある。ひとつは、計算結果を出力して、入力と比べるというやりかた(SEQUENTIAL_MATCHING=1)。もうひとつは、最後まで計算して特定の値になったかどうか チェックするやりかた(SEQUENTIAL_MATCHING=0)。計算結果を出力してしまうと、以降計算はできないので両立はできない。

    (SEQUENTIAL_MATCHING=0) は、受信での CRC-16 チェックに使う。USB ではパケットを受け取り終わってはじめて、2 バイト前が CRC だったと分かる。だから、これを使う以外にはない。

    (SEQUENTIAL_MATCHING=1) は、CRC-5 に使おうかと思っている。

    reg [WIDTH-1:0] r_crc = (-1);
    wire I2 = (r_crc[0] ^ I);
    assign CRC [WIDTH-1:0] = ~r_crc[WIDTH-1:0] ;
    assign O = ~r_crc[0];
    reg r_match = 0;
    assign MATCH = (SEQUENTIAL_MATCHING) ? r_match
    : (WIDTH == 5) ? ( ~r_crc == 5'b11001 )
    : (WIDTH == 16) ? ( ~r_crc == 16'h4FFE )
    : 0;

    always @(negedge CLK)
    begin
    if ( SEL == 2'b11 ) // RST
    begin
    r_crc <= (-1);
    r_match <= 1;
    end
    else if ( SEL == 2'b10 ) // Extract bitstream
    begin
    r_match <= (I ^ r_crc[0]) & r_match;
    r_crc <= { 1'b0, r_crc[WIDTH-1:1] };
    end
    else if ( SEL == 2'b01 ) // Calc.
    if (WIDTH == 16)
    r_crc <= { I2 , r_crc[15]
    , ( r_crc[14] ^ I2) , r_crc[13:2]
    , ( r_crc[ 1] ^ I2) } ;
    else if (WIDTH == 5)
    r_crc <= { I2, r_crc[4]
    , ( r_crc[ 3] ^ I2), r_crc[2:1] };
    end
    endmodule

    実装はこれ。短いとみると長いとみるか...

    これを 送受信に組み込むことは成功した。送信・受信でモジュールを兼用。-- わずか 3 スライスほどだが、節約できた。合計の規模は、110 になった。ただし、FIFO のサイズを 32 バイト にしたときの話。これで FIFO のサイズを 64 バイトにするのは無理になった。

    実は、ピンの数の方が切実。JTAGENB を使うようにすれば、21 pin 使えるのだが、既に 19 pin 使っている。受信では CRC エラーの出力でさらに 1pin 増えた。送信では、CRC を生成するかどうかの 設定を 2 種類入れたかったのだが、無理なので自動で 生成することにした。

      CRC-5 は、計算結果を入れるかどうかの判断をするタイミング(3バイト目 残り 5bit)で、次のデータがあるかどうか を見て なければ、入れる。そこできちんと終わらせないといけない。次のデータがあれば CRC-16 を入れることにする。( データを受け取り終わったら CRC-16 の 2 バイトが挿入される。)

    こういうロジックを入れて 110 スライスなのだ。CRC をサポートせず生のデータを送信・受信するなら 79 スライスになった。31 スライスも使っているが、CRC 自体は小さい。制御が大変なのだ。

    さて、入れたいメカニズムは入ったのだが ... 入出力をどうするのか まだ。
    SPI はひとつの案だが、詳細は詰めないと。それに今の 入出力 。これも一般的な仕様にする必要がある。

    ところで、48 MHz の件、 『クリスタルオシレータ(48MHz) SG-8002DC(3.3V)』 -- PLL なしだとこれを使うしかないようだ。

入出力のハンドシェーク

    これまで、全部が CLK に同期して動いていた。だが、別クロック系統のものと接続するには、具合が悪い。パラレルで MCU と接続する場合も今のままではダメだし、SPI と接続する場合も同様。

    まずは、パラレルのところの整備から始める。

    // ______________________ _______
    // DATA <______________________><_______ // // _______ _____ // RDY ________| |______________| // // ________ // ACK _____________| |________________ // // // CLK | | | | | //

    今考えているのを図にしてみた。
     1. ACK が 0 のとき DATA をセットできる。セットしたら RDY を 0 にする。
     2. DATA が確定したので、RDY を 1 にする。
     3. 受け取る方は、受信後、ACK を 1 にする。
    4. ACK が 1 になったら、RDY を 0 にする。
     5. 受け取る方は、RDY が 0 になったら ACK を 0 にする。
     最短 3 クロック。

    このルールにしたがうようにまず変更した。規模は 110 で変わらない。

    さて、BUS を使うなら、一般的なデバイス -- RD/WR で制御できるもの と同じようにしておきたい。ついでにコントロールレジスタも付けて ピンも減らしたい。

    RDについては、

    assign RECV_DATA_ACK = ~RD;

    で制御できる。データがあれば、ACK の前後で 安定している。なければ、前のデータが 読めるだけ。RD が H になった後 次のデータがセットされるので、読み込みサイクルが早すぎるとマズイ。なんなら FIFO のクロックを 4倍速にしたり マスター側に合わせて良い。それが出来るように ハンドシェークを見直したのだ。

    RECV_DATA_RDY は、BUS に接続するなら コントロールレジスタから読めると便利。だが、AVR などでポート制御するなら、ピンに出しておいた方が良い。

    assign DB[7:0] = (~RD & ~RS) ? RECV_DATA[7:0]
    : (~RD & RS) ? CTRL_REG[7:0]
    : 8'bz;
    assign SEND_DATA[7:0] = DB[7:0];

    出力制御は、こんな風にする。

    次に WRのほう。

    assign SEND_DATA_RDY = ~WR;

    WR を L にしたときデータが安定しているという条件なら、これでも良い。ACK は無視。勝手に L→ H→ L になる。不安なら、WR を遅延させるのも良いかも知れない。

    ここまで作ったところ 115 スライスになった。わずかな増加だが、これを元に SPI を付けるのだ。残り はわずか 13 スライス。

SPI インターフェイス

    これで、 SCK が CLK より早かろうが遅かろうが問題なくなった。いよいよ最後の機能として SPI を付ける。

    module udrv_spi (
    input MOSI
    , input SCK
    , input CS
    , output MISO

    , input [7:0] SPI_DATA_I
    , output SPI_DATA_I_ACK
    , input SPI_DATA_I_RDY

    , output [7:0] SPI_DATA
    , output SPI_DATA_RDY
    , input SPI_DATA_ACK
    );

    モジュール単体は、こう。SPI を パラレルに変換しているだけ。CLK もない。
    続いて本体

    reg [3:0] r_count = 0;
    reg [7:0] r_spi;
    reg [7:0] spi_out;
    reg r_mosi = 1'b0;

    reg o_data_ack = 1'b0;
    reg o_data_set = 1'b0;
    reg o_data_rdy = 1'b0;
    assign SPI_DATA_RDY = o_data_rdy;

    reg i_data_ack = 1'b0;
    assign SPI_DATA_I_ACK = i_data_ack;

    assign SPI_DATA = spi_out[7:0];
    assign MISO = r_spi[7];

    always @(posedge SCK)
    begin
    r_mosi <= MOSI;
    if (r_count == 1)
    i_data_ack <= 1'b1;
    else if (~SPI_DATA_I_RDY)
    i_data_ack <= 1'b0;
    end

    always @(CS , negedge SCK)
    begin
    if (CS) // LOAD
    begin
    r_count <= (-1);
    r_spi <= SPI_DATA_I;
    end
    else if (r_count == 7) // LOAD
    begin
    r_count <= 0;
    r_spi <= SPI_DATA_I;
    spi_out <= {r_spi[6:0], r_mosi };
    end
    else // SHIFT
    begin
    r_count <= r_count + 1;
    r_spi <= {r_spi[6:0], r_mosi };
    end

    o_data_ack <= SPI_DATA_ACK;
    o_data_set <= (~CS & (r_count == 7));
    if (o_data_set)
    o_data_rdy <= 1'b1;
    else if (~o_data_ack & SPI_DATA_ACK)
    o_data_rdy <= 1'b0;
    end
    endmodule

    悩ましいのは、動作のタイミングが足りないところ。

      通常、最後のタイミングは、negedge SCK で (r_count == 7) 。ここでデータ自体は受け取れる。o_data_set を 1 にすることも出来る。だが、o_data_rdy を 1 に出来ない。
      CS の変化も条件に入れてようやく 1 にできる。ただし、0 には出来ない。

      これでもシミュレータでは動く。だが、always @(CS , negedge SCK) という記述を期待通りにやってくれるかどうか? 不安だったりする。前に Xilinx で試したときは、ダメだったような ...

    それも問題だが、上位レイヤーでどう使うか?

    コントロールレジスタをサポートしたいのだが、どういうプロトコルにするか ... RS が邪魔なのだ。まぁ、余裕もなさそうだから、ピンから入力することにしよう。

    送信の場合、RS=0 にして、パケット分のデータを 送り、RS=1 にして、コントロールレジスタに書き込むことで、SEND_START を 1 にする 。FIFO が空になれば、一定時間後に 送信完了。

    受信が問題。通常は受信モード。FIFO が空でなければ、受信中。一定時間後に 受信は完了する。... なんだか苦しいが、ピンもロジックも足りない。内部ロジックには、送信中、受信中というのがあるので、コントロールレジスタをポーリングすれば、分かるようにしておこう。

    こんなところか。で、これを実際に作りこむと .. 124 スライスにまでなった。

      128 スライスなど小さいと思っていたのだが、udrv.v は、なんと 859 行もある。コメントはそれなりにはあるが、コード自体に冗長な部分はない。メモリを使わずにコードだけで埋めるのは結構しんどい。



    シミュレータで、送信側を確認。

      シミュレータでは、negedge CS , posedge CS が動作ポイントに入るのは確認できた。
      r_count は、4bit 。最初に negedge CS で動いたときに 7 にするのは まずい。F にしている。
      negedge CS で F それから 0 1 ... 7 でデータ出力。最後は、negedge SCK で データ出力できている。posedge CS でかろうじて o_data_rdy を 1 。これでデータは受け取ってもらえる。ACK は半端な状態だが、次に送信するときに、 続きから処理される。

      o_data_rdy を 1 クロック早めるのは、データを確実に渡せない気がする。ただ、FIFO 側で 遅延して受け取るような変更なら出来る。決めたルールに例外を持ち込むことになるが、このようなケースなら止むを得ないか。

      追記: 間違っていた。


      always @(CS , negedge SCK)
      begin
      if (CS) // LOAD
      begin
      r_count <= (-1);
      r_spi <= SPI_DATA_I;

      この部分なのだが、先頭の negedge CS で条件が成立していない。
      受信の動作確認で分かった。

      reg r_cs = 0;
      always @(CS or negedge SCK)
      begin
      r_cs <= CS;
      if (r_cs) // LOAD
      begin
      r_count <= 0;
      r_spi <= SPI_DATA_I;

      こう変えたら動いたのだが、どうも釈然としない。 negedge SCK だけの場合は、変更前の SCK が見えたのに、こう書くと変更後?



      受信しているところ。送信側も SPI だから両方ちゃんと動いている。

      FIFO に遅延読み込みのパラメータを付け o_data_rdy を 1 SCK クロック 前にした。

    入れたい機能は入れた。QFN32 の 128 スライスという条件をほぼフルに使った。もう満足だったりする。随分と勉強にはなったが、この後どうしよう?

    Full-Speed/Low-Speed の切り替えを付けたい。あと差動入力を使うオプション。だが、これ以上のロジック追加は無理だろう。今回は、MachXO2-256 でどこまで出来るか? というテーマなので、いずれ別の機会にしよう。

      思い出したので、メモ。
      FIFO は、Dual-Port の RAM を使っている。いま想定している使い方だとパケットを受け取り終わってから、READ することになりそう。送信の場合も同じ。なら Single-Port でも良さそうな気がする。制御ロジックが似た様なものなら 2 倍の容量が使える。FIFO まわりを差し替えるだけで済むなら検討してみたい。
       ... やってみたが、却って規模が増えた。.. 失敗。

      あと FIFO は、同期 FIFO を非同期で使えるようにしている。非同期 FIFO というものもある。それと差し替えるとどうなるか試してみたい。 (参考: 『FPGAの部屋:同期FIFOと非同期FIFO』)

    残っているものとしては、JTAG 通信と ADC 。DAC は一応 設計してみたのだから、ADC もその程度のものはやりたい。

検討もれ

    ひといきつきたいのだが、検討もれがあるかないかが気になっている。マズそうなものをリストアップしていこう。ただし、すぐには直さないかも。

    (EOP だけのパケット)

      Low-Speed では、これがあるそうだ。

      まずこれを送るインターフェイスがない。送信では、SYNC が自動で付加される。0 バイトを送出することもできない。最短は、SYNC - PID - EOP 。

      受信でも検出できない。だけでなく、マズイケースがありそうだ。受信中にまで行くと EOP でともかく状態を抜けるのだが、少々不安。あと、受信は完了したが、0 バイトというインターフェイスがいる。

      (タイムアウト)
      反応が遅すぎてタイムアウトになる可能性がある。 3ms バスが アイドル状態だとサスペンドだそうだ。
      心配だったのだが、AVR の USB だと ACK/NAK/STALL を返すのは、上位レイヤーだったことを思い出した。... たぶん大丈夫ではないかと思う。

      ただちょっと確認。1 ms というのは、1.5 MHz クロックだとして 1500 クロック。8 バイトのパケットは、SYNC , PID , CRC を含まないそうだから 1(SOP) + 14 x 8 + 3(EOP) の 96 クロックが 最短。Low-Speed だとしても 1ms に対して 十分に短い 感じ。反応が悪いと性能が出ない。

      ところで、FIFO は 32 バイトだが、PID + データ を収めないといけない。だが 32 バイトのパケットとは、データのサイズが 32 バイトのようだ。なら収まらない。これもなんとか対処する方法を考えないと。
      幸い、受信部分と FIFO の間には、1 バイトのバッファがある。最後のデータを待たせればなんとか。送信も SPI だとバッファがある。

追記:使い方編

    使い方を考えながら、インターフェイスを見なおした。

    まずは、BUS の信号

    // _ ____________________________________ _____
    // RS _><____________________________________><_____
    //
    // _____ _______
    // nCS |________________________________|
    //
    // ______________ _____________
    // nWR/nRD |_____________|
    //
    // _______________
    // DB[7:0] --------------<_______________>----------
    //

    LCD とかのタイミングを参考にするとこんな感じか。信号名も nWR とか nXX にしてみた。

    READ は、これで問題ない。WRITE はどうしたものか。どうも posedge nWR でデータ採取しているみたいに見えるのだが ... ここは negedge nWR から遅延させて採取にしたい。

    SPI は、普通のタイミング ... だが 、最後のデータは、CS を H にしたとき 、上位に 受け取られる。

    とりあえずは、これでいこうと思っている。

      ところで、FIFO 付きの BUS/SPI コードを作ったことになる。DAC で欲しかったものだ。DAC もこれをくっつけようと思う。

    さて、どう使うかまとめてみた。

    ステータスレジスタ

      bit7: CRC_ERROR
      bit6: DATA_FULL
      bit5: DATA_EMPTY
      bit4: DATA_RDY
      bit3: RECV_EN (受信中)
      bit2: SEND_EN (送信中)
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      内部の状態をステータスレジスタとして見せる。この内いくつかは ピンにも出力している。

      対応するピン出力( 負論理 )
      nCRC_ERROR
      nDATA_EMPTY
      nDATA_FULL
      nDATA_RDY
      nBUSY ( ~r_start )

    信号の説明

      r_recv_mode (送受信モード )

       状態は、0: 送信モード か 1: 受信モードのどちらかに大別される。
       どちらでもない状態は存在しない。

      r_start/BUSY (送受信スタート)

       1 にすると 送信モードでは送信要求、受信モードでは 受信要求。
       送信が完了すると、続いて受信スタートになる。(r_recv_mode = 1)
      受信が完了 すると 0 になる。

      CRC_ERROR :
       受信完了後有効で、1 になると CRC_ERROR が起きたことを示す。

      DATA_FULL :
       1 で、FIFO が溢れたことを示す。(データロスト)
       FIFO をリセットするまで有効

      DATA_EMPTY :
      1 で FIFO が空であることを示す。データが入れば 0 になる。

      DATA_RDY :
      1 で 受信データがあることを示す。
       ハンドシェーク用で、DATA_EMPTY とは変化のタイミングが異なる。

      ※) FIFO は、受信モード→送信モードに切り替えたときと、受信中になったタイミングでリセットされる。

      ※) ステータスレジスタの読み込み:
      (SPI) RS=1 で 0x00 を書き込む。
      (BUS) RS=1 で読み込む

    コントロールレジスタ

      bit7: 書き込み要求
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      動作をさせる信号は、コントロールレジスタとした。書き込み要求は、SPI のみで必須だが、インターフェイスを合わせるために、BUS でも必須ということにしておく。

      ※) コントロールレジスタの書き込み:
       1)送信 モード
      (SPI) RS=1 で 0x80 を書き込む。
      (BUS) RS=1 で 0x80 を書き込む。
      2)送信スタート
      (SPI) RS=1 で 0x81 を書き込む。
      (BUS) RS=1 で 0x81 を書き込む。
       3) 受信スタート
      (SPI) RS=1 で 0x83 を書き込む。
      (BUS) RS=1 で 0x83 を書き込む。

      サポートするのは上記の 3 つ。

    送信方法

       1) 送信 モードに する
       2) PID+データを書き込む。(合計 31 バイトまで)
       3) 送信 スタート にする
       4) (タイミングを見て) 追加のデータを書き込む。

      やはり、規模的に これが限界。最大パケットサイズを 32 バイトにすれば、追加のデータは、高々 2 バイト。すぐに送信が始まることは保証できるので、ステータスを見なくても タイミングは計れるはず。空になる前に送り込めるだろう。



      シミュレータで確認したが、こんな感じ。



      続いて送信されるわけだが、FIFO が空になると CRC を付けて 送信完了。CRC-5 を使う場合も自動で元データと CRC を入れ替える。

      ※ シミュレータ結果で i_recv_mode が 1 になっているが、過渡のものなので気にしないで欲しい。

    受信方法

       1) 受信 スタート にする



      データが来ると nDATA_RDY が 0 になるので データを受け取る。nBUSY が 0 になった後は、新たなデータは来ない。 nBUSY = 0 かつ nDATA_EMPTY = 0 を確認して終わる。

      終わるときに、nDATA_FULL = 0 か nCRC_ERROR = 0 なら、エラーにする。



      FIFO が溢れなければ良いので、高々 2 バイト余分に受け取ることは可能だろう。

        nDATA_RDY=0 で割り込みをかけるとする。ここから 12 MHz で 8 x 31 クロックもある。この間に SPI で 2 バイト 受け取れば溢れない。AVR なら問題なさそうに思える。

    以上だが、パケットの間隔というのが気になる。送信→受信、受信→受信で、データを受け取り損ねるとマズイ。特に受信→受信。このケースには、SETUP や OUT がある。

    まずいなら、仕様を変更しないとならない。... どうもその必要がありそうだ。

    ソースコードは、qfn32samples-11.zip の予定(準備中)。

追記: ソースコードのブラッシュアップとバグ修正

    仕様を変更するにも、まずは規模を減らさないといけない。無駄なところがないかチェックしてみた。

    そうしたら、... 送信での CRC 生成がエンバグしていた。
    後で use_i16 , use_i5 という レジスタを作ったのだが、全く不要なばかりか、バグの原因になっていた。

    次、CRC のモジュールは 送信・受信で共有している。特に CRC-5 は、配線の切り替えの方が重いような気がしたので、2 つに分けてみた .... これは 1 スライスの増加に終わった。残念。

    次、受信での CRC の制御。こっちもバグっていた。まず、CRC-5 か CRC-16 のどちらの結果を取るか示す MATCH_SEL というレジスタがあるのだが、完了でリセットされる。これではまずいので 状態を残すように修正。

    次、MATCH_SEL 自体を良くみたら 受信 4 バイト以上という条件だったので、data_bytes の bit を 1 つ増やして data_bytes[2] に変更。

    次、FIFO リセットの条件を 受信モード・送信モードを切り替えたときに変更。受信→受信に対応するベースにする。FIFO_FULL は、受信モードでずっと残るが、その方が都合が良いようにも思える。その上で、受信モードは常に受信待ちということにした。r_start は、1つめを受信したときに 0 になる。完了が必要ならば、r_startを再度 1 にする。

    さて、 送信→受信のパターン。DATA0/DATA1 を送ったあとの ACK が相当するようだ。タイミングによって ACK を取りこぼしてしまう。やはり送信後すかさず受信モードにすることにした。r_start は、0 になるので、次の受信の完了は検出できない。あと、送信時の FIFO_FULL も検出できない。

    ここまでやった。そして ... なんとか 128 スライスに収まった。

まだ続く

    もう FIX したいのだが、まだ手直しするところが ...

    どれぐらいのクロックで動くのかの確認をするついでに、DCMA モジュールというのを入れてみた。これは、クロックセレクタで、ここを通すとグローバルクロックにつなげられる。で、やってみると DCMA に入力できない。... ピン配置を変えると OK みたいなので、ピン配置を見なおした。

    ついでに RS ... データとコントロールレジスタの選択だが、一般に RS=0 が コントロールレジスタみたいなので論理を逆転。

    あと見直していたら、CRC-5 を 2 個使うようになっていた。これで問題なさそうなので 2 個使う USE_DUAL_CRC5
    を標準にした。

    ついでに SUPPORT_SEND_EOP というのを作りかけていたのだが、ヤメ。コード削除。

    で、動作周波数の確認。... ボトルネックを見ていたら、r_recv_mode1 が引っかかった。1 クロック遅延させるつもりが、半クロックになっていたので変更。さらに FIFO のクロックを CLK_4X にしていたのだが、SPI では必要ないので通常の CLK に。

    最終的に CLK_4X は、67.7 MHz になった。本来 48 MHz だが、これぐらい余裕があれば安心。 規模は 127 スライスでぎりぎり OK。

    あと、CLK_OUT を付けた。CLK_4X の反転出力。水晶があれば、発振させられるかも。ソースコードは、-11 を上書き。

ソフト側の構想

    V-USB を改造すればいけるんじゃないかと上で書いたが、少し確認してみよう。本格的にやる場合は別記事にするが、とりあえず、あたりを付けよう。

    extern usbRxBuf, usbDeviceAddr, usbNewDeviceAddr, usbInputBufOffset
    extern usbCurrentTok, usbRxLen, usbRxToken, usbTxLen
    extern usbTxBuf, usbTxStatus1, usbTxStatus3
    extern usbSofCount

    ; extern unsigned usbCrc16(unsigned char *argPtr, unsigned char argLen);
    ; extern unsigned usbCrc16Append(unsigned char *data, unsigned char len);
    ; extern unsigned usbMeasurePacketLength(void);

    アセンブラで extern を grep すれば、こういうのが見つかる。
    下の 3 つは、アセンブラから呼び出すサブルーチンで無視して良い。CRC などは機能にあるし、usbMeasurePacketLength というのも NRZI 関係だろう。

    さて、上は全部変数。この変数を制御しているのが、usbdrv.c 。同じインターフェイスにすれば、わずかな変更でいけそうだ。たぶんアセンブラは INT0 割り込みで動作する。udrv では、なにか受信すると DATA_RDY が L になる。この変化を INT0(か 1) で拾えば同じような処理ができるはず。

    受信は割り込みベース、送信はポーリングでも良いかも。12 MHz でデータが来るが、16 バイト受信ぐらいまでに応答するとすれば、10 us 以内。20 MHz なら 200 クロックもある。これなら受信ルーチンも C で良いかも知れない。

    memo : 受信側

    uchar usbRxBuf[2*USB_BUFSIZE];
    uchar usbInputBufOffset;
    volatile schar usbRxLen;
    uchar usbCurrentTok;
    uchar usbRxToken;

    このあたりが受信で重要そう。

    len = usbRxLen - 3;
    if(len >= 0){
    usbProcessRx(usbRxBuf + USB_BUFSIZE + 1 - usbInputBufOffset, len);

    こういうコードがある。usbRxLen には、先頭の PID と 最後の CRC-16 が含まれるから -3 する。
    PID の場所は、usbRxBuf + USB_BUFSIZE - usbInputBufOffset ということらしい。

    CRC-16があるということは、DATA0/1 パケット。その前に SETUP か OUT が来ているはずで、それが、その前に入っているのかも。この udrv でも同じ仕様が都合が良さそう。

    usbRxToken は、OUT or SETUP (の PID)が入っている。usbCurrentTok は、OUT だった場合に エンドポイント番号が格納される。

    あと、受信では 1 フレームの送受信を一気にやってしまうようだ。IN が来たら予め用意されたデータを送る。OUT or SETUP では、ACK などの応答を返してしまう。

なんと、まだ続く

    V-USB のコードを眺めていたら CRC-16 は、DATA0/1 パケットに使うことが分かった。サイズは関係ない -- 誤解してた。あと、CRC-5 の送信側のコードがない。.... デバイスは 指示パケットを送信しないのであった。

    仕様を誤解していたのだから、まずは修正。CRC-5 の送信側のフラグは、crc5_extract というものなのだが、ややこしいので、整理したら規模が少し減った。さらにデバイス専用にも出来るようにして、少し規模を削れるように parameter を導入したところ、論理が変わらないのに規模が減った。今は 123 スライス。デバイス専用だと 121 スライス。

    ついでなので、0 バイト送信で SOP のみを送る機能は、Low-speed のみなので FS パラメータ(1 で Full-speed) に連動するように変更。さらに DP , SE0 状態を negedge CLK でサンプリングするよう変更。こうしておかないと 受信毎にクロックを 180°ずらすことになる。ソースコードは、-12 版準備中。

    最終的な仕様を記録しないとわけが分からなくなりそうだ。新記事を起こそう。

関連記事

     ・ 『QFN32の FPGA
     ・ 『TTL ALU 74281
     ・ 『FPGA時計の設計
     ・ 『MCPU -- A Minimal 8 Bit CPU
     ・ 『DACを設計してみよう
     ・ 『USBコントローラの設計

    最新ソースコード (2012/07/1)
     ・ qfn32samples-10.zip

      qfn32samples-10.zip で一応の完成とした。最終的な規模を記録しておこう。GSR は、1 つしかないリソースで、SPI で使った非同期リセットで 使われたらしい。

      BUS SPI
      Number of registers: 117 126
      PFU registers: 115 123
      PIO registers: 2 4
      Number of SLICEs: 116 122 /128
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 84 90 /96
      As RAM: 12 12 /96
      As Logic/ROM: 72 78 /96
      Number of logic LUT4s: 200 214
      Number of distributed RAM: 12 12
      Total number of LUT4s: 224 238
      Number of PIO sites used: 20 14 /22
      Number of GSRs: 0 1 /1

    最新ソースコード (2012/07/05)
     ・ qfn32samples-11.zip

    最新ソースコード (2012/07/06)
     ・ qfn32samples-12.zip

      今度こそ.. ちなみに、実際に動かそうと思えるレベルかどうかが、『一応の完成』の基準。(動かせばバグは出るだろう)
      規模を記録しておく。僅かながら変更する余裕ができた。SUPPORT_CRC5S=0 とすると、送信時 CRC-5 は生成しない(device では不要) が、-2 スライスになる。

      BUS SPI (CRC 分)
      Number of registers: 123 148 45
      PFU registers: 121 145 45
      PIO registers: 2 3
      Number of SLICEs: 112 123 /128 27
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 80 91 /96 33
      As RAM: 12 12 /96
      As Logic/ROM: 68 79 /96
      Number of logic LUT4s: 197 218 64
      Number of distributed RAM: 12 12
      Total number of LUT4s: 221 242 64
      Number of PIO sites used: 21 14 /22
      Number of GSRs: 0 1 /1


参考文献(データシート等)
 ・ 『MachXO2 ファミリデータシート(日本語 pdf)
 ・ 『MachXO2 テクニカルノート(日本語)
 ・ 『Lattice Diamond 1.4マニュアル
posted by すz at 00:24| Comment(0) | TrackBack(0) | CPLD

2012年06月21日

DACを設計してみよう

QFN32 パッケージの MachXO2-256 という FPGA でなにが出来るのか、検討しているのだが。... DAC が作れそうな気がする。FPGA は大概、コア用の電源と IO 用の電源に分かれている。この QFN32 も 例外ではないどころか、VCCIO が 4 つもある。ちゃんと電源を設計すれば、ノイズ的に有利かも知れない。ただ、オーディオDAC に匹敵するようなものには、できないだろう。どこまでのものが作れるかも追求しない。256 に入れられて 検証できるものを目指す。

AVR など PWM を持った MCU は 多いわけだが、ΣΔ変調の DAC を持っているものは、殆ど無い。同じ bit幅でも、ΣΔ変調だと ノイズを減らせるそうだ。ちょっと試してみたい。

ΣΔ変調の原理

    1 bit の ON/OFF だけで DAC を作ると 256 クロック使って ようやく 8bit 幅になる。このことは、PWM と変わらない。ノイズを減らせるのは、どうしてかというと PWM では 0 が続いたあと 1 にするだけのところを、1 を分散させて ノイズの周波数を 上げるためらしい。

    どうやって そういう 0/1 を作るのだろう?

    なかなか理解できなかったのだが、3bit の DAC を使って 8bit 出力することを考えてみたら、分かったような気がする。

    例えば、8'b01011010 を 3bit の DAC で出力するには ... 上位 3bit の 3'b010 をまず出力して 時々 3'b011 も出力することで 中間値を表現する。

    3'b010 を DACで出力すれば 5'b11010 が出力されなかった分として残る。次も 3'b010 なら 5'b11010 + 5'b11010 が残るのだが ... 桁上がりして、6'b110100 になる。だから 3'b011 を DAC で出力して、5'b10100を出力されなかった分として 覚え直す。

    要するに、ずっと 5'b11010 を加算していって、桁上がりしたときに 3'b011 を DACに出力すれば良いのだ。

    では、0/1 だけで表現するには ... 8'b01011010 を ずっと 加算していって、桁上がりしたときに 1 を出力すれば良い。256 回加算すれば、16'b 01011010 00000000 になるから、確かに 8'b01011010 個の 1 を出力したことになる。

    フィルタがどうとか に囚われてよく分からなかったのだが、どうも原理は簡単な話のようだ。

作ってみよう

    module dac # (
    parameter WIDTH = 8
    , parameter WITH_LATCH = 0
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-1:0] fraction = 0;
    reg [WIDTH-1:0] count = 0;
    reg r_out = 1'b0;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    fraction <= I_DATA;
    r_out <= 0;
    if (WITH_LATCH)
    r_data <= I_DATA;
    end
    else
    begin
    { r_out, fraction } <= { 1'b0 ,fraction }
    + { 1'b0, (WITH_LATCH ? r_data : I_DATA) };
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    取り敢えず書いてみたのがこれ。ラッチあり/なし を選べたり ビット幅を指定できるようにしてみた。そうそう、新しい呪文を覚えた。&count は、count の各ビットを AND する意味で all 1 のとき 1 になる。

    書いてみて思ったのだが、毎回 Nbit の加算をしている。これはクロックを上げられないのではないか?

    例えば 16 bit の出力 を 50kHz でするとする。65536 倍のクロック が必要で 計算すると 3.2768 GHz で駆動することに。とりあえず 3bit の DAC を使うとすれば ... 409.6 MHz 。これでも無理な話だが、周波数が減る上に 加算する bit 数も減る。

    さて、この 3bit の DAC もまた、ΣΔで作ってやれば、どうなるのだろう? 同じ 1bit のΣΔでも最大周波数が上がるのではないだろうか?

N bit ΣΔ

    module dac5 # (
    parameter AWIDTH = 5
    , parameter WIDTH = 16
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output [AWIDTH-1:0] A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-AWIDTH-1:0] fraction = 0;
    reg [WIDTH-AWIDTH-1:0] count = 0;
    reg [AWIDTH-1:0] r_out = 0;

    wire [WIDTH-AWIDTH-1:0] WK = 0;
    wire [AWIDTH-1:0] A_ZERO = 0;
    wire [AWIDTH-1:0] A_WK = ~A_ZERO -1;
    wire [WIDTH-1:0] I_MAX = { A_WK , ~WK };
    wire [WIDTH-1:0] I_DATA2 = (&I_DATA[WIDTH-1:WIDTH-AWIDTH])
    ? I_DATA : I_MAX;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    r_data <= I_DATA2;
    fraction <= I_DATA2[WIDTH-AWIDTH-1:0];
    r_out <= I_DATA2[WIDTH-1:WIDTH-AWIDTH];
    end
    else
    begin
    { r_out, fraction } <= { A_ZERO , fraction } + r_data;
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    例えば 3bit DAC では、3'b111 より大きな値は、出力できないので 上限値を設けてやる。後は最初に説明したとおり。

    すなおに、N bit の R-2R ラダー DAC を使っても良いのだが、これと 1bit ΣΔを組み合わせてみたい。

    module dacx # (
    parameter WIDTH = 8
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg [2:0] hi_clk;
    wire [2:0] hi_out;
    wire lo_stb = CLK;

    always @(negedge CLK)
    begin
    hi_clk <= hi_clk + 1;
    end

    dac #( .WIDTH(3) , .WITH_LATCH(0) ) dac_lo ( .CLK(CLK), .STB(lo_stb)
    , .I_DATA(hi_out), .A_OUT(A_OUT) );
    dac5 #( .AWIDTH(3) , .WIDTH(WIDTH-3) ) dac_hi ( .CLK(hi_clk[2])
    , .STB(STB), .I_DATA(I_DATA) , .A_OUT(hi_out) );

    endmodule

    こうかな?

    ところで、ラッチなしにして、2 ** N 回クロックを待たないで、適時値を変更しても、それなりに追従する。dac5 の方もラッチなしに対応しておいた方が応用が効きそうだ。

    あとは、フィルタがどうとか ... だが、クロックはたっぷりある。簡単なものなら加算だけで作れそうな気がする。これはまた別途考えてみたい。

    それとは別に、データをどうやって受け取るかという問題もある。パラレルは無理だしなにより面倒。やっぱり SPI ? 実は、JTAG 経由の通信というのも検討中。

R-2R ラダー DAC

    簡単に説明すると

    A_OUT_2 --- R ---+----- Analog
    |
    2R
    |
    A_OUT_1 --- R ---+
    |
    2R
    |
    A_OUT_0 --- R ---+
    |
    2R
    |
    GND

    こんな回路。抵抗が 2 種類しかいらないが、値が厳密。あと電流は取れないから、オペアンプを使ってボルテージ・フォロワを組む。

      FPGA 出力には、(数十Ωの)抵抗分がある。これにより R と 2R の比が崩れるので注意。また、出力電流を 4段階ぐらいで調整できる(たぶん 抵抗分が変わる)ので、チューニングに使えるかも。

    秋月で 1608 をリール売りしているから、1K/2K , 1.2K/2.4K , 1.5K/3K とかの組みを選ぶと良さそう。2500 個もあるから、選別しほうだい。

    選別には、SMDテスターが便利そう。3000 カウントだから 3K は避けたほうが良いか。

動作周波数について

    2 つの モジュールにすることで、周波数を上げられないか? やってみたのだが、4bit でも 16bit でも 150MHz ぐらいで、あまり変わらない。2 つの モジュールにしても、やっぱり あまり変わらない。要するに CLK 自体が ボトルネック。150MHz ということは、12bit で 36.6 kHz が上限。3bit の R-2R DAC + 10 bit ΣΔあたりが妥当?

    ところで、100 MHz で 駆動したとして 例えば 010101 というパターンが出てきたとき素直に駆動して良いものなのだろうか? 1bit ならともかく、多ビットだと 周波数を落としたくなる。

    PWM だと 周期が低くなりすぎるし ... PWM + ΣΔ が良いのだろうか? PWM でも Phase Correct MODE というのが AVR にある。UP していって TOP まで行ったら 逆に DOWN していく。こうすると スイッチング周波数は、普通の PWM の 1/2 。例えば 4bit Phase Correct MODE PWM なら CLK が 100 MHz でも スイッチング周波数は、3 MHz ぐらいまで落とせる。ちょっとこれを検討してみたい。2 つの モジュール は、既に出来ているから、片方を PWM にするだけ -- 難しいことはない。

    3bit の R-2R DAC で PWM を使い 、さらに上位を ΣΔにするのは、ちょっと難しい。これも 挑戦してみよう。

    PWM を使うのとは別に 010101 というパターンを 例えば 000111 というのに変換してしまうのはどうだろう?

    考えたのは、次のやりかた。

      今出力しているのが 1 だとする。次に出力するのが、01 だと分かっているなら 10 にしてしまうのだ。(0 についても同様)

      1 01 → 1 10
      0 10 → 0 01

      要するに 単独の 1/0 を出ないようにしてしまうわけだから 絶対に 2bit は続くわけだ。3 bit に拡張するなら、上記に加えて

      1 001 → 1 100
      0 110 → 0 011

      とする。これで 3 つ以上連続することが保証される。

      ただ、この処理には問題がある。例えば 2 を出力する場合 、わざわざ離れた位置に 1 を配置するわけだが、これを くっつけてしまう。連続した数を数えておいて 、くっつける処理をキャンセルする必要がある。

      なにやら面倒な話になってきた。やはり PWM の方が良さそうな。

SPI 通信の例

    秋月で売っている DAC MCP4922 や ADC の MCP3002 は、SPI で通信する。プロトコルを調べてみた。

    DAC :
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 16 bit 単位

    bit15 ~ A/B チャネル選択
    bit14:12 config
    bit11:0 データ[11:0]

    ADC:
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 24 bit 単位

    bit23 START (1)
    bit22:20 config (チャネル含む)
    (ADC → Master)
    bit19 NULL (0)
    bit18:9 データ[9:0]
    bit8:0 データ[1:9]

     ・ START を 遅らせて 16 bit 単位 にすることも可能

    bit15 (0)
    bit14 START (1)
    bit13:11 config
    (ADC → Master)
    bit10 NULL (0)
    bit9:0 データ[9:0]

    CS を 上げ下げする必要があるのは、FPGA 側としては、都合が良いかも。SPI というからには、MSB first にした方が自然。バイト単位になるような配慮も必要。変換タイミングは、正確にしたいだろうから 別途ということになりそう。(AVR だと PWM を使う)

    もっと大きな問題があった。クロックをどうしよう。変換タイミングを完全に正確にするには、送信側から CLK をもらうか、DAC 側のタイミングに 合わせてもらうか ...

    送信側から CLK をもらうにしても 50 MHz 〜 100 MHz というのは厳しい。規模が大きい FPGA だと PLL があるが、MachXO2-256 にはない。

    今回のは、DAC 側のタイミングに 合わせてもらうことにしよう。それにしても 問題が。AVR を使うならなんとかなるのだろうが、PC と通信する場合は、どうしよう。

    普通にシリアルで CTS/RTS 制御で良いのかな? 115Kbps だと 11.5 Kbytes/sec になってしまって 性能が足りない。1M bps とか できたら 3M bps とか欲しいところだが ... 途切れなく送り込めるのかどうかが問題。

    DAC を設計するなら MachXO2-1200 ぐらいは 欲しいところで、PC とのインターフェイスに FT2232H を使いたいということになりそうだ。 そういうものとして、『MachXO2 Breakout ボード』があるのだが ... 今回は 256 を使いこなすのがテーマなのでパス。とにかく AVR で使えるものだけを考えよう。

    ここまでの検討で
     ・ SPI を使う。
     ・ データリクエストを DAC 側が出す。
     ・ 16bit 単位
     ・ DAC データは、12bit-14bit の範囲
    ということに決めた。

    プロトコルは、別途。

ソースコード

関連記事
posted by すz at 22:55| Comment(0) | TrackBack(0) | CPLD

2012年06月17日

MCPU -- A Minimal 8 Bit CPU

MCPU -- A Minimal 8 Bit CPU in a 32 Macrocell CPLD

    というのを思い出した。これは、CPLD で実装できる 極端にシンプルな 8bit CPU 。

    実際に CPLD に実装しても メモリを別に用意しなくてはならない 。FPGA だとメモリがあるのが普通なので、全部を実装可能だ。でも、どうせならもっと規模が大きい CPU を実装したい ... ということで今までは興味がなかった。

    128スライスしかない MachXO2-256 ならどうだろう? メモリを載せて実装できるのだろうか? 試してみたくなった。

    まずは、資料の入手から。"MCPU - A Minimal 8 Bit CPU in a 32 Macrocell CPLD" でググると PDF ファイルがヒットする。まずは、これを入手。

  • http://opencores.org/project,mcpu

    どうも プロジェクトホームページは、ここらしい。アクセスできる人は、ここから入手するのが良さそうだ。

MCPU の概要

    ドキュメントを読むと 命令は なんと 4 つしかない。

    そんなものでプログラムを組めるのだろうか? という疑問にマクロを示すことで答えている。

      .. 確かにプログラムを書けそうな気はする。

    ソースコードは、VHDL版、Verilog 版が、ドキュメントの最後に添付されている。

      私は、VHDL版だけ見て、自己流の Verilog 版を作ることにした。... というより Verilog 版があるのを知らないで作ってしまった。まぁ、後でいじるつもりだし著作権的には自己流の方が都合が良いかも知れない。

      -- 著作権は、アイディア(ロジック)を保護するものではなく、表現を保護するもの。言語が違うから、表現も違う。オリジナルは尊重すべきだが、どうもこういうことらしい。

      verilog 版をよく見たらバグがあるようだ。要注意。

最初のインプリメント

    さて ... 規模をまずは見たい。メモリを付けて合成してみよう。

    メモリ空間は、なんと 6bit 64バイト分しかない。初期値付きの RAM として合成してみたら 59/128 スライス しか消費しなかった。

      module sram # (
      parameter SIZE = 64
      ) (
      input CLK,
      input WEB,
      input [5:0] ADDRB,
      input [7:0] DIB,
      output [7:0] DOB
      );
      reg [7:0] mem [0:SIZE-1];

      reg [7:0] r_addr;
      always @(negedge CLK)
      begin
      if (WEB) mem[ADDRB] <= DIB;
      r_addr <= ADDRB;
      end
      assign DOB = mem[r_addr];

      initial
      begin
      $readmemh("rom_data.mem" , mem, 0 , SIZE-1);
      end
      endmodule


    つけた RAMは、こういうもの。negedge で駆動。アドレスラッチ付き。

構想

    まずアドレス空間が狭すぎる。ハーバードアーキテクチャにしよう。これで RAM 専用空間に 64 バイト割り当てられる。

    で、何をしたいかと言うと I2C やらタイマがある EFB を割り当てたい ... ただ EFB の空間は 256 バイトもある。

EFB の調査

    MachXO2 和訳テクニカルノート にある
     
  • TN1205 ユーザフラッシュメモリ(UFM)と組み込み機能ブロック(EFB)の使用ガイド
    を見てみよう。

    まずは、アドレスマップ

     プライマリI2C   0x40 〜 0x49
     セカンダリI2C  0x4A 〜 0x53
     SPI       0x54 〜 0x5D
     タイマ/カウンタ  0x5E 〜 0x6F

    一応 64 バイト以内に収まっている。0x70 〜 0x7f の 16 バイトも空いている。あと、使わない機能のレジスタは RAM としても使えるはず。

    独自の I/O ポートを付けたいなら どこかを削らなければならない。これは、後で考えよう。

    次に EFB との接続インターフェイス。

    EFB は、WISHBONE インターフェイス。この説明は、TN1205 に載っている。

  • wb_clk_i posedge で動作
  • wb_rst_i active high の同期動作
  • wb_adr_i {2'b01, adreg}
  • wb_dat_i EFB の入力
  • wb_dat_o EFB の出力
  • wb_we_i 1 で Write だが レベルセンシティブ
  • wb_stb_i 1 で EFB を選択 ( adreg[5:4] != 2'b11 )
  • wb_cyc_i 1 でバス有効?
  • wb_ack_o

    こんな感じだが、ライトもリードも 最低3サイクル必要と書いてある。

    WISHBONE 用のクロックをまず生成し、それを3分周してマスタクロックにすることにしよう。

MCPU の拡張 (1) ハーバードアーキテクチャ

    これは、実はすごく簡単。(元が簡単だし)

    メモリから READ しているもの のいくつかを プログラムメモリに変えれば良い。
     
  • adreg にロードしている所
     
  • 上位 2bit だけをロードしている所

MCPU の拡張 (2) アドレス空間の拡張

    ハーバードアーキテクチャ に変更しても プログラムメモリは、64B しかない。MachXO2-256 は、128B までいけるはずなのだ。もう少し増やせないか?

    空間を増やすためには、プログラムメモリのビット幅を増やせば良い。分散メモリの場合、使わない bit は 最適化で消えるから、気前よく 12 bit にしてしまおう。

    で、アドレス空間自体は、7bit にする。これで規模がどうなるか。

    Number of Inst. 64(org) 64 96 128
    Design Summary
    Number of registers: 32 34
    Number of SLICEs: 51 92 107 124
    SLICEs(logic/ROM): 32 32
    SLICEs(logic/ROM/RAM): 19 60 75 92
    As RAM: 6 24
    As Logic/ROM: 13 36 51 68
    Number of logic LUT4s: 77 126 156 188
    Number of distributed RAM: 6 24
    Number of ripple logic: 5 5
    Total number of LUT4s: 99 184 214 246

    分散メモリは、ROM に格納するデータによって規模が変わる。64B 分しか使わないなら 128B 全部使うのと比べ 32 スライスも減った。

    同じ 64B でも ハーバードアーキテクチャ版から随分増えた。これは、メモリ(RAM) を 16B から 64B に増やしたのが主な理由。-- メモリが 16B しか使えないという制限で良いなら、変に拡張しないほうが良い。

      ちなみに、オリジナルは 57 スライスで、ハーバードアーキテクチャ版は規模が減っている。

      メモリは、RAM 64B だったのが、ROM 64B + RAM 16B になったわけだ。2 種類に増えた上、トータルの容量も増えた。さらに EFB まで付けて 減ったわけだ。

      オリジナルは、ハーバードアーキテクチャ版を元に書きなおした。分散メモリの RAM実装が 重いということになるのだろう。

アドレスマップ と I/O ポート

    何種類か設計したわけだが、アドレスマップを整理して I/O ポートをどうしたら良いのか考えてみよう。


    オリジナル EFB版 MCPU EFB + アドレス拡張
    0x00 - 0x2F : プログラム EFB EFB
    0x30 : 0xFF (allone) 0xFF (allone) 0xFF (allone)
    0x31 : 0x00 (zero) 0x00 (zero) 0x00 (zero)
    0x32 : 0x01 (one) 0x01 (one) 0x01 (one)

    0x34 - 0x3F : RAM RAM
    0x40 - 0x7F : --- ---  拡張RAM

    プログラムROM : ---- 64B 64B - 128B

    マクロを見ると 0xFF,0x00,0x01 がメモリ上に必要なのだ。そして書き換えられるのは都合が悪い。アドレスは、EFB 版とアドレスを合わせることにすると 0x30 〜の 3 バイトが良さそうだ。

    実際に付けるかどうかは、別にして ... 出力ポートは、0x30 - 0x32 の 3バイトを割り当てることにする。入力ポートは 0x33 の 1 バイトのみ ということにしたらどうだろう。

ソースコード

  • qfn32samples-04.zip

      バグなど
    • mcpu.v , mcpu_efb.v , mcpu2_efb.v の sram モジュールで WEB の論理が逆になっていた。


追記: シミュレータ(iVerilog) でデバッグ

    iVerilog を使ってデバッグした。いろいろ修正したが、どうやら動かせるようになったようだ。

    動かせたのは、EFB を組み込んだりした拡張版。オリジナルに一番近いのは、まだ。

    テストコードは、PDF に載っていたサンプルコード。どうも 最大公約数をもとめるものらしい。あと、アセンブラもどきを perl スクリプトで組んだりもしている。

    作った MCPU のバージョンは4つある。

      (1) mcpu.v オリジナルをもとに作ったもの
      (2) mcpu_efb.v ハーバードアーキテクチャ版 にした上で EFB を組み込んだもの。
      (3) mcpu1_efb.v 拡張しやすいように、rewrite したもの。
      (4) mcpu2_efb.v アドレス空間を拡張したもの。

    (1) だけ動いていない。

    アセンブラもどきは、後方参照した場合、アドレスが分からないというレベルのもので、出力を参考にしてコードを転記している。

    規模は、(3) が、53スライス、(4) が 103 スライス。EFB を組み込んだ上で、adreg, O_DATA などをピンに出力している。(4) は、LDI 命令を追加しての規模で、なしにすると 92。なにか使い物になりそうな気もしてきたのだが ...

    よくよく考えると bit 操作すらできない代物だった。(3) は随分余裕があるようなので、これを元に ALU を付けたCPUを設計したほう方が実用的だろう。

    あと RAM のアクセスの仕方に難がある。分散メモリ専用ならこれでも良いのだが、今はブロックRAM を使えない。一方 ROM の方は問題ないようだ。

    (3) について、ちょっとメモっておこう。


    ADD 命令 + STA 命令
    _______ _______ _______
    CLK ______| |_______| |_______| |______|

    code_data 01AAAAAA | 01BBBBBB |

    inst 000 | 101 | 000 | 110
    ______________
    WE _______________________________________| |__

    @
    (RAM_USE_CLK)
    ______________
    WE_mem ______________________________| |__
    @ @ @ @

    分かりにくいと思うが、2 クロックで 1 命令実行 。posedge で CPU は動作している。最初の状態は、000 で アドレスだけは確定している。

    code_data は ROM 出力で negedge で変化する。アドレスは、ラッチを持たず ROM データをそのまま使っている。

    で、ROM データは、毎クロック読み出しているが、inst==0 の時だけの方が良いかも知れない。

    問題の RAM は、WE の立ち上がりで書き込みとしていた。一般的なコードにするとすれば ...

    posedge で動作させることにして、WE = 1 で書き込み。ただし WE は半クロック前に確定させないといけない。

    実際にやってみたら動いたのだが、規模は +7 スライスの増加。

     ・ qfn32samples-05.zip

新たな CPU へ

    mcpu2 では、命令用メモリを 12bit にした。そのうち 下位 7bit はアドレス。命令用に 5bit 使えるわけだが今は 2bit しか使っていない。ここを再構成し、ALU を入れることによって 別の CPU にしたい。

    といっても基本は変えない。演算する、ストアする、条件ジャンプするという枠組みは出来ているのだ。枠組みを壊さなければ、書きなおすところが圧倒的に減る。

    改造の要点

      10 : STA -- 変更しない。
      00 : NOR -- 演算命令 8 つ に変更

      使う ALU は、自作AVRコア用 ALU 。ちょうど 8bit だし、都合が良い。
      00XXX -- 3bit 使えるが まるまる ALU の S に使う。

      01 : ADD -- 即値演算命令 4 つに変更

      即値演算にも ALU を使う。ただし、論理演算のみ。

      01XX -- 即値は、2bitしか使えないのだ。命令の割り当ては、演算命令との兼ね合いで決める。

       即値は、01 S[1] S[0] 8bit即値 (S[2] = 1) とすることにしよう。
       演算は、00 S[1] S[0] S[2] 7bitアドレス

      11 : JCC -- 条件を拡張+α

      条件ジャンプ命令は、3bit 使える。C or Z で 1bit ,条件を逆にするのに 1bit 。あと 1bit は、C の値としておこう。 C をロードする命令がないのだ。

    動くかどうか分からないのだが、ROM 64 words , RAM 16 bytes なら 97 スライスに収まった。これならいけそう。

    命令表

      (inst == 100)
      00 00 0 ADD
      00 01 0 ADC (with carry)
      00 10 0 SUB
      00 11 0 SBC (with carry)
      00 00 1 AND
      00 01 1 XOR
      00 10 1 OR
      00 11 1 LD

      (inst == 101)
      01 00 ANDI
      01 01 XORI
      01 10 ORI
      01 11 LDI

      (inst == 110)
      10 XX X ST

      (inst == 111)
      11 00 C JCS jump if carry set
      11 01 C JCC jump if carry clear
      11 10 C JNE jump if zero-flag set
      11 11 C JEQ jump if zero-flag clear

      carry,zero-flag が変化するのは、ADD などの 算術演算命令のみ。

    全部で 17 命令。... だがコード上は 4 種類のまま。ALU が何をしているか関知してないから。

    シフト命令は .. ないなぁ。
    これを付けるとすれば ... 001 〜 011 の inst を使うことになる。

      (inst == 100)
      10 00 STA
      (inst == 001)
      10 01 拡張命令 1
      (inst == 010)
      10 10 拡張命令 2
      (inst == 011)
      10 11 拡張命令 3

    どんな命令にするか別にして、こういう割り当てにする。

    だいたい作った上で動作周波数を見てみた。4 グレードで 20 MHz 〜 25 MHz ぐらい。クロックは、EFB を操作する都合で 3 倍クロックを入力している。命令実行は 2 クロックに 1 回なので 1/6 ... 4 MIPS ぐらい?

    多分なにか間違っている。制約の設定が足りないのだろう。だが、EFB のクロック関係は適当すぎたかも。

    EFB を外して CLK で駆動したら 22.9 MHz になった。これでも 違うはずだが、この 2 倍は無理だろう。
    改造前の mcpu2 だと 45.5MHz と出た。ALU 周りがボトルネックと見做されたわけだ。だが、1.5 クロックかけて演算している。これを 0.5 クロックと見られたら 低い結果になりそう。

      どうも、MULTICYCLE制約というので設定するらしいので試してみた。

      FREQUENCY PORT "CLK" 36.000000 MHz ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i0" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i1" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i2" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_lo/akku_i3" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i4" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i5" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i6" 1.500000 X ;
      MULTICYCLE TO CELL "alu_impl_I/alu_impl/alu_hi/akku_i7" 1.500000 X ;
      MULTICYCLE TO CELL "C_68" 1.500000 X ;
      MULTICYCLE TO CELL "Z_69" 1.500000 X ;

      ... と 22.9 MHz が 57.0 MHz まで上がった。 いくらなんでも 上がりすぎのような ...

      FREQUENCY PORT "CLK" 36.000000 MHz ;
      DEFINE CELL GROUP "akku" "alu_impl_I/alu_impl/alu_hi/akku_i7"
      "alu_impl_I/alu_impl/alu_lo/akku_i1"
      "alu_impl_I/alu_impl/alu_lo/akku_i2"
      "alu_impl_I/alu_impl/alu_lo/akku_i3"
      "alu_impl_I/alu_impl/alu_lo/akku_i0"
      "alu_impl_I/alu_impl/alu_hi/akku_i6"
      "alu_impl_I/alu_impl/alu_hi/akku_i4"
      "alu_impl_I/alu_impl/alu_hi/akku_i5"
      "Z_69"
      "C_68" ;
      DEFINE CELL GROUP "code" "rom_impl/r_addr__i5"
      "rom_impl/r_addr__i4"
      "rom_impl/r_addr__i3"
      "rom_impl/r_addr__i2"
      "rom_impl/r_addr__i6"
      "rom_impl/r_addr__i1" ;
      MULTICYCLE FROM GROUP "code" TO GROUP "akku" 1.500000 X ;

      よく分かってないのだが、GROUP というのを定義して、その間を設定するやり方もある。指定したいのは、こっちのような気がする。基本的に rom の アドレスが変わることによって 結果が出るのだ。これだと 37.6 MHz ...

      mcpu2 だと 45.5MHz が 63.0 MHz に。

追記

    mcp3 の 命令の仕様を決めて、簡易アセンブラを 作った。ちょっと改良して後方参照も 見るようにした。やはり、ないとデバッグが不便。あと 出力ポート追加。

      MCPU3 命令表(全 21 命令)

      (inst == 100)
      00 00 0 ADD
      00 01 0 ADC (with carry)
      00 10 0 SUB
      00 11 0 SBC (with carry)
      00 00 1 AND
      00 01 1 XOR
      00 10 1 OR
      00 11 1 LD

      (inst == 101)
      01 00 ANDI
      01 01 XORI
      01 10 ORI
      01 11 LDI

      (inst == 110)
      10 00 X ST
      (inst == 001)
      10 01 ASR
      (inst == 010)
      10 10 ROL
      (inst == 011)
      10 11 ROR
      (inst == 111)
      11 00 C JCS jump if carry set
      11 01 C JCC jump if carry clear
      11 10 C JNE jump if zero-flag set
      11 11 C JEQ jump if zero-flag clear

      (MACRO)
      SEC set carry JCS *+1 (C=1)
      CLC clear carry JCC *+1 (C=0)
      JMP addr JCC adr (C=0), JCC adr (C=0)
       ・carry が変化するのは、ADD などの 算術演算命令
        と JCC 等のブランチ命令, シフト命令
       ・zero-flag は、論理演算やロード LD,LDI でも変化

    これで コードは作れるようになった。最初に作ったのは、MCPU のサンプルの GCD の移植。単純変換したら、1命令しか減らなかった。

    さて、次は EFB にアクセスしてみたい ... のだが、ちょっと出来が悪かったので再考。

      ・ まず 3 倍速というのがダメ。CPU の CLK が 2:1 になり ボトルネックになる。2 倍速にする。
      ・ 2 倍速 を検討したのだが、CPU は、ちょうど 2 クロック毎の処理になっている。
      ・ ROM を非同期アクセスに変更すると具合が良いことが分かった。

      READ サイクル
      _____ _____
      CLK | |_____| |_____|

      inst | 000 | 100 |
      ____________
      wb_cyc ______| |_____
      @ @
      ADDR DATA @
      ラッチ 実行
      WRITE サイクル

      _____ _____
      CLK | |_____| |_____|

      inst | 000 | 110 |
      _______________________
      wb_we | |_
      ____________
      wb_cyc ______| |_____
      @
      ADDR
      DATA

      READ/WRITE サイクルを図示するとこんな感じ。
      READ では、wb_cyc=1 と同時に アドレスが決まってないといけない。アドレスは、命令に含まれ、CLK 立ち上がりで読み込み開始。次の CLK_2X で データを読み込めるが、ここでラッチしないといけない。

      WRITE は、命令を読みこめばただちに分かる。DATA も用意しなければならないが、akku(アキュームレータ)を WRITE するだけなので、命令と同じタイミングで読める。

    あと、制約を書くのに C_68 とか変なサフィックスになるので、reg [1:0] flags に変更。

    だいぶ仕上がって来た。いままで作ってきたサンプルは、MachXO2-256 をターゲットにしている。当然 『MachXO2 breakout ボード』でも 動くはず。EFB アクセスはこれで確認してみたい。



      シミュレーション結果。赤い不定値部分は、RAM の範囲外ということ。EFB を読み込んでいるわけだ。(演算命令なので、値は使わないが)。 タイミングは上に書いた通りになっている。アクセスする時だけ wb_cyc をアサートしているので、wb_we のタイミングも想定通り。... なのだが、仕様を勘違いしているかも。

      ところで、EFB を使ったときの 最大周波数が低く出てしまう。赤い不定値部分のとなり -- RAM への書き込みをしているが、WE_mem が 1 になって、値が書き込んだときの値に変わっている。どうも ここがボトルネックと解析されているのだが、この値はだれも使わない。from WE_mem を 6X と設定したら CLK_2X が 61.5MHz になった。(命令実行は 1/4 なので 15 MIPS といったところ -- たいしたことはない)。

    あと作っておきたいのは、JTAG 経由の通信 。

関連記事
posted by すz at 10:34| Comment(0) | TrackBack(0) | CPLD

2012年06月13日

FPGA時計の設計

QFN32の FPGA』 を使ったなにかを設計してみたい。この際 AVR 使った方が有利で楽なものでも構わないが、実際に動かせるもの。ALU では、それを使ったものの敷居が高い。

で、時計はどうだろう? プロセッサなしで動かすには一体どうするのか? 実は意外にも簡単なことかも知れない。

構想1

    まず出力。今は 4桁の 7セグが入手できるからそれを使う。時計の : がないが、とりあえずは気にしない。気にする場合は、(配線が面倒になるのと引換に) 2桁の 7セグ x2 で間に LED を 2個入れられるようにして置けば良い。

    この FPGA だが ... DRIVE 能力の設定がある。LVCMOS33 だと デフォルト 8mA 、最大では 24mA の設定ができる。ただ、24 mA 流せてもコモンは 7 倍になるから 足りない。ドライバは必須。カソードコモンだと NPN トランジスタ が使えて楽そうな気もするのだが、7セグ側を H にして点灯させないといけない。そうなると白色 LED を使うのは厳しくなる。

    一応アノードコモンを使い 7セグ側を L で点灯ということにしておきたい。コモンのドライブも L 。こういったものは、出来た後見直すことにしよう。

    ピン数の合計 7 + 4 + 1(2) = 12(13)

      使える I/O は 21 しかない。ちゃんと入るものなのかどうか、常に気にするようにしよう。

    次にクロック。ダイナミック点灯や ボタンでのオペレーションを考えて 128 Hz を基本としておこう。 8MHz なら 128 で割り切れる。32768 Hz を原発信にしたい気もする。これは、あくまで最初の想定。出来上がってしまえば、対応できる範囲というのも分かってくるだろう。

    さて、これはどうやって入力するか -- 。取り敢えずは、外部のオシレータを想定すれば良い。ただ、水晶発振回路を内蔵したい。ピン数は、2 と見積もっておく。

    ピン数の合計 14(15)

    入力。時刻の設定は出来ないと困るだろう。最も簡単なのは、SEL と UP の 2 つ。DOWN も付けると、よくある時計のオペレーションになるはず。一応 3 つと見積もる。

    ピン数の合計 17(18) --- 残り 4(3)

    あと最大 4 つしか余らない。まぁ機能など付ける余裕もなさそう。JTAG はできれば空けときたいので、4 つ余らせることにしよう。

構想2 カウンタと表示

    ここからは、内部をどうするか ...

    まず時計というからにはカウンタが必要だ。それをどういう風に設計するか?

    前提としてダイナミック点灯するわけだ。ならば、出力は 4bitの BCD 1つで良い。これを 7seg エンコーダを通して 表示させるわけだ。

    考えたのだが、0〜59 や 0〜24 の 2 桁カウンタのモジュールをまず作ろうと思う。最大値は モジュールのパラメータで指定。あと 出力は 4bit で HI/LO を切り替える SEL 入力を持つことにする。

    入力は、128Hz のクロック と カウンタ動作の指定 UP(/DOWN)。あと上位に対する UP(/DOWN)。まずは、DOWN は後回し。回路が入るようなら検討。

    7seg エンコーダは、分散メモリを使ったテーブル ... にしたかったのだが Lattice の場合、妙な Warning が出るので assign と ? 演算子だけで何とかしようかと思う。(テーブルと同じ効果になるはず)。

構想3 カウンタと表示(続き)

    上のカウンタモジュールを 3 つ使い 時分秒に割り当てる。出力も 3 つ。とりあえず、時分 と 分秒 を表示する 2 つのモードを作ろう。

    セレクタでのこの 3 つの出力を切り替えられるようにして、モジュール内の HI/LO を切り替え を合わせてダイナミック点灯させる。

      ダイナミック点灯の 2bitカウンタがあるとする。下位は HI/LO に割り当てる。上位だけ考えれば良いのだ。

      0 1
      disp_mode 0 HH MM
      disp_mode 1 MM SS

      こうなるようにすれば良い。

    さて、これだけか? というと違う。時刻の設定である。時刻の設定ではセレクトされたものがブリンクする。設定モードについて考えておこう。

    モード自体の定義は、次のようにしよう。

      set_mode 0 : 通常表示 (HH:MM)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク
      set_mode 3 : 秒設定 (MM:SS) で SS ブリンク

      blink off blink on
      0 1 0 1
      set_mode 0 : HH MM HH MM
      set_mode 1 : HH MM -- MM
      set_mode 2 : HH MM HH --
      set_mode 3 : MM SS MM --

      ( -- : blank )

    だいぶややこしくなってきた。が、所詮これだけ。全部で 16 通りしかない。あと blink 信号は、クロックから 1Hz を取り出せば良い。

構想4 ボタン

    次は、どうやってボタンから入力をするかに飛ぼう。

    例えば、SELボタンを押したときは、set_mode をインクリメントするだけで良い。だが、どうやって押したと認識するのか? スイッチの入力をそのまま入れたのでは、128 Hz で set_mode が回転するだけだ。また、チャタリング対策というものも必要になる。

      (128 Hz で次の処理を行う)
      N 回 同じ入力が続いたら ボタンが押された、離されたと認識する。
      押された、離された という状態を作り、離された→押された と変化したとき ボタンON。
      使う側が、ボタンONを認識して リセット を送ってきたら (非同期に)ボタンOFF。

    こういう処理をするボタンモジュールを作ろうと考えている。

    N 回 同じ入力というのは、チャタリング対策。これを を認識するのに N -1 bit の レジスタが必要。N=4 にしたいなら、 128 Hzは、周波数が高すぎるかも知れない。これは、後で要調整。
 
実装編 カウンタ

    以上で重要な部分は説明した。後は実際のコードを示していこう。

    module clock_counter # (
    // parameter MAX = 60
    parameter MAX = 24
    ) (
    input CLK
    , input I_UP
    , input SEL // select output
    , output [3:0] O
    , output O_UP
    );

    reg [3:0] lower;
    reg [3:0] upper;
    reg r_ovr;

    assign O[3:0] = SEL ? lower[3:0] : upper[3:0];
    assign O_UP = r_ovr;

    always @(negedge CLK)
    begin
    if (I_UP)
    if ((upper >= (MAX-1)/10) & (lower >= (MAX-1)%10))
    begin
    r_ovr <= 1'b1;
    upper <= 0;
    lower <= 0;
    end
    else if (lower >= 9)
    begin
    r_ovr <= 1'b0;
    upper <= upper + 1;
    lower <= 0;
    end
    else
    begin
    r_ovr <= 1'b0;
    lower <= lower + 1;
    end
    else
    r_ovr <= 1'b0;
    end
    endmodule

    説明したとおりのもの。規模は1つ 10スライスのようだ。3つ使うから、これだけで 30 スライス。

実装編 ボタン

    module button
    (
    input CLK_128HZ
    , input I
    , output O
    , input R
    );

    reg [3:0] i_stat;
    reg prev_stat;
    reg r_out;

    always @(R, negedge CLK_128HZ)
    begin
    i_stat <= { i_stat[2:0] , I } ;
    if ( { i_stat[2:0] , I } == 4'b0000 )
    prev_stat <= 1'b0;
    else if ( { i_stat[2:0] , I } == 4'b1111 )
    prev_stat <= 1'b1;
    if (R) r_out <= 1'b0;
    else if (~prev_stat & ( { i_stat[2:0] , I } == 4'b1111 ))
    r_out <= 1'b1;
    end
    assign O = r_out;
    endmodule

    これもまた説明どおり。これは、4スライス。最低 2 つ使う。

一応完成

  • qfn32samples-03.zip

    Diamond プロジェクト込みのソースを置いておく。iVerilog シミュレータも一応通している。

    // XO2-256 modified ROM/RAM XO2-256
    // 27 B 1 24 VCC
    // 28 G 2 23 DIG1 25
    // 29 3 22 DIG2 23
    // 30 4 21 DIG3 21
    // 32 5 20 DIG4 20
    // 1 6 19 CLK_1HZ 17
    // 4 C 7 18 BTN_DN 16
    // 5 D 8 17 N.C.
    // 8 E 9 16 BTN_UP 14
    // 9 A 10 15 BTN_SEL 13
    // 10 F 11 14 CLK 12
    // GND 12 13 CLK_OUT 11

    ピン配置はとりあえずこうした。DIGは、LVCMOS33 の 24mA に設定。ACTIVE_HIGH に変更して、3mA 程度まで電流を減らせば直接ドライブできるかも。

    機能としては、
  • down ボタンを付けた
  • 7セグ制御の正論理・負論理を切り替えられるようにした。
  • クロックは、128HZ の倍数なら OK なようにした。

    一応説明で言及したものは、入れたことになる。-- これで規模は 86/128 スライス。
    機能追加はなかなか厳しそうだが、アラームクロックにするのは ... 可能かも。time_couner に比較用のレジスタを作って ... まぁ今後の課題にしておこう。

    追記: アラーム機能を付けてみた。

    .. といっても ポートを増やしたくなかったので、秒を示す LED を 1分間 高速点滅させるだけ。機能もちょっと見なおしている。


      set_mode 0 : 通常表示 (HH:MM)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク (アラーム)
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク (アラーム)
      set_mode 1 : 時設定 (HH:MM) で HH ブリンク
      set_mode 2 : 分設定 (HH:MM) で MM ブリンク
      set_mode 3 : 秒表示 で SS ブリンク

      blink off blink on
      0 1 0 1
      set_mode 0 : HH MM HH MM
      set_mode 1 : HH MM -- MM
      set_mode 2 : HH MM HH --
      set_mode 3 : SS -- (秒表示のみ)

      ( -- : blank )

     ・ qfn32samples-04.zip (ソースコード)

関連記事
posted by すz at 20:03| Comment(0) | TrackBack(0) | CPLD

2012年06月10日

TTL ALU 74181

前の記事『QFN32の FPGA』から派生。

    FPGA MachXO2 の QFN32 パッケージが出るので、DIP にする基板を作ってみたい -- というところから、74HC181 (SN74LS181) の代替ができるものを .. となった。

    この IC は ALU(Arithmetic Logic Unit) で、部品を組み合わせて CPU を作ったりするのに欠かせない。有名な『CPU の創りかた』 でも 74HC181 を使っているとのこと。

    ただ入手がとても難しい。では、DIP にするついでにピン互換機能を持たせられるようにすれば、どうだろう。... 調べてみると、電子工作レベルで、ピン互換にできそうなものはあまりない。

    ならば、やってみようというのが動機。

手始めに コードを作ってみよう。

データシートを見てみる。

    www.alldatasheet.jp で検索してみる。
     
  • *4HC181
     
  • *4LS181

    とりあえず、TI の SN74LS181NE4 を参考書として使うことにする。

    で、見てみたのだが、難解。キャリー入力(Cn) が L と H で計算式が違う。これは面倒だなと思ったのだが、どうも ACtive-Low の表が、そういうことらしく、ACtive-High なら見れなくはない。

    ただ、キャリー出力がどうなるのかサッパリ分からない。結局頼りになるのは、回路図。回路図を読みこなすならば、再現してしまった方が 簡単のような気がしてきた。

      例えば、S == 3 では、キャリーありで F = 0 / キャリーなしで F = -1 と なっているのだが ..

      8bit に拡張した場合のことを考えると、0 を出力したときにキャリー出力が 1 (負論理なので L) にならないと変だ。だが、合っているのか? こういうことを、いちいち 回路図を眺めて確認するのは面倒すぎる。

回路図を元にコードを作る。

    回路図を見ると A/B を入力にして 2 つの出力にしている部分(左)と それを元に F などの出力を生成している部分(右)に分かれるようだ。

    左の出力を C/D という中間出力にして論理式にしてみよう。

    wire [3:0] C;
    wire [3:0] D;

    assign C[0] = ~( A[0] & ~B[0] & S[2] | A[0] & B[0] & S[3] );
    assign C[1] = ~( A[1] & ~B[1] & S[2] | A[1] & B[1] & S[3] );
    assign C[2] = ~( A[2] & ~B[2] & S[2] | A[2] & B[2] & S[3] );
    assign C[3] = ~( A[3] & ~B[3] & S[2] | A[3] & B[3] & S[3] );

    assign D[0] = ~( A[0] | B[0] & S[0] | ~B[0] & S[1] );
    assign D[1] = ~( A[1] | B[1] & S[0] | ~B[1] & S[1] );
    assign D[2] = ~( A[2] | B[2] & S[0] | ~B[2] & S[1] );
    assign D[3] = ~( A[3] | B[3] & S[0] | ~B[3] & S[1] );

    上の部分を C , 下の部分を D とするとこうなった。なんか随分すっきりしている。調子に乗って次。

    assign F[0] = ~( ~M & C_IN ) ^ D[0] ^ C[0];
    assign F[1] = ~( ~M & ( D[0] | C_IN & C[0] )) ^ D[1] ^ C[1];
    assign F[2] = ~( ~M & ( D[1] | D[0] & C[1] | C_IN & C[0] & C[1]))
    ^ D[2] ^ C[2];
    assign F[3] = ~( ~M & ( D[2] | D[1] & C[2] | D[0] & C[1] & C[2]
    | C_IN & C[0] & C[1] & C[2])) ^ D[3] ^ C[3];

    wire Yinv = D[3] | C[3] & ( D[2] | C[2] & ( D[1] | C[1] & D[0]));
    wire C_OUT = Yinv | C[3] & C[2] & C[1] & C[0] & C_IN;
    wire EQ = F[0] & F[1] & F[2] & F[3];

    wire X = ~( C[3] & C[2] & C[1] & C[0] );
    wire G = ~ Yinv;
    wire P = X;

    なにかバグがあるかもしれないが、こんな感じ。

    これを合成してみると ...

    Number of SLICEs: 9 out of 128 (7%)
    SLICEs(logic/ROM): 9 out of 32 (28%)
    SLICEs(logic/ROM/RAM): 0 out of 96 (0%)
    As RAM: 0 out of 96 (0%)
    As Logic/ROM: 0 out of 96 (0%)
    Total number of LUT4s: 17
    Number of PIO sites used: 20 out of 22 (91%)

    なんと 9/128 スライス。楽勝ではないか。P/G は生成していないが、C_OUT(Cn+4) の計算で使っているし、生成しても 僅かの差のはず。

    ただ、17 LUT で作れてしまうものなのだろうか? C/D 生成は、4 入力1出力 が 8 個だから 8LUT で確かにできる。残り 9 個だが .. 配線のために消費しなければ、出来そうな 気もする。

    訂正: F3 の括弧の位置が違った。直したら さらに規模が減った。

    ソースコード (Diamond プロジェクト込み)

     
  • qfn32samples-02.zip (2012/06/12 最新版に更新)
     
  • qfn32samples.zip (ここで説明したもの 旧版)

    ちなみに以前作った 74281 (レジスタ付き ALU = アキュームレータ) P/G なし (上記ソースコードに添付)

    Number of registers: 4
    Number of SLICEs: 39 out of 128 (30%)
    SLICEs(logic/ROM): 32 out of 32 (100%)
    SLICEs(logic/ROM/RAM): 7 out of 96 (7%)
    As RAM: 0 out of 96 (0%)
    As Logic/ROM: 7 out of 96 (7%)
    Total number of LUT4s: 78
    Number of PIO sites used: 20 out of 22 (91%)

    こちらは、高機能だし、最適化された回路図なしだから効率が悪いのかも。それにしても楽勝なのは変わりない。確か 64 マクロセルの XC2C64A(CoolRunner II) にようやく入れられたような 規模だったはずなんだが ...

      追記: これなのだが、後述の 74181 拡張をしてそれをベースにしたら 規模が多少小さくなった。そうであれば自作AVRコアにも適用したくなるし、いずれは 32bit CPU も作りたいので 整理して使いやすくしておこうかと思う。

      自作AVRコアにも適用しても確かに規模は減る。ただ、規模が減れば速くなるものかどうか? あと厳密に機能を作らないといけないのでデバッグが面倒。

検証するには

    規模のチェックは、これぐらいで良いだろう。次はどうやって検証するか。

    C のコードに変換するのは、簡単だ。で、実際にいくつかのパターンを入力して OK そうか見る。大丈夫そうになったら、真理値表を作る。

    確か誰かが全入力の真理値表を作っていたような ... 探してみよう。答え合わせができれば、完了。

    追記: 実際に C 化してみた。

    #define _C(v) (((v)&1)?'1':'0')

    #define wire
    #define assign

    void alu181() {
        (上記の Verilog コード)
    printf("M %c ", _C(M) );
    printf("S = %c%c%c%c ", _C(S[3]), _C(S[2]), _C(S[1]), _C(S[0]) );
    printf("C_IN %c ", _C(C_IN) );
    printf("A = %c%c%c%c ", _C(A[3]), _C(A[2]), _C(A[1]), _C(A[0]) );
    printf("B = %c%c%c%c ", _C(B[3]), _C(B[2]), _C(B[1]), _C(B[0]) );
    printf("F = %c%c%c%c ", _C(F[3]), _C(F[2]), _C(F[1]), _C(F[0]) );
    printf("C_OUT %c", _C(C_OUT) );
    printf("\n");
    }

    こんな風にすると、Verilog のコードを触らなくて済む。

      M 1 S = 0000 C_IN 1 A = 0011 B = 0111 F = 1100 C_OUT 1
      M 0 S = 0000 C_IN 1 A = 0011 B = 0111 F = 0011 C_OUT 1
      M 0 S = 0000 C_IN 0 A = 0011 B = 0111 F = 0100 C_OUT 1

      M 1 S = 0001 C_IN 1 A = 0011 B = 0111 F = 1000 C_OUT 1
      M 0 S = 0001 C_IN 1 A = 0011 B = 0111 F = 0111 C_OUT 1
      M 0 S = 0001 C_IN 0 A = 0011 B = 0111 F = 1000 C_OUT 1

      M 1 S = 0011 C_IN 1 A = 0011 B = 0111 F = 0000 C_OUT 1
      M 0 S = 0011 C_IN 1 A = 0011 B = 0111 F = 1111 C_OUT 1
      M 0 S = 0011 C_IN 0 A = 0011 B = 0111 F = 0000 C_OUT 0

      M 1 S = 0110 C_IN 1 A = 0011 B = 0111 F = 0100 C_OUT 1
      M 0 S = 0110 C_IN 1 A = 0011 B = 0111 F = 1011 C_OUT 1
      M 0 S = 0110 C_IN 0 A = 0011 B = 0111 F = 1100 C_OUT 1

    幾つか試したが、合っているようだ。 (最初は合わなかったので、括弧の間違いが見つかったわけだが)

所感

    実を言うと、MachXO2-1200 とか使っていても もっと規模が欲しい。256 ぐらいで何が出来るのか? と思っていたりしたのだが ... 制限内で何かを作るのも面白いかも知れない。CPLD なんかよりずっと回路が入りそう。それでも複雑なものは作れないから、単機能のものをじっくりやることになる。お手軽に使えるように環境を整えて、いろいろ設計してみようかと思う。

74181 の拡張

    74181 は、ものすごく高機能な印象があったのだが、74281 の ALU や 74381 をこれを元に実装しようとすると機能が足りないことが分かった。

    281 の ALU や 381 では、" B - A " があるのだが、181 では無理なのだ。さらに 281 の ALU では、" ~B - 1" なんてものまである。

    で、どうしようか 作ったのを眺めて考えた。181 では、前段の C,D を作るところだけで、機能セレクトを使っている。ようく見ると A と B は対称ではないが、機能セレクトを追加すれば、対称にできる。

    assign C[0] = ~((~A[0] & S[6] | A[0] & S[7])&(~B[0] & S[2] | B[0] & S[3]));
    assign C[1] = ~((~A[1] & S[6] | A[1] & S[7])&(~B[1] & S[2] | B[1] & S[3]));
    assign C[2] = ~((~A[2] & S[6] | A[2] & S[7])&(~B[2] & S[2] | B[2] & S[3]));
    assign C[3] = ~((~A[3] & S[6] | A[3] & S[7])&(~B[3] & S[2] | B[3] & S[3]));

    assign D[0] = ~( A[0] & S[4] | ~A[0] & S[5] | B[0] & S[0] | ~B[0] & S[1] );
    assign D[1] = ~( A[1] & S[4] | ~A[1] & S[5] | B[1] & S[0] | ~B[1] & S[1] );
    assign D[2] = ~( A[2] & S[4] | ~A[2] & S[5] | B[2] & S[0] | ~B[2] & S[1] );
    assign D[3] = ~( A[3] & S[4] | ~A[3] & S[5] | B[3] & S[0] | ~B[3] & S[1] );

    こうするのだ。S が 4bit から 8bit になってしまうわけだが、使い方は次のようにする。

    S[7:4] S[3:0]
    1 0 0 1 FS[3:0] --- (1)
    FS[3:0] 1 0 0 1 --- (2)

    0 1 1 0 FS[3:0] --- (3)
    FS[3:0] 0 1 1 0 --- (4)

    新設の上位 4bit を 1001 にすると ... 今までと互換(1)。 で、上位ビットと下位ビットを入れ替えると ... A と B が入れ替わる(2)。これは計算式を眺めれば分かるはずだ。

    さらに (1) の 1001 を 0110 にすれば A がビット反転する(~A)。 A と B を入れ替えた上で B をビット反転するには、(4) のようにする。

    これでようやく 281 の ALU や 381 の機能を作れるようになった。

メモ: (ピン)互換モジュールのピンアサイン案

    XO2-256 74181 74181 XO2-256
    27 B0 1 24 VCC
    28 A0 2 23 A1 25
    29 S3 3 22 B1 23
    30 S2 4 21 A2 21
    32 S1 5 20 B2 20
    1 S0 6 19 A3 17
    4 ~Cn 7 18 B3 16
    5 M 8 * 17 N.C. 26
    8 F0 9 16 ~Cn+4 14
    9 F1 10 * 15 SWAP 13
    10 F2 11 14 EQ 12
    GND 12 13 F3 11

    前の記事にも書いたが、実際は有効なピンが足りず互換にできない。P/G は使わないだろうということで、17 は N.C. 扱い。(N.C. とは書いたが無接続にすること)

    15 は、入力 A と B を入れ替える機能にしようかと思う。どうせ回路は余っているのだ。9 スライスが 13 スライスになったが さほどのことはない。

    あ、あたかもモジュールが存在するような書き方をしたが、部品も入手できておらずあくまで予定。

    181 の 機能

    S3 S2 S1 S0 M = 1 C_IN = 1 C_IN = 0
    logic (no carry) (with carry)

    0 0 0 0 ~A A A + 1
    0 0 0 1 ~(A | B)
    0 0 1 0
    0 0 1 1 0 -1 0
    0 1 0 0 ~(A & B)
    0 1 0 1 ~B
    0 1 1 0 A ^ B A - B - 1 A - B
    0 1 1 1
    1 0 0 0 ~A
    1 0 0 1 ~(A ^ B) A + B A + B + 1
    1 0 1 0 B
    1 0 1 1 A & B
    1 1 0 0 1 A + A A + A + 1
    1 1 0 1
    1 1 1 0 A | B
    1 1 1 1 A A - 1 A

    使う可能性があるものをピックアップしてみた。結構スカスカ。SWAP を追加しても M = 1 は結果が同じ。役に立ちそうなのは、B - A , B - 1 , B + 1 ぐらい。

オーバーフラグ(OVR)の 追加

    AVRコアだと H フラグ, V フラグというのがある。H フラグは、4bit のキャリーフラグで 181 ベースでの ALU なら 出力するだけで済む。(自作のコアでは、Verilog の演算をしていたので、別途生成しないといけなかった)

    さて、V フラグは どういうものか ... 現状のものは、次のような式を使っている。

    assign V_out = ~M &
    ( ~(S[1]) & (F[7] & ~A[7] & ~B[7] | ~F[7] & A[7] & B[7])
    | (S[1]) & (F[7] & ~A[7] & B[7] | ~F[7] & A[7] & ~B[7]) )
    ;

    M=1 が論理演算で、~(S[1]) は、算術加算。 で S[1] が算術減算。... 要するに A/B/F の最上位ビットを 比べているわけだが、加算/減算で論理が違う。

    一方 74LS382 (381 のモディファイ版) には、OVR という出力があって、こんな計算はしていない。よくわからなかったのだが、どうも C_OUT(n) ^ C_OUT(n-1) という計算をしているようだ。

    C_OUT(n) は、既にあるわけだが、181 は内部で C_OUT(n-1) も作っているはず。そうであれば、比較的簡単に OVR を作れるのではないか?

    wire C_OUT1 = ~( D[0] | ~C_IN & C[0]);
    wire C_OUT2 = ~( D[1] | C[1] & D[0] | ~C_IN & C[0] & C[1]);
    wire C_OUT3 = ~( D[2] | C[2] & ( D[1] | C[1] & D[0])
    | ~C_IN & C[0] & C[1] & C[2]);

    assign P = ~( C[3] & C[2] & C[1] & C[0] );
    assign G = ~(D[3] | C[3] & ( D[2] | C[2] & ( D[1] | C[1] & D[0])));
    assign C_OUT = ~(~G | ~C_IN & C[0] & C[1] & C[2] & C[3]);

    assign F[0] = ~( ~M & ~C_IN ) ^ D[0] ^ C[0];
    assign F[1] = ~( ~M & ~C_OUT1 ) ^ D[1] ^ C[1];
    assign F[2] = ~( ~M & ~C_OUT2 ) ^ D[2] ^ C[2];
    assign F[3] = ~( ~M & ~C_OUT3 ) ^ D[3] ^ C[3];

    assign OVR = ~M & (C_OUT3 ^ C_OUT);
    assign EQ = F[0] & F[1] & F[2] & F[3];

    どうもこうらしい。これで、たいぶすっきりした。

    で、この修正を入れたら、15 スライスにまで増えた。( ただし 使わなければ 元と同じ )

8bit への拡張

    この alu を 2 つ使って 8bit 版を作ってみている。C_OUT と 上位 4bit の C_IN をつないだもの。

    35 スライス / 68 LUT になった。だいぶでかくなった。

    P/G の生成は、

    assign P = PL | PH;
    assign G = GH & (PH & GL );

    こんな風にやるものらしい。PH , GH を出力すれば良いものではないようだ。8bit 版単体で使うなら関係ないのだが、いずれは、32bit CPU を作りたいので対応しておいた。また、8bit 版 になるとピンが増えるので、想定しているモジュールでは合成できない。

    最初の目的は、 自作AVRコアに適用すること。まだ機能が厳密に合っているのかどうか分からないが .. 42 スライス(83 LUT) が、31 スライス(62 LUT) にまで減った。問題は、規模よりは 速度のボトルネック。もとのやつより速いと良いのだが...

  • ここまでのソースコード (Diamond プロジェクト込み) → qfn32samples-04.zip

     2012/06/17 更新: iVerilog 用テストベンチを追加し、自作AVR コアに適用できるようデバッグ。

     
  • rtavr では、C_OUT/HALF フラグの意味が 減算では反転している。( 1 で ボロー )
     
  • 論理演算では、C_OUT/HALF フラグ は無視してよい。( 結果が違っても良い) -- 実際違うので テストで比較に入れない。
     
  • EQ の意味を取り違えていた。
      ( F == 1111 で EQ = 1 だから Zero Flag とは関係なし。)
     
  • (alu4.v 自体は無変更)

関連記事
posted by すz at 13:27| Comment(0) | TrackBack(0) | CPLD

2012年06月06日

QFN32の FPGA

ふと、MachXO2 でググってみたら、あらたに 32pin QFN が出るらしい。



デジキーでも LCMXO2-256HC のエントリーが出来ている。製品ページを見ても、なにやら情報が出ている。

Mouser では、1個買いの単価が出ている

    330円 (4 グレード)
    371円 (5 グレード)
    418円 (6 グレード)
    まだ在庫なし。工場リードタイム すら出ていない。

      (2012/7/3) 6 グレードだけだが、リードタイムが出ていた。9 週間。価格も安くなっていた。
      244円 (4 グレード)
      279円 (5 グレード)
      314円 (6 グレード)
      256 なんだし、これぐらいでないと。お、4 グレード は、25個時 単価 214円 か。これぐらいだと、AVR とかのサポート用に使える。


  • I/O 数は 22 -- これはどういう計算なんだろう?

    BSDLファイル は既にある。VCCIO が 4 バンクあって 電源だけで 10 pin 。残りが I/O という計算らしい。

    JTAG を専用にすると .. 18 I/O 。それに、PROGRAMN/DONE/JTAGENB の扱いをどうするか。

WLCSP25 は、1200 なのに、QFN32 は、256 のみ。1200 だったら大喜びしてたのに... 残念。

... とここまで書いて、以前の自分の記事に QFN32 のことが載っていることに気がついた。 忘れていたのか。

まぁ、そのことは置いておいて、今は、実際に 電子工作で扱える小ピンの FPGA が出ることはすなおに嬉しい。



これは、ストロベリーリナックスの CP2103 USBシリアルボードだが、こんな風に変換基板を作ることができれば、ナローDIP FPGA ボードになる。ただし、作ることができるとは限らない。CP2103 は、QFN28 なので QFN32 だとより条件が厳しい。1個分のホールを潰せば、ナローDIP化はできそうだが、ランドが少ない。果たして手ハンダが可能なのか? リフローもやってみたいが、それしか方法がないのもどうかという気がする。

    QFNパッケージのはんだ付け

    この記事には、『位置合わせさえ確実であれば無洗浄タイプのフラックスを使ってはんだ付けが可能です。』と記されている。楽々だったとも。

    ランドは、標準より少し 広げているようだ。パッケージの端から 0.5mm ぐらい?

それは後にして、そもそも この LCMXO2-256HC は何に使えるのだろう?

  • 4LUT 数 256
  • ブロック RAM なし
  • I2C, SPI, タイアカウンタ ハードマクロあり
  • PLL なし
  • 内蔵 クロック あり

まぁ ALU ぐらいは出来るだろう。ならば 74HC181 ピン互換が可能になるような、ピン配置にして 24pin の DIP モジュールにするのはどうか? 出来れば、データシートすら、なかなか見つからない 74LS281 互換にもしてみたい。

『CPUの創りかた』で 74HC181 を使っているらしいのだが、入手困難らしい。価値のある使い方がひとつできることになる。ランドも大きくすることが出来て、手ハンダも可能になるはず。

    電源を 3.3V 専用にして、2 pin 分にし、残りの 22 I/O を出すことは可能。

    ただ、Config で JTAG を Disable にすると、JTAGENB が JTAG を強制的に enable するための専用ピンになる。-- 要するに I/O が足りず完全互換にするのは無理。

    出力のうち めったに使われない P または G に JTAGENB を割り当てることでごまかそう。

    あとの問題は、電圧範囲で 3.3V 専用になることぐらい。



    こちらは、aitendo の CP2103モジュールだが、ちょうど 24pin 。こんな風に部品を載せる余裕が随分ある。

そうそう 低容量(128B)の ROM/RAM にはなる。

    8KB ROM と 2KB RAM は、

    A7 (1) (24) VCC
    A6 (2) (23) A8
    A5 (3) (22) A9
    A4 (4) (21) A12 /WE
    A3 (5) (20) CS /OE
    A2 (6) (19) A10
    A1 (7) (18) A11 /CE
    A0 (8) (17) D7
    D0 (9) (16) D6
    D1 (10) (15) D5
    D2 (11) (14) D4
    GND (12) (13) D3

    こんな ピン割り当て。(9) または (11) に JTAGENB を割り当てるので、互換にするのは無理。

    D2 を A8/A9/A10 のどれか に持っていくしかないかな。

    低容量(128B)でも、CPU を創るなら役にはたつ。マイクロプログラムなら 容量よりビット幅だし。

    訂正: 256B が行けるかと思ったのだが、実際にやってみると 128B までしか作れなかった。

    1LUT は、16bit (2B) の容量のはずで、256 LUT あれば 512B 分。ロジックに使う分があるから 256B の計算だったのだが ...

あとは ... 折角 I2C やら SPI やら付いているのだから、なにか デバイスを作ってみるとか。CPUは、入らないから、ステートマシンで作ることになる。内蔵クロックは、最大 133 MHz なので、超高速 PWM ってのも可能。

差動入力もある。コンパレータがわりに使えるかも。

ところで、こいつの電圧範囲はどうなるのだろう? 上限が 3.3V なのは良いとして下限は?
VCCIO は、3.3V/2.5V/1.8V/1.2V は行けそうなのだが、Config で決めた電圧でないといけないのだろうか? それとも、半端な電圧でも良いのだろうか?

ちなみに、ライタは、FT232RL が使えるものを 作った。デバイスを追加しないといけないし、実際には FT232RL での動作を確認していないが、たぶん大丈夫。論理合成ツールは、Diamond 1.4.2 以降で対応済みだそうだ。

追記: 74281 (P/G なし)を合成してみる。

    ピン配置
    XO2-256 74281 74281 XO2-256
    27 A1 1 24 VCC
    28 A2 2 23 A0 25
    29 RS1 3 22 CP 23
    30 RS0 4 21 SIO0 21
    32 RC 5 20 AS0 20
    1 SIO3 6 19 AS1 17
    4 A3 7 18 AS2 16
    5 Cn 8 17 M 14
    8 ~G 9 16 F0 13
    9 Cn+4 10 15 F1 12
    26 ~P 11 14 F2 11
    GND 12 13 F3 10

    以前 74281 の コードを XC2C64A 向けに作ってあったので、試してみることにした。

    実は基板は、eagle で設計済みで、モジュールへのピンアサインも決めてしまっている。

    実際にピンを割り当てるときに、まず JTAG_PORT を DISABLE にしないといけなかった。次に JED ファイル生成でエラーになった。どうやら MUX_CONFIGURATION_PORTS を ENABLE にしないといけないらしい。

    Number of registers: 4
    Number of SLICEs: 24 out of 128 (19%)

    Number of logic LUT4s: 42
    Number of ripple logic: 3 (6 LUT4s)
    Total number of LUT4s: 48


    CPLDだと厳しかったが、小規模でも FPGA だけあって 楽勝 のようだ。74181 は作ったことがないが、これも P,G なしにするし 楽勝だろう。

    基板は、もうすこし練りたい。例えば TDO は出力 だが、JTAG を Disable にし忘れると 回路を壊してしまう恐れが ... 抵抗ぐらいは入れておきたい。他にもオプションとして仕込んでおきたいものもある。

    思ったより回路が入るようだし、サンプルをいくつか用意出来るといいなと思っている。

    ALU と ROM/RAM は作るとして、後は?

    4桁7セグを使った時計とか? 電流は多少流せるようだし、いけるかも。あと、周波数カウンタなんかも出来そう。

基板の設計(1)



    まだ部品も手に入らないわけだが、基板を設計してみた。

  • QFN は、デザイン優先で(というかワザを覚えたので)ナナメにマウント。
     ただ、ちゃんとしたパッドの作り方がわからないので、そこは適当。
     シルクもナナメを使ってみたり。

  • コネクタ部分は、0.8φで小さめの径。秋月の細ピンヘッダを想定。

  • 今回は、裏に部品を付けない。いずれ リフローやってみたい。

      ホットプレートを使うらしいが、小型の電気鍋 (鍋 MG-500 とか 鍋 KG-152 とかでググると多数みつかる) ではダメなんだろうか? 保管場所を取るのはちょっと避けたい。

  • I2C は、プルアップ抵抗を付けられるように。XO2 は、I2C からコンフィグできたりするので実験用。 (普通は)付けない。
  • TDO は、ミスで出力がぶつかるのを危惧して抵抗付き。LED を付けるのにも便利なように配慮。
  • セラミック(or 水晶)発振子を付けられるようにしてみた。
     もうひとつ端子間に抵抗を入れられるようにしてある。
     この 2 つ -- 1M Ωを入れてしまうと、使い方に制限が出るわけだが、私が作るものは、配慮する。

    いつもの gerbtool で画像はつくれなかった。ナナメの部品には対応できていないようだ。



      原因だけは、分かった。ナナメの部品を使うと G36 というコードが使われるようになる。これは、多角形の塗りつぶしを指定するもの。

      そんなアルゴリズムは持っていないので、おかしくなっていたのだった。とりあえず G36 で アパーチャ を小さな矩形にしたところ上のようになった。対応するのは面倒なので、放置になりそう。

  • xo2qfn-05.zip

    eagle の ソース。部品が手に入るまで寝かせておく。(最新版に更新)

      更新: JTAGENB の位置が違うパターンを追加。

  • ここまで作って 74HC181 に合わせられないことが分かった。P/G のピンアサインを確認したつもりが ... 違った。どうしたものか。...



    結局、74HC181 の互換にできるように変更。281 互換にできたとしても、設計できる人しか有用である可能性がない。設計できるなら、ピン互換であることは重要でないだろうし、そもそも こういうものは使わないだろう。

    JTAGENB は、G である 17pin に移動。

    ところで、74HC181の コードを作るのは (P/G なしでも) 結構面倒だと分かった。そもそも Cn+4 の論理が分からない。回路図があるんだから、追っていけば分かるのだろうけど。

    追記: 多角形のフィルに対応。ちょっとインチキで形に条件が付くのだが、多分大丈夫。



    ナローDIP 版も設計してみた。1pin 分潰すだけではダメで 2pin 分になった。でも DIP28 だしソケットは使える。ブレットボードなんかでは、N.C. があるのは逆に便利かも。信号の並びは、ワイド版と同じだが ... おまけの部品は付けられず。まだまだ FPGA が手に入るのは先だろうから、これをベースに練っていこう。



    追記: 練ってみたら、こんなものに ... 。ワイドとナロー両方いけるうえに、1 列だけのユニバーサルエリア付き。切ればナローにもなる。なにか不恰好だが、発注するならこれか。

  • gerbtool-0.8.zip

    多角形のフィルに対応したので、gerbtool 単体もアップデート。

74HC181 とかの コード

  • qfn32samples.zip

    書いてみただけで、ピンアサインも前の案ベースになっていたりするのだが、74281, 74HC181, ROM, RAM のコードをまとめた。

    74HC181 は、とりあえず、回路図のロジックをそのまま組んでみた。

    追記 : ALU については、別記事にした。→ 『TTL ALU 74181』 。
    結構わかってきたので、拡張したりしてみている。

  • qfn32samples-03.zip とりあえず更新版 (2012/06/12)

    ALU だけではなく、いろいろ作ってみたいと思っている。ちなみに、I2C や SPI のモジュールは、バスから使う仕様なので、マイクロコントローラが必須に思えてきた。なので、256 では無理。何故 256 にこんなものが付いているのか? .. とも思ったのだが、コンフィグに使えるから意味はあるのだろう。

    次は、7seg 使った時計 .. かな? 時計自体は楽勝で入るのだが ... 時刻設定のインターフェイスが必要。ここが難しそう。チャタリング対策も要りそうだし。

    追記: 『FPGA時計の設計』設計はできてしまった。思いの外簡単だった。規模もまだ余裕がある。

  • qfn32samples-04.zip 更新版 (2012/06/17)

    なんと 無理だと思っていた CPU の実装が出来た。EFB にもアクセス可能。ただしメモリは、プログラム 128B(最大) + RAM 16B 。これでまともなコードが書けるかというと 無理そう。もうひとひねり要りそうだ。

    あと、FPGA時計にアラーム機能を追加したり、ALU の検証をしたり。

    後はなにを設計しよう --- 周波数カウンタ? 全然違うものとしては、DC/DC コンバータ? 高速 PWM も可能だし、コンパレータも 差動入力の流用で可能。面白いかも。

      そう言えば 『簡易シグマデルタ ADC』なんてものが、リファレンス・デザインにある。規模は 54 LUT だそうだから、載らないことはなさそう。温度計とか作れないかな? 一体 どれぐらいの周波数(sps)で AD変換可能なのだろう? 手軽に買える範囲だと PIC32MX が 1.1Msps だそうだ。それ以上になると AD9283BRS-80 の 80Msps 。10 Msps 程度がない。6bit でも良いから 簡単にできるのなら、(各種)液晶を RGB モニタに出来るかも。 まぁこの FPGA で作る必要はないのだが、何種類か ADC のモジュールを持っていると応用範囲が広がりそうな気がする。

      ... 高性能なのは、やっぱり無茶か。温度計とかが精々?

      コンパレータとして使うには、LVDS25 などを設定する。設定できるピンには制限がある。xxxT2_0 , xxxC2_0 とか T/C が含まれるピンのペア。T が非反転入力で このピンにアサインする。C は反転入力で指定しなくとも自動で選ばれる。QFN32 では、4/5 , 11/12, 13/14, 21/20, 28/27 の 5 ペアのみ。

      コンパレータとして使う場合、入力電圧の範囲が広いかどうか? ドキュメントには、LVDS を使ったときは、わずかに 低くなると書いてある。 VCCIO - 0.5V まで?

      MachXO2 のデータシートには、 (VCCIO=3.3Vのとき)入力コモンモード電圧 は、0.05V 〜 2.6V と書いてあった。(2.5V のときは 0.05V 〜 2.0V ) あと差動入力閾値 は、± 100mV だそうだ。

      入力電圧の範囲が問題になるのは、非反転入力に ダイレクトに ADC入力 を接続する場合。オペアンプと同じような話で、抵抗を直列に入れて (ADC入力 + 生成電圧) を 仮想GND と比較する場合は関係なくなる。

      ところで ADC が出来たとして データを 10進数に変換するのは、どうしよう? MachXO2-256 の規模では、無理なんじゃないかと思えてきた。可能にする方法として思いつくのは、内部形式を 出力形式に合わせることぐらい。そんなことが出来るものなのかどうか?

追記(2012/6/21)

  • qfn32samples-06.zip 更新版 (2012/06/21)

    ADC は置いておいて ... DAC が出来ないか検討。なんかいけそうなので、コード追加。

    あと MCPU 。オリジナルベースは捨てて、rewrite 版のみ残した。何種類もあってもメンテできないし。

    ところで、Mouser で、1個買いの単価が出ているが、6 グレードが発注可能になった。リードタイムが9週間になっているが、ボチボチか。基板を発注する時期が近づいている。

    アナログ回路を組むなら、VCCIO のいくつかを別電源にしたいところ。なのだが ... ナロー型では、無理だった。ワイド型なら とも思ったのだが、なかなか難しい。VCCIO1 と VCCIO3 の 4pin 分をパターンカットで分離可能にするのが精一杯。

追記(2012/6/23)

    基板変更。VCC, VCCIO 0/2, VCCIO 1/3 をパターンカットで分離可能にした。VCCIO 1, VCCIO 3 だけもさらに分離可能。追加したコンデンサが 4 つになったが、全部裏側。従来どおり 追加コンデンサなしでも使えるのだが、位置の関係で、パターンをいくつかショートさせた方が良い ... という変なものに。まぁ 追加コンデンサ付けるし。

    電源は、VIA を寄せて 2mm ピッチのコネクタにしてみた。

    これをIteadStudio (新サイト)に、ついに発注。発注したのは 2 つで 最初に作ったワイド版と ナロー版。IteadStudio の DRC でチェックしたら、0.3mm のドリルが DRC でエラーになった。0.4mm に変更。

    送料は、$5.59 。

    6/30 : 発送の連絡

    実は、ナロー版について、『問題が出るかもしれない、そのときは相談させてくれ』という内容のメールをもらった。が、なにごともなく製造できたようだ。

    7月9日 20:27 国際交換支店に到着

    来ても眺めるしかないが、いよいよか。
    実を言うと Muser で 6 グレードのやつを 6/24 に発注した。8/27 に発送できる見込みだそうだ。

    7月13日 基板受け取り。

    来た。書くのを忘れていたのだが、両方 1mm 厚にした。大きい方は 10 枚ぴったりで、テープで巻いてあるだけだった。小さい方は 16 枚でパックされていた。

    一見しただけだが、とても作れそうに見えない。まぁチップが来たら頑張ってはみるが。

    8 月 1日 Shipment Notification がキタ。予定よりずいぶんはやい。



追記: 2012/8/26 ようやく組立て



    はんだ付けは、位置さえ決まれば、簡単だった。足がないためか .. ハンダがとなりとくっつかず切れが良い。吸い取り線を使うまでもなかった。

      (注意)簡単なのは、底面と側面のパッドがつながっているタイプなので QFN ならなんでも簡単というわけではない。あと PIC32MX にも QFN があって興味が出てきたのだが、こちらのピッチは、0.65mm 。基板を起こすときに注意しないと。

    今は、とりあえず組み立てただけ。PIC32MX が終わったら、こちらに戻ってくる予定。

    LC など部品についてすっかり忘れていた。

      R2/R3/R5 -- I2C のプルアップや、セラミック発振子を使うときピン間に入れる 1MΩ用。これは今後も使わないようなオプションなので付けない。

      右下のソケットと R1/C5/C6 は、水晶振子を付けるためのもの 1MΩ/22pF で一応付けてみた。

      忘れていたのが、ジャンパなのだが、左下 と 右(ナナメ)の VIA は、電源の引き回しがながいのを嫌う場合のショートカット用。裏面にパスコンを付けるなら不要だし、そもそも不要かも知れない。様子見するので、ショートカットしない。

      あと、上の2x3の VIA は、I/O電圧を 3.3V 以外にする場合に使う。ウラのパターンをカットして使うが、3.3V 以外は考えていないので変更なし。

      左の 300mil 幅のやつは、オプションがほとんどない。(あっても裏面を使う) 。付けたのは、コンデンサ 3 つのみ。

    作ったは良いがどう書き込むかについて。

      JTAGツールとSYNCBB』にある、rtavr_tools-0.10 を使って 書き込む予定だが、MachXO2-1200 しか対応していない。ちょっと修正が必要。

      あと、『MachXO2 1200ZE Breakout ボード』 も 少しの改造でライタにできる。Diamond から直接書き込めるからお薦め。JTAGENB を有効にする オプションで なにもしない回路をコンフィグし、JTAGENB を L にするスライドスイッチを付けるだけ。JTAG 用のコネクタがあるので、そこから 出力。JTAG をチェーンする方法もあるが、パターンカットとか配線とかが面倒。

eagle の ソース

関連記事
posted by すz at 22:11| Comment(0) | TrackBack(0) | MachXO2