2012年03月09日

MachXO2のJTAG通信

MachXO2 , MachXO ともに JTAG を通して SPI (のような方法)で通信できることになっている。MachXO2 breakout ボードでは、MPSSE で JTAG と接続されているから、理想的には、30Mbps までの速度での通信が可能。シリアルのように 外付けクロックにも依存しないからお手軽でもある。

  • LatticeXP2のFPGA回路内部へJTAGで通信する

    これは、XP2 に付いての記事だが、MachXO2もほぼ同じらしい。XP2 では、JTAGE を使うが、MachXO2 では、JTAGF , MachXO では、JTAGD を使う。

    /c/lscc/diamond/1.4/cae_library/synthesis/verilog/machxo2.v

    このあたりを見ると、インターフェイスがわかる。HOST 側での使い方はまったく同じ。

というわけで、通信しようとしているのだが、なかなか思うようにいっていない。何が間違いなのか分からなかったのだが ...どうも根本的なミスをしていたらしい。

    そもそも SPI は、双方の出力は同じタイミングで切り替わる。JTAG 経由の場合もそれは同じ。だから、8bit 目の SCK の立ち上がりで、入力処理をしたら、SCK が立ち下がるまでに次の出力処理も完了させなければならない。
    (補足: 普通に作ると出力が先行する。8 bit のデータを受け取る前に、出力を確定させないといけないが、そうはしたくないのが前提としてある。)

    だいたいが、そういう処理にしていなかった。だが、次の条件があり、なかなか面倒なのだ。

    上の記事には、SCK と MOSI は同時に変化するから、SCK の立ち上がりをトリガーにする 『@(posedge SCK)』 と MOSIは読み込めない。ようなことが書いてある。

で、困ったわけだが ... もっと高い周波数の CLK で 駆動して SCK MOSI ともにデータとして扱うことにした。(SCK はエッジを検出して処理)。もともと ISP として使いたいので、CLK が別にあるのが前提になるから、こうした方が都合がよい。

SCK と MOSI は同時に変化するので、SCK は、CLK でサンプリングして遅延させるようにする。具体的には、top モジュールで次のようにする。

    always @(posedge CLK)
    begin
    r_jtck_prev <= JTCK_IN;
    if (r_jtck_prev == JTCK_IN)
    r_jtck <= r_jtck_prev;
    end
    wire JTCK1 = r_jtck;
    wire JTDI = JTDI_IN;

JTCK_IN だけ ひとまず CLK で同期する。2 クロック連続で同じ値ならという論理なので、これだけで遅延されている。次に下位モジュールで、半クロックずらして処理をする。

    always @(negedge CLK)
    begin
    r_sck <= SCK;
    if (CS)
    :
    else if (~r_sck & SCK) // posedge SCK : INPUT
    begin
    入力処理
    :
    end
    end

これで、JTDI_IN の値は読めるはず。さて、SCK の立ち上がりですべてを行わないといけない 。これは次のようにした。

    else if (~r_sck & SCK) // posedge SCK : INPUT
    begin
    req_shift <= 1'b1;
    入力処理
    end
    else if (req_shift) // next
    begin
    req_shift <= 1'b0;
    :
    出力処理
    end

CLK を複数クロック使って 処理するわけだ。通信速度は、CLK の 1/10 以下でないとダメそうだが、CLKは普通 10 MHz 以上で、1Mbps 程度ならいける。
 
CLK
___ ___ ___ ___ ___ ___ ___ ___ ___
__| |___| |___| |___| |___| |___| |___| |___| |___| |___|

: : :
SCK : : :
_______________________________ ___
_____| |________________________________|


r_valid : : :
_________________________________________________________________
_________| |
: : :
r_req : : :
_______
_________| |_________________________________________________________
@ SPI_DATA_I input

これは、8bit 目を受け取ったときのタイミング。出力処理を他のモジュールで行うなら、CLK の立下りで 行うのが良さそう。

    always @(negedge CLK)
    begin
    if (SPI_DATA_REQ)
    r_data <= SPI_DATA;
    end
    assign SPI_DATA_I = r_data;

こんな風にして、エコーするだけのものは、動いた。(ラッチは必要なく、入出力をつないでも動く。)

