Kintex-7でPCI Expressの回路を作っています。
まず、CoreGeneratorでPCI Expressのコアを生成すると、下の図のような構造のファイルが作られます。
これらのファイルはCoreGenのXCOファイルをプロジェクトに追加しているときには見えません。XCOを外して、\ipcore_dir\pcie_7x_v1_9\example_design ディレクトリの中にあるファイルをプロジェクトに追加すると、見えるようになります。
生成されたコアは、pcie_7x_v1_9というブロックと、pcie_app_7xという2つの大きなブロックに分けられ、pcie_7x_v1_9にはEndPoint BlockやGTXなど、人間が触る必要がない部分が入っています。
ユーザが作るべき回路はpcie_appに入っています。CoreGenで自動生成すると簡単なPIOのサンプルが作られているので、これをもとに改良していくことにします。
今回は受信回路の動作を解析します。受信回路はPIO_RX_ENGINE.vhd です。
ホストパソコンからMemory Writeを行ってみたときの波形をMITOUJTAGでキャプチャしたものを下図に示します。
XILINXのコアは、受信したデータm_axis_rx_tdataに乗せ、m_axis_rx_tvalidとm_axis_rx_tlastを使ってデータの有無とパケットの終端を示してきます。
XILINX 7シリーズのPCIeコアはデータバスが64bitです。m_axis_rx_tdataからは40000001 0000000F F0600000 00010203という4DWのデータが出てきていることがわかります。
パケットの先頭を示すフラグはないので、PIO_RX_ENGINE.vhdの中ではin_packet_qという信号と、sopという信号を作ってパケットの先頭の位置を割り出しています。
in_packet_qはm_axis_rx_tvalidでアサートされ、m_axis_rx_tlastでデアサートされます。もちろん、ユーザ回路が準備可能であることを示すtready_intも絡んできます。
sopが'1'のとき、パケットの先頭であり、このときのデータバスの[31:0]の値を見て、パケットの種類を判別しています。ステートマシンのコード(state_decode)も0→2→8→0と動いています。上の波形では40000001なので、長さ1のメモリライトとして解釈しています。
しかし、次の場合はどうでしょう。これは長さ64バイトのCombied Writeを発行したときの波形です。メモリライトなのですが、先頭の4DWは40000010となっていて、長さフィールドが0x10です。
この場合、ステートマシンが反応していません。
その原因は、下記のコードにあります。
if ((sop = '1') and (m_axis_rx_tvalid = '1') and (m_axis_rx_tready_int = '1')) then
case (m_axis_rx_tdata(30 downto 24)) is
when RX_MEM_RD32_FMT_TYPE =>
m_axis_rx_tready_int <= '0' after TCQ;
if (m_axis_rx_tdata(9 downto 0) = "0000000001") then
state <= PIO_64_RX_MEM_RD32_DW1DW2 after TCQ;
else
state <= PIO_64_RX_RST_STATE after TCQ;
end if;
when RX_MEM_WR32_FMT_TYPE =>
m_axis_rx_tready_int <= '0' after TCQ;
if (m_axis_rx_tdata(9 downto 0) = "0000000001") then
state <= PIO_64_RX_MEM_WR32_DW1DW2 after TCQ;
else
state <= PIO_64_RX_RST_STATE after TCQ;
end if;
先頭のデータの[9:0]は長さフィールドで、これが1の場合しかステートマシンが反応しないようになっています。これゆえ、Combined Writeのときのように4バイトを超えるデータを書き込もうとしたときに対応できないのです。
つまり、書き込みパケットが4バイトに制限されるから、ものすごく遅い。PCI Expressの速度のメリットがないコアであるといえるでしょう。
どうすればよいかというと、まず、最初の条件のところから長さに関する制約を取り払います。そして、長さフィールドをremainという新たに作った信号に代入します。
when RX_MEM_WR32_FMT_TYPE =>
m_axis_rx_tready_int <= '0';
remain <= m_axis_rx_tdata(9 downto 0);
state <= PIO_64_RX_MEM_WR32_DW1DW2;
そして、ステートがPIO_64_RX_MEM_WR32_DW1DW2に遷移したら、次のような内部ステートfstateを作ってここにとどまります。
最初の1回目のときには書き込みアドレスをフェッチする。二回目以降は書き込みデータをフェッチする。3回目以降は書き込みアドレスをインクリメントしていきます。
when PIO_64_RX_MEM_WR32_DW1DW2 =>
if (m_axis_rx_tvalid = '1') then
m_axis_rx_tready_int <= not m_axis_rx_tready_int;
if(fstate = "00") then
wr_addr_p <= region_select(1 downto 0) & m_axis_rx_tdata(10 downto 2);
fstate <= "01";
else
fstate <= "10";
if(m_axis_rx_tready_int = '1') then
wr_data_p <= m_axis_rx_tdata(63 downto 32);
else
wr_data_p <= m_axis_rx_tdata(31 downto 0);
end if;
remain <= remain - 1;
wr_en_p <= '1';
if(fstate = "10") then
wr_addr_p(8 downto 0) <= wr_addr_p(8 downto 0) + 1;
end if;
if(remain = 1) then
state <= PIO_64_RX_WAIT_STATE;
end if;
end if;
else
state<= PIO_64_RX_MEM_WR32_DW1DW2;
end if;
また、データバスm_axis_rx_tdataは64ビットですが、ユーザ回路には32bitで渡すので、上と下とに分けて受け取らなければなりません。この回路では、データを受信するためのこのステートでm_axis_rx_tready_intをバタバタと動かして、AXIバスに2サイクルに1回だけデータを送るようにさせています。
このようにして、長いデータも受信することができるようになりました。
PCI ExpressのGen1でつないだ場合、170MB/secくらい出ているようです。
最近のコメント