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) になる。
// 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 も似た様なことは可能だが、内蔵クロックを使えないので、内蔵クロックを使わない やりかたに変更しないといけない。