実際の応用回路として、ISP を作ろうとしているのだが、こちらは、うまく動いてくれない。

SCK
___ ___ ___ ___ ___ ___ ___ ___
__| |___| |___| |___| |___| |___| |___| |___| |.....
: :
CS _______
|_____________________________________________________.....
: :
data
<bit7 ><bit6 ><bit5 ><bit4 ><bit3 ><bit2 > .....


随分長い間悩んでいたのだが、CS が L になってから 余計な 1bit があるのが原因だった。エコーするだけのものは ずれても問題ないのだが、通信できていると思い込んでいたのが敗因。


CLK
___ ___ ___ ___ ___ ___ ___ ___
__| |___| |___| |___| |___ .... __| |___| |___| |___| |___|

SPI_DATA_REQ
_______ _______
_________| |______________________ .... ___| |__________

r_req
_______ _______
_____________| |__________________ .... ______| |______

(a) (b) (c) (d)

コマンド データ

ISP のモジュールを設計しているのだが、こんな風に 2 バイト のデータ (コマンド , データ) 組みにしようとしている。処理のタイミングは それぞれ 2 つ作る。

(a) , (c) で 出力をラッチすると SPI_DATA_REQ の立下りで受け取られて 次の フェーズで HOST に出力される。(a), (b) で入力データを受け取ることが出来るが、(a) で解析してすぐに次のデータを決めるのは、タイミング的に厳しい。あらかじめ決めておいたデータを送ることにしよう。

といっても、アドレスが決まるのは、(c) でデータを受け取った後。演算する場合もあるので、(d) になる。

    ここで ISP の仕様を書いておく。

    // SSTPR 0110 100a PR[a] ( PR[0] : low byte of PR )
    // XXXX XXXX XXXX XXXX ( PR[1] : high byte of PR )
    //
    // SLD 0010 0I00 XXXX XXXX ( if I == 1 , PR = PR +1 )
    // XXXX XXXX load_data
    //
    // SST 0110 0I00 store_data ( if I == 1 , PR = PR +1 )
    // XXXX XXXX XXXX XXXX

    まぁたいしたことはない。ポインタをセットする命令と、ロード・ストアがあるのみ。


always @(posedge CLK)
begin
if (CS)
:
else
begin
r_req <= SPI_DATA_REQ;
(a) if (SPI_DATA_REQ & ~SPI_COUNT[0])
begin
r_data_out <= ISP_LOAD_DATA;
end
(b) if (r_req & ~SPI_COUNT[0])
begin
r_pr_inc <= f_pr_inc;
r_sstpr_hi <= f_sstpr_hi;
r_sstpr_lo <= f_sstpr_lo;
r_store <= f_store;
end
(c) if (SPI_DATA_REQ & SPI_COUNT[0])
begin
r_data_out[0] <= r_pr_inc;
r_data_out[1] <= r_load;
r_data_out[2] <= r_store;
r_data_out[3] <= 1'b1;
r_data_out[7:4] <= SPI_DATA[7:4];
end
(d) if (r_req & SPI_COUNT[0])
begin
if (r_pr_inc) r_pr <= r_pr + 1;
else if (r_sstpr_hi) r_pr[15:8] <= ISP_STORE_DATA;
else if (r_sstpr_lo) r_pr[7:0] <= ISP_STORE_DATA;
end
end

コードの一部を紹介するとこんな風になった。

ちなみに、コードで ISP_STORE_DATA となっているのは、SPI からの入力データで、ISP_LOAD_DATAは、メモリからの読み出しデータ。

f_pr_inc などは、デコードの結果。

HOST 側のコード

    HOST 側のコードは、随分前から用意してある。(rtavr_tools-0.x.tar.gz)。ちょっと解説。

    もともとは、Xilinx の自作ボード用として作っていたものだが、MachXO/MachXO2 にも一部対応。ISP を使ってプログラムメモリを書き換えるのと config の 両方の機能を想定している。

    MachXO2 は、FT2232H なので MPSSE が使えるのだが、FT232R でも使えるように Bitbang モードを使っている。

    使い方なのだが、少々くせがある。

    target_load タイプ [サイズ]

    これで、バッファに タイプで指定したものから読み込む。

    fpga_ram fpga_flash spi_flash rtavr_isp

    タイプには、以上のものを指定できるが、コードが出来ているのは、rtavr_isp のみ。

    erase タイプ
    program タイプ
    verify タイプ

    書き戻したり、ベリファイする場合は、こういう風に指定する。

    ファイルから読み込む場合は、次のコマンド。

    file_load [ファイルタイプ] file
    filetype: bit jed mcs mem

    MachXO2 に関係するのは、jed と mem 。isp の場合は、mem を使う。jed をコンフィグするコードも用意してあるが、未テスト。... というか未完成。

    mcs も読み込めはするが、SPI FLASH に書き込むには、先に専用の config を書きこまないとだめで、コードは全然。構想では、RAM のみ config して ... みたいなことを考えていたのだが ... MachXO2 では RAM 用のデータを用意するのが面倒そう。

    ちなみに、Xilinx も JTAG を通して通信することが可能。Xilinx 用のコードも入れてはあるのだが、実際に動かすのは随分先の話になりそう。

    他に テスト用のコードも入れてあって

    spi_test
    isp_test

    のコマンドがある。ついでなので、API に付いてもちょっと紹介。

    isp_test(TARGET tgt, CABLE cbl) {

    uint32_t s,r;

    jtag_reset(cbl);
    (tgt->sel_chan)(tgt, cbl, ISP_CHAN);

    s = (TPI_SETPR_LO << 8);
    jtag_SDR32R(cbl, s, 16, JTAG_RECV|JTAG_CONT, &r);
    :

    こんな感じで使えるようになっている。jtag_SDR32R が MSB first の SPI 通信で 32bit までを指定できる。JTAG_CONT は、最後に TMS を H にしないオプションで SPI 通信では必須。

    JTAG_RECVを指定しないと、戻り値不要。書き方も変わりバッファリングされる。

    tgt, cbl の初期化は、ちょっと面倒なので割愛。

rtavr_tools 最新版

  • rtavr_tools-0.7.tar.gz

    上記の内容に対応するコードを rtavr_tools 最新版として置いておく。


    rtavr_tools-0.7/
    /src -- HOST 側プログラム (MinGW , Linux 用)
    /rtavr_tools.exe -- プログラム 実行形式 (CUI)
    /test_hdl/ -- HDL ソース (tool_test.v , jtag_spi.v , isp.v)
    /out/test_hdl_xo2.jed -- MachXO2 breakout ボード用 (CLK 10 MHz)


    さて、これでようやく、ISP のベースが出来た。これがないと rtavr のデバッグが出来るような気がしなかった。ぼちぼち rtavr も実機でのデバッグを進めていこうと思う。

    あと、この ISP は、内部のメモリ(と アドレス可能なデータ)を読み書きするものでもある。調停がまだ出来ていないが、ロジアナとかフレームバッファの通信に応用できるはず。これをベースにしてなにか作ってみたい。

    fpga flash への 操作も できたほうが便利そうな気も。 開発のフェーズによっては、いちいち Diamond で書くのも面倒だろうし。プログラムとハードウェア(HDL) を入れ替えてデバッグしたりするとき なんかは役に立ちそう。ちなみに、fpga flash への 操作は、SVF を作って解析して作る。根気はいるが難しいことではない。気が向いたときでもやっておこう。

タイミング変更

    せっかく動かせたのだが、JTAG 通信の都合に合わせていったので、タイミングはやはり標準的なものではなくなっている。いずれは、rtavr の SPI モジュールに接続したりもしたいので、これでは困るのだ。それで、大胆な変更をあえて行うことにした。

    変更するのは以下のところ。

    • CS の遅延

      CS と通信開始までの 1 クロック(SCK) の空白は 困るので、CS を遅延させることにした。

    • 入力/出力のタイミング

      ISP 向けのコードを作って 入力 した後に 出力を確定できるという特徴は利用しなかった。タイミングが厳しくなるのが嫌なのが、その理由。そうであれば、出力先行で 普通のタイミングにした方が良い。それに合わせて SCK も反転させる。

    • SPI_DATA_VALID の有効化

      いままで SPI_DATA_REQ だけで制御してきたが、タイミングが全く変わるので SPI_DATA_VALID も 使うようにする。


    変更後 タイミング

    ___ __________________ _____
    SCK |___________________| |_________________|

    SPI_DATA_VALID: ____________________________________
    _______________________| |____

    SPI_DATA_REQ:
    _ _
    ___| |______________________________________________________| |__

    コードも随分変わったが、すんなり動かせた。今後は新方法に全面置き換え。以前のコードについて せっかく説明も書いたのだが、rtavr_tools-0.7 のみのコードになる(予定)。

付録: 外部 SPIデバイスとの通信

    `define TARGET_MachXO2
    //`define TARGET_MachXO
    //`define TARGET_LatticeXP2

    module spi_lattice (
    // JTAG
    input TOP_TDI
    , input TOP_TCK
    , input TOP_TMS
    , output TOP_TDO
    // EXTERNAL SPI PORT
    , output CS
    , output SCK
    , output SI
    , input SO
    );

    `ifdef TARGET_MachXO
    `define JTAG_BSCAN JTAGD
    OSCC osc_internal (.OSC(CLK)); // 18 MHz - 26 MHz
    `elsif TARGET_MachXO2
    `define JTAG_BSCAN JTAGF
    OSCH #(.NOM_FREQ("24.18")) osc_internal
    (.OSC(CLK), .STDBY(1'b0));
    `elsif TARGET_LatticeXP2
    `define JTAG_BSCAN JTAGE
    OSCE #(.NOM_FREQ("20.0")) osc_internal
    (.OSC(CLK));
    `endif

    `JTAG_BSCAN bscan (
    .JSHIFT(BSCAN_SHIFT)
    // .JRSTN(BSCAN_RESET) // H : in TestLogicReset State
    // , .JUPDATE(BSCAN_UPDATE)

    , .TDI(TOP_TDI)
    , .TCK(TOP_TCK)
    , .TMS(TOP_TMS)
    , .TDO(TOP_TDO)

    , .JTDI(JTDI_IN)
    , .JTCK(JTCK_IN) // negedge capt, posedge shift

    // signals for IPA
    , .JRTI1(JRTI1)
    , .JCE1(SEL1)
    , .JTDO1(JTDO1)

    // signals for IPB
    , .JRTI2(JRTI2)
    , .JCE2(SEL2)
    , .JTDO2(JTDO2)
    );

    reg r_jtck;
    reg r_jtck_prev;
    reg r_cs1;
    reg r_cs1_prev;
    reg r_cs2;
    reg r_cs2_prev;

    always @(posedge CLK)
    begin
    r_jtck_prev <= JTCK_IN;
    if (r_jtck_prev == JTCK_IN)
    r_jtck <= ~r_jtck_prev;
    if (r_jtck_prev & r_jtck & (r_jtck_prev == JTCK_IN)) // negedge JTCK
    begin
    r_cs1_prev <= ~(SEL1 & BSCAN_SHIFT);
    r_cs1 <= r_cs1_prev;
    r_cs2_prev <= ~(SEL2 & BSCAN_SHIFT);
    r_cs2 <= r_cs2_prev;
    end
    end
    wire JTCK1 = r_jtck;
    wire JTCK2 = r_jtck;
    wire JTDI = JTDI_IN;
    wire CS1 = r_cs1;
    wire CS2 = r_cs2;

    assign CS = CS1;
    assign SCK = JTCK1;
    assign SI = JTDI;
    assign JTDO1 = SO;

    endmodule

    たぶん、このコードで XO/XO2/XP2 で JTAG を通じて SPI デバイスと通信できる。XO2 で確認済みのコードをベースにしているのでたぶん大丈夫だろう。

    ちなみに、XP2 / XO2 だと SPI FLASH からのブートが可能。接続した SPI FLASH にプログラムするためには、こういったものを 一時的に config する必要がある。(バウンダリスキャンでも良いが遅い)

    XilinX も似た様なことは可能だが、内蔵クロックを使えないので、内蔵クロックを使わない やりかたに変更しないといけない。
posted by すz at 00:24| Comment(0) | TrackBack(0) | MachXO2
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

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


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

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