« 2013年6月 | トップページ | 2013年8月 »

2013.07.31

AXIのWriteを作った

AXI-GPIOは、クロックやストローブがなくアドレスもないので、複数のレジスタに書き込めるようにしたり、ハンドシェイクをするにはGPIOのどこかのbitを使うということになるのですが、そんなことするくらいなら、AXI-GPIOではなくAXI Slaveそのものを使ったほうがよいと気が付きました。

そこで、ZYNQ用にAXI Slaveの受け側を作ってみることにしました。

まず、AXIのWriteを受け取れるようにしようと思います。

AWREADY(アドレス受信可)とWREADY(データ受信可)を'1'に固定し、データを受け取ったらBVALIDを'1'にします。

ソフトでは

volatile unsigned long *tmp = (volatile unsigned long *)(XPAR_AXI_EXT_SLAVE_CONN_0_S_AXI_RNG00_BASEADDR);
*tmp = count++;

と、ポインタでメモリアクセスします。

すると、

Axi_write

こんな感じで、AWVALID(アドレス出力有効)とWVALID(データ出力有効)が同時に出ていました。

AWVALIDとWVALIDが必ず同時にでるかどうか確証が得られません。場合によっては、AWVALIDが出てから数クロックしてからWVALIDが出るということがあるかもしれません。

そのため、アドレスをデコードしてから、そのあとでWVALIDを受け取るようにしたいのですが、同時に出てしまうとそうもいきません。そこで、WREADYを制御することにしました。

次に作った波形がこれ。

Axi_write1

AWVALIDが出てアドレスを受け取ったら、WREADYを上げるようにしました。

これでアドレス→データという順序で確実に受け取れます。

さて、ソフトでlong long型のポインタでアクセスしてみると、

volatile unsigned long long *tmpll = (volatile unsigned long long*)(XPAR_AXI_EXT_SLAVE_CONN_0_S_AXI_RNG00_BASEADDR);
*tmpll = count++;

Axi_write2

このようにバースト転送が発行されました。しかし、書き込みアドレスAWADDRは先頭のアドレスを指したままで自動的には更新されていないようです。

そこで、アドレスはZYNQが出してくるAWADDRをそのまま使うのではなく、いったん自分の信号としてキャプチャしてから、使うようにしました。

最終的な波形はこちら。まず、シングルライトの場合。3サイクルで処理しています。

Axi_write3

次にバーストライトの場合。4サイクルでアクセスしています。

Axi_write4

また、アドレスも自動的にインクリメントするようにしました。これで、シングル転送でもバースト転送でも受信できます。それに処理中はAWVALIDを下げておくようにしているので、他のトランザクションが同時にきても排他的にアクセスできるでしょう。

最終的に出来上がったコードをこちらに記載しておきます。

process(plclk) begin
    if(plclk'event and plclk = '1') then
        if(axis_reset = '0') then -- リセットで、アドレスはレディ、データは非レディに設定
            axis_WREADY_i <= '0';
            axis_AWREADY_i <= '1';
        else
            if(axis_AWVALID_o = '1') then -- アドレスが来たらキャプチャ
                axis_addr <= axis_AWADDR_o;
                axis_BID_i <= axis_AWID_o; -- BIDにはAWIDをコピー
            elsif(axis_WREADY_i = '1') and (axis_WVALID_o = '1') then
                axis_addr <= axis_addr + 4; -- データを受け取ったらアドレスを4増やす
            end if;             if(axis_AWVALID_o = '1') then -- アドレスを受け取ったら
                axis_WREADY_i <= '1'; -- データを受け取りレディ
                axis_AWREADY_i <= '0'; -- アドレスはもう受け取らない
            elsif(axis_WREADY_i = '1') and (axis_WVALID_o = '1') and (axis_WLAST_o = '1') then -- 最後のデータを受け取ったら
                axis_WREADY_i <= '0'; -- データはもう受け取らない
            elsif(axis_BVALID_i = '1') and (axis_BREADY_o = '1') then -- レスポンスを返したら
                axis_AWREADY_i <= '1'; -- アドレスを再び受け取る
            end if;             -- 最後のデータを受け取ったら
            if(axis_WREADY_i = '1') and (axis_WVALID_o = '1') and (axis_WLAST_o = '1') then
                axis_BVALID_i <= '1'; -- レスポンスを返す
                axis_BRESP_i <= "00"; -- 正常終了の意味?
            elsif(axis_BREADY_o = '1') then -- レスポンスを受け取ってくれた
                axis_BVALID_i <= '0'; -- レスポンスを下げる
            end if;             if(axis_WREADY_i = '1') and (axis_WVALID_o = '1') then -- データを受け取った
                case axis_addr(7 downto 0) is
                    when x"00" => -- addr xxxxxx00
                        if(axis_WSTRB_o(0) = '1') then
                            led_op <= axis_WDATA_o(7 downto 0);
                        end if;
                    when x"04" => -- addr xxxxxx04
                        reg_04 <= axis_WDATA_o(7 downto 0);
                    when others =>
                end case;
            end if;
        end if;
    end if;
end process;
cpu_i : cpu
    port map (
        processing_system7_0_MIO => MIO,
        processing_system7_0_PS_SRSTB_pin => PS_SRSTB_pin,
        processing_system7_0_PS_CLK_pin => PS_CLK_pin,
        processing_system7_0_PS_PORB_pin => PS_PORB_pin,
        processing_system7_0_DDR_Clk => DDR_Clk,
        processing_system7_0_DDR_Clk_n => DDR_Clk_n,
        processing_system7_0_DDR_CKE => DDR_CKE,
        processing_system7_0_DDR_CS_n => DDR_CS_n,
        processing_system7_0_DDR_RAS_n => DDR_RAS_n,
        processing_system7_0_DDR_CAS_n => DDR_CAS_n,
        processing_system7_0_DDR_WEB_pin => DDR_WEB,
        processing_system7_0_DDR_BankAddr => DDR_BankAddr,
        processing_system7_0_DDR_Addr => DDR_Addr,
        processing_system7_0_DDR_ODT => DDR_ODT,
        processing_system7_0_DDR_DRSTB => DDR_DRSTB,
        processing_system7_0_DDR_DQ => DDR_DQ,
        processing_system7_0_DDR_DM => DDR_DM,
        processing_system7_0_DDR_DQS => DDR_DQS,
        processing_system7_0_DDR_DQS_n => DDR_DQS_n,
        processing_system7_0_DDR_VRN => DDR_VRN,
        processing_system7_0_DDR_VRP => DDR_VRP,
        processing_system7_0_FCLK_CLK0_pin => plclk,
        processing_system7_0_M_AXI_GP1_ARESETN_pin => axis_reset,
        axi_gpout_GPIO_IO_O_pin => gpout,
        axi_gpin_GPIO_IO_I_pin => gpin,
        axi_ext_slave_conn_0_M_AXI_AWID_pin    => axis_AWID_o,
        axi_ext_slave_conn_0_M_AXI_AWADDR_pin  => axis_AWADDR_o,
        axi_ext_slave_conn_0_M_AXI_AWLEN_pin   => axis_AWLEN_o,
        axi_ext_slave_conn_0_M_AXI_AWSIZE_pin  => axis_AWSIZE_o,
        axi_ext_slave_conn_0_M_AXI_AWBURST_pin => axis_AWBURST_o,
        axi_ext_slave_conn_0_M_AXI_AWVALID_pin => axis_AWVALID_o,
        axi_ext_slave_conn_0_M_AXI_AWREADY_pin => axis_AWREADY_i,
        axi_ext_slave_conn_0_M_AXI_WDATA_pin   => axis_WDATA_o,
        axi_ext_slave_conn_0_M_AXI_WSTRB_pin   => axis_WSTRB_o,
        axi_ext_slave_conn_0_M_AXI_WLAST_pin   => axis_WLAST_o,
        axi_ext_slave_conn_0_M_AXI_WVALID_pin  => axis_WVALID_o,
        axi_ext_slave_conn_0_M_AXI_WREADY_pin  => axis_WREADY_i,
        axi_ext_slave_conn_0_M_AXI_BID_pin     => axis_BID_i,
        axi_ext_slave_conn_0_M_AXI_BRESP_pin   => axis_BRESP_i,
        axi_ext_slave_conn_0_M_AXI_BVALID_pin  => axis_BVALID_i,
        axi_ext_slave_conn_0_M_AXI_BREADY_pin  => axis_BREADY_o,
        axi_ext_slave_conn_0_M_AXI_ARID_pin    => axis_ARID_o,
        axi_ext_slave_conn_0_M_AXI_ARADDR_pin  => axis_ARADDR_o,
        axi_ext_slave_conn_0_M_AXI_ARLEN_pin   => axis_ARLEN_o,
        axi_ext_slave_conn_0_M_AXI_ARSIZE_pin  => axis_ARSIZE_o,
        axi_ext_slave_conn_0_M_AXI_ARBURST_pin => axis_ARBURST_o,
        axi_ext_slave_conn_0_M_AXI_ARVALID_pin => axis_ARVALID_o,
        axi_ext_slave_conn_0_M_AXI_ARREADY_pin => axis_ARREADY_i,
        axi_ext_slave_conn_0_M_AXI_RID_pin     => axis_RID_i,
        axi_ext_slave_conn_0_M_AXI_RDATA_pin   => axis_RDATA_i,
        axi_ext_slave_conn_0_M_AXI_RRESP_pin   => axis_RRESP_i,
        axi_ext_slave_conn_0_M_AXI_RLAST_pin   => axis_RLAST_i,
        axi_ext_slave_conn_0_M_AXI_RVALID_pin  => axis_RVALID_i,
        axi_ext_slave_conn_0_M_AXI_RREADY_pin  => axis_RREADY_o
);

宣言部はこんな感じです

    component cpu is
    port (
        processing_system7_0_MIO : inout std_logic_vector(53 downto 0);
        processing_system7_0_PS_SRSTB_pin : in std_logic;
        processing_system7_0_PS_CLK_pin : in std_logic;
        processing_system7_0_PS_PORB_pin : in std_logic;
        processing_system7_0_DDR_Clk : inout std_logic;
        processing_system7_0_DDR_Clk_n : inout std_logic;
        processing_system7_0_DDR_CKE : inout std_logic;
        processing_system7_0_DDR_CS_n : inout std_logic;
        processing_system7_0_DDR_RAS_n : inout std_logic;
        processing_system7_0_DDR_CAS_n : inout std_logic;
        processing_system7_0_DDR_WEB_pin : out std_logic;
        processing_system7_0_DDR_BankAddr : inout std_logic_vector(2 downto 0);
        processing_system7_0_DDR_Addr : inout std_logic_vector(14 downto 0);
        processing_system7_0_DDR_ODT : inout std_logic;
        processing_system7_0_DDR_DRSTB : inout std_logic;
        processing_system7_0_DDR_DQ : inout std_logic_vector(31 downto 0);
        processing_system7_0_DDR_DM : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_DQS : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_DQS_n : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_VRN : inout std_logic;
        processing_system7_0_DDR_VRP : inout std_logic;
        processing_system7_0_FCLK_CLK0_pin : out std_logic;
        processing_system7_0_M_AXI_GP1_ARESETN_pin : out std_logic;
        axi_gpout_GPIO_IO_O_pin : out std_logic_vector(31 downto 0);
        axi_gpin_GPIO_IO_I_pin : in std_logic_vector(31 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWID_pin : out std_logic_vector(11 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWADDR_pin : out std_logic_vector(31 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWLEN_pin : out std_logic_vector(7 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWSIZE_pin : out std_logic_vector(2 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWBURST_pin : out std_logic_vector(1 downto 0);
        axi_ext_slave_conn_0_M_AXI_AWVALID_pin : out std_logic;
        axi_ext_slave_conn_0_M_AXI_AWREADY_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_WDATA_pin : out std_logic_vector(31 downto 0);
        axi_ext_slave_conn_0_M_AXI_WSTRB_pin : out std_logic_vector(3 downto 0);
        axi_ext_slave_conn_0_M_AXI_WLAST_pin : out std_logic;
        axi_ext_slave_conn_0_M_AXI_WVALID_pin : out std_logic;
        axi_ext_slave_conn_0_M_AXI_WREADY_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_BID_pin : in std_logic_vector(11 downto 0);
        axi_ext_slave_conn_0_M_AXI_BRESP_pin : in std_logic_vector(1 downto 0);
        axi_ext_slave_conn_0_M_AXI_BVALID_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_BREADY_pin : out std_logic;
        axi_ext_slave_conn_0_M_AXI_ARID_pin : out std_logic_vector(11 downto 0);
        axi_ext_slave_conn_0_M_AXI_ARADDR_pin : out std_logic_vector(31 downto 0);
        axi_ext_slave_conn_0_M_AXI_ARLEN_pin : out std_logic_vector(7 downto 0);
        axi_ext_slave_conn_0_M_AXI_ARSIZE_pin : out std_logic_vector(2 downto 0);
        axi_ext_slave_conn_0_M_AXI_ARBURST_pin : out std_logic_vector(1 downto 0);
        axi_ext_slave_conn_0_M_AXI_ARVALID_pin : out std_logic;
        axi_ext_slave_conn_0_M_AXI_ARREADY_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_RID_pin : in std_logic_vector(11 downto 0);
        axi_ext_slave_conn_0_M_AXI_RDATA_pin : in std_logic_vector(31 downto 0);
        axi_ext_slave_conn_0_M_AXI_RRESP_pin : in std_logic_vector(1 downto 0);
        axi_ext_slave_conn_0_M_AXI_RLAST_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_RVALID_pin : in std_logic;
        axi_ext_slave_conn_0_M_AXI_RREADY_pin : out std_logic
    );
    end component;     attribute BOX_TYPE : STRING;
    attribute BOX_TYPE of cpu : component is "user_black_box";     signal axis_AWID_o    : std_logic_vector(11 downto 0);
    signal axis_AWADDR_o  : std_logic_vector(31 downto 0);
    signal axis_AWLEN_o   : std_logic_vector(7 downto 0);
    signal axis_AWSIZE_o  : std_logic_vector(2 downto 0);
    signal axis_AWBURST_o : std_logic_vector(1 downto 0);
    signal axis_AWVALID_o : std_logic;
    signal axis_AWREADY_i : std_logic;
    signal axis_WDATA_o   : std_logic_vector(31 downto 0);
    signal axis_WSTRB_o   : std_logic_vector(3 downto 0);
    signal axis_WLAST_o   : std_logic;
    signal axis_WVALID_o  : std_logic;
    signal axis_WREADY_i  : std_logic;
    signal axis_BID_i     : std_logic_vector(11 downto 0);
    signal axis_BRESP_i   : std_logic_vector(1 downto 0);
    signal axis_BVALID_i  : std_logic;
    signal axis_BREADY_o  : std_logic;
    signal axis_ARID_o    : std_logic_vector(11 downto 0);
    signal axis_ARADDR_o  : std_logic_vector(31 downto 0);
    signal axis_ARLEN_o   : std_logic_vector(7 downto 0);
    signal axis_ARSIZE_o  : std_logic_vector(2 downto 0);
    signal axis_ARBURST_o : std_logic_vector(1 downto 0);
    signal axis_ARVALID_o : std_logic;
    signal axis_ARREADY_i : std_logic;
    signal axis_RID_i     : std_logic_vector(11 downto 0);
    signal axis_RDATA_i   : std_logic_vector(31 downto 0);
    signal axis_RRESP_i   : std_logic_vector(1 downto 0);
    signal axis_RLAST_i   : std_logic;
    signal axis_RVALID_i  : std_logic;
    signal axis_RREADY_o  : std_logic;

    signal plclk          : std_logic;
    signal axis_addr      : std_logic_vector(31 downto 0);
    signal axis_reset     : std_logic;

| | コメント (0)

2013.07.25

Spartan-6 MCBのクロック周期の謎

Spartan-6でMCBを使ったデザインを作るときに、クロック周期の設定でいつも悩みます。

MCBをCoregenで作ると、こういう階層構造のものができあがります。

Mcb_hier

トップの階層の下に、memc1_infrastructure.vhdというのが出来上がります。

このinfrastructureは、genericでパラメータを渡すことができるようになっていて、

entity memc1_infrastructure is
generic
  (
    C_MEMCLK_PERIOD    : integer := 2500;
    C_RST_ACT_LOW      : integer := 1;
    C_INPUT_CLK_TYPE   : string  := "DIFFERENTIAL";
    C_CLKOUT0_DIVIDE   : integer := 1;
    C_CLKOUT1_DIVIDE   : integer := 1;
    C_CLKOUT2_DIVIDE   : integer := 16;
    C_CLKOUT3_DIVIDE   : integer := 8;
    C_CLKFBOUT_MULT   : integer := 2;
    C_DIVCLK_DIVIDE   : integer := 1

  );

このようになっています。問題はC_MEMCLK_PERIODです。

ここでは2500になっているので2.5nsを意味します。つまりDDR2のクロックは400MHz(データレートは800MHz)ということを示しています。

このC_MEMCLK_PERIODはその後どうなるかというと、infrastructure.vhdの中で、

constant CLK_PERIOD_NS  : real := (real(C_MEMCLK_PERIOD)) / 1000.0;
constant CLK_PERIOD_INT : integer := C_MEMCLK_PERIOD/1000;

という記述がされているので、CLK_PERIOD_NSが2.5に設定されます。

ところが、CLK_PERIOD_NSは

u_pll_adv : PLL_ADV  generic map 
    (
     BANDWIDTH          => "OPTIMIZED",
     CLKIN1_PERIOD      => CLK_PERIOD_NS,
     CLKIN2_PERIOD      => CLK_PERIOD_NS,
     CLKOUT0_DIVIDE     => C_CLKOUT0_DIVIDE,
     CLKOUT1_DIVIDE     => C_CLKOUT1_DIVIDE,
     CLKOUT2_DIVIDE     => C_CLKOUT2_DIVIDE,
     CLKOUT3_DIVIDE     => C_CLKOUT3_DIVIDE,
     CLKOUT4_DIVIDE     => 1,
     CLKOUT5_DIVIDE     => 1,
     CLKOUT0_PHASE      => 0.000,

となっていて、PLL_ADVの入力クロックの周期として使われています。入力クロックの周期はPLLの元になる周波数ですから、50MHzとか100MHzのはずです。

実際にMCBを使ったデザインを論理合成するとこういうWarningが出るはずです。

WARNING:NgdBuild:1440 - User specified non-default attribute value (5) was
   detected for the CLKIN1_PERIOD attribute on PLL
   "u_memctrl/memc1_infrastructure_inst/u_pll_adv".  This does not match the
   PERIOD constraint value (20 ns.).  The uncertainty calculation will use the
   PERIOD constraint value.  This could result in incorrect uncertainty
   calculated for PLL output clocks.
Checking expanded design ...

PLL_ADVの入力クロックの周期が5になっていて(単位はns)、おそらくUCFの記述から推測した20nsと違うと言っています。

Coregenの出力したコードは、PLL_ADVの入力クロックの周期を指定すべきところに、PLL_ADVの出力クロックの周期を指定してしまっているので、このようなWarningが出ます。

結局、どうすればよいかというと、*_infrastructure.vhdの中115行目あたりにあるCLK_PERIOD_NSの定数定義を、

constant CLK_PERIOD_NS  : real := (real(C_MEMCLK_PERIOD)) * real(C_CLKFBOUT_MULT) / real(C_CLKOUT0_DIVIDE) / 2.0 / 1000.0;

に変えればよいのだと思います。

| | コメント (0)

2013.07.23

特電EZ-USB FX3ボードもUSB-JTAG化

昨日はArtix-7ボードのFX3をUSB-JTAG化することに成功したので、今日は特電FX3ボードもUSB-JTAG化してみました。

EZ-USB FX3にEndPoint2を追加して、そのEndPoint2を経由してJTAGの情報をやりとりしています。そして、FX3から出力されたJTAG信号はピンヘッダを通って、上に載ったSpartan-6ボードのFPGAにつながっています。

Fx3jtag_1

こうして、FX3をUSB-JTAG化できたら、MITOUJTAGで見てみると・・・ うまくいきました!

Spartan-6の動作状態が見えます!見えます!端子の1本1本までくっきりです。

Fx3jtag_2_2

Spartan-6 FPGAに書き込みをしてみると、なんと、3秒くらいで書き込み完了しました。

FX2で作ったUSB-JTAGよりも速い。この速さは病みつきになりそうです。

そして、昨日公開したSpartan-6 & Artix-7 USB JTAG書き込みツールでも、書き込みができるようにしました。

Fx3jtag_3

Spartan-6の45への書き込みはわずか3秒(XC6SLX16なら1秒程度)しかかかりません。この速度は病みつきになりそうです。

各種ファイルのダウンロードはこちら。

これは快適です。ぜひとも、FX3で作るUSB-JTAGをお試しください。

| | コメント (0)

2013.07.22

Artix-7用USB-JTAG書き込みツールをリリース

お待たせしました。特電Artix-7ボード用のUSB-JTAG書き込みツールをリリースしました。

このツールは従来のsp6jtagwの改良版で、Artix-7にも対応しました。(だから、実はSpartan-6とArtix-7の両対応です)

Jtagw_4

USB3.0をつなぐだけで、FPGAにも、SPI ROMにも書き込めてしまいます。しかも、iM●ACTよりも速いし、楽です。SPI ROMに書き込むときに、Bit→MCSの変換も不要です。Bitファイルのまま書き込めてしまいます。

詳しい使い方は下記のページにまとめました。

http://www.tokudenkairo.co.jp/art7/jtagw.html

どうぞよろしくお願いします。

| | コメント (0)

2013.07.20

Artix-7ボードにFX3 USB-JTAG機能を実装

いままでは、特電Artix-7ボードのFPGAをコンフィギュレーションするのに、Pocket JTAG Cableや、XILINX Platform USBケーブルが必要でした。
こうだとUSBが2本必要になり、扱いが面倒でした。

そこで、特電Artix-7ボード上のEZ-USB FX3にUSB-JTAG機能を実装することにしました。

Usbjtag_before_after

こうなると、1本のUSB3.0を通じて、データ通信とJTAGコンフィギュレーションが一つでできます。

FX3とFPGAの構成は次の図のようになります。

Usbjtag_configuration

FX3の中のEP1のINとOUTは、GPIF IIにつないで高速データ転送に使い、EP2はFX3内のCPUから操作してJTAG信号を出力し、Artix-7のJTAG操作のために使うというわけです。

このようなFX3-JTAGですが、Windows上のソフトは今のところMITOUJTAGをベースに開発しています。だから、コンフィグだけではなくFPGAの端子の状態も見えます。

Mitoujtagartixboard_3

ロジアナモードで使ってみた場合、端子のピンの状態を見るサンプリング間隔は約3msでした。これはもう少し速くしたいと思います。

Fx3artixlogiana

書き込み速度も、XC7A100Tの場合で、約30秒でした。

Fx3artixprogramming


このような感じで、とりあえず動作することが確認できたので、これから高速化にチャレンジしていきたいと思います。

| | コメント (1)

2013.07.19

EZ-USB FX3でEndPointを追加する

特電Artix-7ボード上のEZ-USB FX3でUSB-JTAGを実現するため、FX3にEndPointを追加することにしました。

現在、このFX3はEndpoint1(と0x81)をそれぞれOUT用とIN用のSlaveFIFOに使っています。これらのEndPointのデータはハード的に処理されてFPGAに送られるので、CPUが関与できません。

そこで、CPUが自由に操作できるEndPointを2つ追加しようというわけです。

そのためにはまず、ディスクリプタを書き換えて、EndPointを2つ作りました。1個分を示します。SuperSpeedだとcompanion descriptorというのが必要なようです。

/* Endpoint descriptor for producer EP */
0x07,                           /* Descriptor size */
CY_U3P_USB_ENDPNT_DESCR,        /* Endpoint descriptor type */
0x04,                           /* Endpoint address and description */
CY_U3P_USB_EP_BULK,             /* Bulk endpoint type */
0x00,0x04,                      /* Max packet size = 1024 bytes */
0x00,                           /* Servicing interval for data transfers : 0 for bulk */

/* Super speed endpoint companion descriptor for producer EP */
0x06,                           /* Descriptor size */
CY_U3P_SS_EP_COMPN_DESCR,       /* SS endpoint companion descriptor type */
0x00,                           /* Max no. of packets in a burst : 0: burst 1 packet at a time */
0x00,                           /* Max streams for bulk EP = 0 (No streams) */
0x00,0x00,                      /* Service interval for the EP : 0 for bulk */

エンドポイントの番号は、OUT用が0x04、IN用が0x88にしました。

それから、ファームウェアでもEndPointの設定をします。

CyU3PMemSet ((uint8_t *)&epCfg, 0, sizeof (epCfg));
epCfg.enable = CyTrue;
epCfg.epType = CY_U3P_USB_EP_BULK;
epCfg.burstLen = 1;
epCfg.streams = 0;
epCfg.pcktSize = size;
CyU3PSetEpConfig(CY_FX_EP_MYPRODUCER, &epCfg); // 0x04 EP4 OUT
CyU3PSetEpConfig(CY_FX_EP_MYCONSUMER, &epCfg); // 0x88 EP8 IN

ここで、CY_FX_EP_MYPRODUCERは0x04、CY_FX_EP_MYCONSUMERは0x88です。

これで、EndPoint4と8が作られ、それぞれBulkOUTとBuldInとしてセットアップされます。

次にdmaの設定を行います。CPUがデータを処理したい場合でもDMAを使います。
FX3ではProducerとかConsumerという用語が出てくるのですが、BulkInの場合はProducerはCPUのプログラムで、ConsumerはUSBのEndPointです。

// EndPoint EP8 INの設定
dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE;
dmaCfg.notification = 0;
dmaCfg.cb = NULL;
dmaCfg.prodHeader = 0;
dmaCfg.prodFooter = 0;
dmaCfg.consHeader = 0;
dmaCfg.prodAvailCount = 0;
dmaCfg.prodSckId = CY_U3P_CPU_SOCKET_PROD;
dmaCfg.consSckId = CY_FX_EP_MYCONSUMER_SOCKET; // <= CY_U3P_UIB_SOCKET_CONS_8
apiRetStatus = CyU3PDmaChannelCreate (&glChHandleBulkLpOut,
    CY_U3P_DMA_TYPE_MANUAL_OUT, &dmaCfg);

CyU3PUsbFlushEp(CY_FX_EP_MYCONSUMER); // 0x88
apiRetStatus = CyU3PDmaChannelSetXfer (&glChHandleBulkLpOut, 0);

prodSckIdとconsSckIdは上のリストのように設定します。consSckIdに設定されているCY_FX_EP_MYCONSUMER_SOCKETは、マクロ定義でCY_U3P_UIB_SOCKET_CONS_8にしています。こうすると、8番のEndPointに割り当てられるのだと思います。

ちなみに、USB Managerで見るとこうなっています。

Fx3_ep_4

これをどうやって使うかというと、SlFifoAppThread_Entryの中で、

int i;
CyU3PDmaChannelGetBuffer (&glChHandleBulkLpOut, &inBuf_p, CYU3P_WAIT_FOREVER);
for(i=0;i<1024;i++) {
    inBuf_p.buffer[i] = i;
}
CyU3PDmaChannelCommitBuffer (&glChHandleBulkLpOut, 1024, 0);

とするようです。つまり、バッファが空きになるまで待って、空いたらデータを詰めて、それからCommitします。

上のリストはBulkInの場合ですが、BulkOutの場合も同様でCyU3PDmaChannelCommitBufferの代わりにCyU3PDmaChannelDiscardBufferを使えばよいようです。

これで、EndPointに入ったデータをCPUで読んだり、CPUからデータをセットしてINパケットに乗せることができるようになります。

| | コメント (0)

2013.07.13

Artix-7にMicroBlazeを入れてみた

特電Artix-7ボードに、MicroBlazeを入れてみました。

ISEからNew SouceでEmbedded Processorを追加し、XPSを起動します。

100MHz動作、50MHzクロック入力、内蔵RAMは32kBにしておきます。パフォーマンスは変更しなくても大丈夫でしょう。

XPSが起動したデフォルトの状態では、DDR3はMT41J128M8になっているので、まずはそれを追加しておいて、あとからMT41J256M8に直します。

それからGPIOやUARTなども追加しておきます。GPIOやUARTの線はExternalにして、ISEの中のロジックから使えるようにします。

Clockのモジュールをそのまま作ってしまうと、差動クロック入力になってしまうので、クロックのPortを一度削除して、External Port:clock_generator_0_CLKIN_pinを追加するようにしたらシングルエンドのクロックを

Art7xps

次にISEに戻ってきたら、メインのHDLを書きます。

XPSが作る信号線の名前は冗長なので、適当に切り上げた信号線を自分で作ってFPGAの外に出すようにします。

Art7mbise

ちなみに、論理合成が成功した状態で、occupied Slicesは22%でした。XC7A100Tの約5分の1しか使っていません。

次にXPSからデザインをExportし、SDKを起動します。

ハードウェア記述とBSPを作ったら、テストアプリを作ります。ここではXILINXの用意しているメモリテストをひな形にしました。

Art7sdk

XILINXのメモリテストは、実はメモリ全域をテストしてくれません。先頭の4096バイトとかです。

実際に全域をテストすると10分くらいかかります。ソフトウェアは、FPGAのハードウェアの100~1000分の1の速度でしか動かないといえます。

そのため、自分でテストルーチンを作りました。最初に先頭1MBにXOR128の乱数を詰めて読み書きし、次にメモリ256MB全体を1024バイト単位でアドレスをインクリメントしながら粗く乱数を読み書きするテストを行いました。これなら数秒で終わります。

実行結果をLEDではなく文字として読みたいので、Artix-7ボードからUARTの配線を引き出します。特電の基板は裏にFPGAのピン番号が書いてあるので、回路図を見なくてもどのピンにつながっているかがわかって便利です。

Art7uart_1

そしてRS232Cレベル変換基板と、Platform USBケーブルをつなぎます。

Art7uart_2_3

Platform USBを使うのはSDK上でプログラムをデバッグしたりしたいためです。

実行中の画面はこんな感じ。TeraTermに結果を表示しています。

Art7memtest

◆追記

さて、作ったELFファイルを毎回毎回SDKからダウンロードするのは面倒です。できればBitファイルの中に埋め込みたいものです。

そういうとき、data2memというツールを使います。data2memは、出来上がったBitStreamの中のBlockRAMにデータを埋め込んでくれるツールです。XILINXのISEをインストールしていれば、デフォルトでインストールされていて、パスは通っているはずです。

ですから、コマンドラインから、

data2mem -bm BMMファイル名 -bd ELFファイル名 -bt BITファイル名

のようにすれば使えます。

Art7data2mem


ここで、BMMファイルというのは、出来上がったデザインの中でどのBRAMに何のデータが書かれているかを記述したファイルです。BitStreamができているフォルダに自動的に作られているはずです。2つある場合は、ファイル名の最後に_bdが付いている方を選びましょう。_bdのほうはBRAMのロケーションまで指定されています。

こんな感じのファイルです。

// BMM LOC annotation file.
//
// Release 14.5 -  P.58f, build 3.0.7 Mar 3, 2013
// Copyright (c) 1995-2013 Xilinx, Inc.  All rights reserved.
///////////////////////////////////////////////////////////////////////////////
//
// Processor 'microblaze_0', ID 100, memory map.
//
///////////////////////////////////////////////////////////////////////////////
ADDRESS_MAP microblaze_0 MICROBLAZE-LE 100
    ///////////////////////////////////////////////////////////////////////////////
    //
    // Processor 'microblaze_0' address space 'microblaze_0_bram_block_combined' 0x00000000:0x00007FFF (32 KBytes).
    //
    ///////////////////////////////////////////////////////////////////////////////

    ADDRESS_SPACE microblaze_0_bram_block_combined RAMB32 [0x00000000:0x00007FFF]
        BUS_BLOCK
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_0 RAMB32 [31:28] [0:8191] INPUT = microblaze_0_bram_block_combined_0.mem PLACED = X2Y12;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_1 RAMB32 [27:24] [0:8191] INPUT = microblaze_0_bram_block_combined_1.mem PLACED = X2Y13;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_2 RAMB32 [23:20] [0:8191] INPUT = microblaze_0_bram_block_combined_2.mem PLACED = X2Y19;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_3 RAMB32 [19:16] [0:8191] INPUT = microblaze_0_bram_block_combined_3.mem PLACED = X2Y14;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_4 RAMB32 [15:12] [0:8191] INPUT = microblaze_0_bram_block_combined_4.mem PLACED = X1Y14;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_5 RAMB32 [11:8] [0:8191] INPUT = microblaze_0_bram_block_combined_5.mem PLACED = X2Y18;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_6 RAMB32 [7:4] [0:8191] INPUT = microblaze_0_bram_block_combined_6.mem PLACED = X1Y15;
            mb_i/microblaze_0_bram_block/microblaze_0_bram_block/ramb36e1_7 RAMB32 [3:0] [0:8191] INPUT = microblaze_0_bram_block_combined_7.mem PLACED = X1Y16;
        END_BUS_BLOCK;
    END_ADDRESS_SPACE;
END_ADDRESS_MAP;

ELFファイルは、SDKが生成したDebugフォルダにあるはずです。

これでbitファイルにELFが埋め込まれます。出来上がったbitファイルは、元のファイル名_rp.bitになります。

このmain_rp.bitファイルなら、MITOUJTAGから書き込めます。

Artixmbjtag

100MHzで動くカスタマイズ可能なCPUがArtix-7内に簡単に作れて、しかも、何も苦労しなくてもprintfまで使えるというのは素晴らしいことです。

MicroBlazeはソフト開発環境が非常によく作りこまれていて、fatfsやlwipのライブラリまでXILINXが提供してくれているようなので、いずれ、それらも試してみたいと思います。

| | コメント (0)

2013.07.12

Artix-7ボードが出荷可能になりました

今日、実装屋さんからArtix-7ボードが12台、届きました。

Firstlot

さっそくコネクタを取り付け、動作テストを行います。

DDR3 SDRAMよし。USBよし。各部の電圧も設計どおり。

最初のロットなので念を入れてGPIOをオシロですべて確認しました。
断線しているところもありませんでした。

そして、今日は5台ほど出荷可能なものができました。

Artix75pcs

来週も順次、検査をして出荷可能にしていこうと思います。

ご注文はオンラインショップで承っております。

どうぞよろしくお願いします。

品切れの際はどうかご容赦ください。

| | コメント (0)

2013.07.11

Artix-7ボードの資料を整備

いよいよ明日、特電Artix-7ボードの出荷を開始します。

今日、Artix-7ボードの資料を整備しました。下記のページからハードウェアマニュアルや、ピン配置図などがダウンロードできるようになりました。

http://www.tokudenkairo.co.jp/art7/

また、特電オンラインショップでも注文の受付を開始しました。

https://shop.tokudenkairo.co.jp/shopping/detail.php?shpdi=TKDNART7BRD

さて、いまARTIX-7がどのくらいの速度があるのか、スピードグレード-1と-2と-3の違いは何か、を調べようとしています。

FPGA内に32bitのバイナリカウンタを作って、それを駆動するクロックにPERIOD 1nsのタイミングをかけて(わざとタイミング違反を起こさせて)、クリティカルネットの遅延を測りました。その結果ですが、

XC6SLX16-3  2.059ns  486MHz
XC6SLX16-2  2.281ns  438MHz
XC7K70T-1  1.564ns 639MHz
XC7A100T-3  1.961ns  509MHz
XC7A100T-2  2.192ns  456MHz
XC7A100T-1  2.642ns  378MHz
XC3S50A-5 4.341ns 230MHz

となりました。

Artixspeedgraph

つまり、同一グレードのSpartan-6より速そうです。

-1のグレードでも32bitカウンタが378MHzで動くというのはすごいと思います。実際のアプリケーションは125MHzとか175MHzくらいで動かすことになると思うので、あまりスピードが問題になることはないと思います。

あと、まだ確かめられてはいませんが、メモリや乗算器などのハードウェアプリミティブも高速化されていると思うので、トータルではSpartan-6よりもよくなっていると思います。

Artixthumb

そんなArtix-7ボードは、明日、発売予定。

今日からオンラインショップでご注文いただけるようになりましたので、皆様どうぞよろしくお願いします。

https://shop.tokudenkairo.co.jp/shopping/detail.php?shpdi=TKDNART7BRD

| | コメント (0)

2013.07.10

RXduinoをEclipseで使うには

最近、ZYNQとかEZ-USB FX3の開発でEclipseを使わざるを得なくなって、ようやくEclipseに対する抵抗感が和らいできました。

そこで、拙作のRXduinoでもEclipseを使ってみようと思いました。

今まで使ったことはなかったのか?というと、実は私はEclipseを使ってRXの開発をしたことはありませんでした

いままでは、CygwinとMakefileでやっていたので、Eclipseを使う必要性を感じていなかったのです。

RXduinoのチュートリアル(http://rx.tokudenkairo.co.jp/tutorial.html)にあるEclipseの使い方のページは、昔アルバイトの人に頼んで書いてもらったので、何が書いてあるのか私自身もよく把握していませんでした。

で、今回、Eclipseを使ってみたくなってチュートリアルにしたがって実際にやってみました。

・・・・・・いっぱいハマリました。

・・

躓いたポイントが順に紹介していきます。

① Eclipseをダウンロードしてくるときにはhttp://www.eclipse.org/downloads/から、「Eclipse IDE for C/C++ Developers」というのをダウンロードすること。

② JREというJavaの実行環境をインストールしないとEclipseは動かない。現在のJREはVersion7のBuild25

③ Cygwinをインストールして、rx-elf-gccをインストールしたら、環境変数を通すこと!つまり、PATHの最後にC:\Cygwin\binを追加する

Cyginpath

④ Eclipseでは、既存のMakefileをもとにプロジェクトファイルを作るらしい。だから、新規プロジェクトを作成するときには、File→New→Makefile Project with Existing Codeを選ぶこと。

Eclipsenewproj_3

⑤ プロジェクト新規作成の際、ツールチェーンを選ぶところが出るので、「Cygwin GCC」を選ぶこと。「Cross GCC」や「<none>」を選んではいけない。

Eclipsetoolchain

⑥ これでmakefileを元にビルドが始まるが、現在のRXduinoのsampleディレクトリにあるmakefileはいろいろ問題がある。以下のように修正しなければならない。

(1) もし、CCPATHの設定が

#CCPATH = /usr/local/tkdn-20110720/rx-elf/bin/

とコメントアウトされていたら、そのコメントアウトを解除すること。

CCPATH = /usr/local/tkdn-20110720/rx-elf/bin/

(2) intvect.oの場所

古いRXduinoではintvect.oがsample/common/ディレクトリにあったが最新のでは/common/にある。したがって、

OBJS = ../../lib/start.o $(TARGET).o ../common/intvect.o

という行でエラーが出ているようならば、

OBJS = ../../lib/start.o $(TARGET).o ../../common/intvect.o

に修正する。

(3) リンカスクリプトの場所

同様に、

RAMSCRIPT=../common/rx62n_ram_standalone.ld
ROMSCRIPT=../common/rx62n_rom_standalone.ld

という記述でエラーが出ているようならば、

RAMSCRIPT=../../common/rx62n_ram_standalone.ld
ROMSCRIPT=../../common/rx62n_rom_standalone.ld

にする。

また、

all: ram

という行があったら、

all: rom

にする。(こうしないとRAM実行用のELFができてしまう)

これでRXduinoのプログラムがEclipseで開発できるようになります。

個々のサンプルプログラムを、Eclipseプロジェクト化して、一気にビルドするということもできました。

Eclipsebuild

チュートリアルのページの書き直しと、サンプルプロジェクトの提供は今週末に行いたいと思います。

| | コメント (0)

2013.07.08

ZYNQのPS/PL通信をやってみた(4) GPIOテストコードを書く

ZYNQのPS/PL通信をやってみた(3) SDKを使う」の続きです。

さて、SDKが動いてHello Worldが出たら、アプリケーションを自分用に書き換えてあげましょう。

プロジェクトtestappの中にあるhellow.cはこんな感じ。

#include <stdio.h>
#include "platform.h"

void print(char *str);
int main()
{
    init_platform();
    print("Hello World\n\r");
    return 0;
}

さて次のように修正します。

  • printという関数のプロトタイプを消去(次にincludeするものとぶつかるため)し、print→printfに変更
  • includeに、xparameters.hと、xgpio.hを追加

最初のステップでXPSで生成したAXI_GPIOを操作したいけどアドレスがどこにあるか・・・とかそういうことを調べる必要はありません。それらはxparameters.hをインクルードすることで解決されます。このファイルをインクルードしたらXPAR_まで打てばあとは自動補完されてなんとかなります。

GPIOから値を出力するには、

XGpio xgpout;
XGpio_Config xcfg_out;
xcfg_out.DeviceId = XPAR_AXI_GPOUT_DEVICE_ID;
XGpio_CfgInitialize(&xgpout, &xcfg_out, XPAR_AXI_GPOUT_BASEADDR);
XGpio_SetDataDirection(&xgpout, 1, 0);
XGpio_DiscreteWrite(&xgpout,1,i);

とします。 xgpoutという変数はハンドルみたいなものです。xcfg_outはDeviceIdを設定するためだけに使われるようで、しかもこの値は結局参照されていないようなのですが、上のリストのようにXPAR_AXI_GPOUT_DEVICE_IDでマクロ定義された値を設定します。

そして、XGpio_CfgInitializeでGPIOの特定のポートを初期化します。ここで与えるアドレスはGPIOの実効アドレスなのですが、これもXPAR_AXI_GPOUT_BASEADDRというマクロで定義されています。(実際の値は0x41200000)

XGpio_SetDataDirectionはGPIOの方向を指定する関数ですが、第一引数は先のハンドルを指定します。第二引数はチャネル番号で、通常は1です。最後の0は全ポート出力を意味します。(各ビットが1だと入力になる)

実際に値を出力するのは、XGpio_DiscreteWriteという関数です。第一引数は先のハンドルを指定します。第二引数はチャネル番号でこれも1です。第三引数に出力したい値を指定します。

たかがGPIOからの出力のためにわざわざハンドルを作らなければならないのが面倒ですが、このようにしてソフトウェアからFPGA部分に任意の値を送り込むことができます。

ソフトウェアでGPIOからの入力を行うには次のようにします。

XGpio xgpin;
XGpio_Config xcfg_in;
xcfg_in.DeviceId = XPAR_AXI_GPIN_DEVICE_ID;
XGpio_CfgInitialize(&xgpin, &xcfg_in, XPAR_AXI_GPIN_BASEADDR);
XGpio_SetDataDirection(&xgpin, 1, 1);
printf("%4d ", XGpio_DiscreteRead(&xgpin,1));

出力とだいたい同じ流れです。XGpio_CfgInitializeでハンドルを作成して、XGpio_SetDataDirectionでチャネル番号(1)と方向(1:入力)をセットして、XGpio_DiscreteReadで読み出しを行っています。

このようにして、FPGAの中の回路とソフトウェアが通信できるようになりました。最後にまとめとして、FPGA内に作ったセレクタをGPOUTして切り替えて、8ch ADCの内容をGPINして読みだすというプログラムのコードを示します。

#include <stdio.h>
#include "xparameters.h"
#include "platform.h"
#include "xgpio.h"
#include "xgpiops.h"

int main()
{
    init_platform();

    printf("Hello World\n\r");
    printf("This is nahitafu\n\r");

    XGpio xgpin,xgpout;
    XGpio_Config xcfg_in,xcfg_out;
    xcfg_in.DeviceId = XPAR_AXI_GPIN_DEVICE_ID;
    xcfg_out.DeviceId = XPAR_AXI_GPOUT_DEVICE_ID;

    XGpio_CfgInitialize(&xgpin, &xcfg_in, XPAR_AXI_GPIN_BASEADDR);
    XGpio_SetDataDirection(&xgpin, 1, 1);

    XGpio_CfgInitialize(&xgpout, &xcfg_out, XPAR_AXI_GPOUT_BASEADDR);
    XGpio_SetDataDirection(&xgpout, 1, 0);

    while(1)
    {
        int i;
        for(i=0;i<8;i++)
        {
            XGpio_DiscreteWrite(&xgpout,1,i);
            printf("%4d ", XGpio_DiscreteRead(&xgpin,1));
        }
        printf("\r\n");
        usleep(1000);
    }

    return 0;
}

これは以前作ったZED Board用のADC拡張ボード

Zynq_pspl_33

のデータ読み出しプログラムです。なんとなく動いているような気がします。

Zynq_pspl_32

この一連のやり方がわかるまで、XILINXのチュートリアルや、過去のセミナー資料などを読みながら試行錯誤しました。これらの記事がZYNQで何かをしようとしている皆様のお役に立てば幸いです。

ひととおりやってみて驚いたのは、「PLとPSは完全に独立しているので、どっちを先にコンフィグしてもよい」ということでした。つまり、高性能なCPUとFPGAが別々に存在しているような感じだということです。

また、SDKはよくできています。ほとんど何も(ソフトもハードも)書かなくてもこれだけのことができてしまったということで、XILINXの作りこみのすごさにただただ驚くばかりです。

| | コメント (4)

ZYNQのPS/PL通信をやってみた(3) SDKを使う

ZYNQのPS/PL通信をやってみた(2) HDLのカスタマイズ」の続きです。

ハードウェアのデザインができたので、ソフトウェアを作成します。ソフトの開発は、EDKの中にあるSDKを使います。

まず、SDKを起動する(Windowsのスタートメニューから起動するか、XPS上でSDKボタンを押す)と、Eclipseが立ち上がってworkspaceの場所を聞かれます。

これはソフトウェアのさまざまなプロジェクトをどこのフォルダに入れるかということなので、できれば新しいフォルダを作ってそこを指定します。

Zynq_pspl_19

今回は、c:\share\np1063\softwareにしました。

Eclipseが起動したら、File→New→Application Projectをやります。この画面やメニューはインストールしたISEのバージョンによって若干異なるようです。

Zynq_pspl_20

そうして、次のNewProjectというダイアログが開いたら、Target Hardwareを「Create New」にします。zed_hw_platformではありません。なぜならば、自分でカスタマイズしてAXI GPIOを追加したZEDボードだからです。

Zynq_pspl_21

Create Newを選ぶとダイアログの見た目が急に変わります。Project Nameは任意の文字列なので、とりあえずhw_zedemoと入れておきます。下のXMLファイルを入れる欄は、XPSからエクスポートしたXMLを入れるのですが、これは(ISEのプロジェクトフォルダ)\cpu\SDK\SDK_Export\hw\cpu.xml に生成されていました。

Zynq_pspl_22

上の画面でFinishを押すと、元のダイアログに戻ってきました。

Project Nameにはtestapp、Hardware Platformには先ほどのhw_zedemo、Board Support Packageはtestapp_bsp(自動で名前が付けられる)とします。

Zynq_pspl_23_2

Nextを押したらどんなサンプルをひな形として使うかのダイアログが出るので、Hello Worldを選びます。

Zynq_pspl_24

Eclipseに出ているWelcome画面を消すと、プロジェクトの編集画面が出てきます。

Zynq_pspl_25

ここで、hw_zedemoと、testappと、testapp_bspの3つのプロジェクトが出来上がっています。しかも自動でビルドされています。(自動ビルドがデフォルトで有効になっているため)

hw_zedemoというのは、基本的なアドレスや持っている機能の定義、testapp_bspというのは作成されたハードウェアのためのライブラリ集、testappがアプリケーションの本体、だと私は認識しています。基本的にhw_zedemoや_bspは編集する必要はないと思います。

上の画面で、すでにtestappがビルドされ、testapp.elfが生成されています。

なので、プロジェクトツリーのtestappにカーソルを合わせ、メインメニューにあるDebugボタンか、RUNボタンを押せば、自動的にELFファイルがロードされてプログラムが実行されます。

Zynq_pspl_26

※もし、XILINX Platform USBケーブル経由でターゲットシステムにうまく接続できないようであれば、iMPACTを一度落としたりしてみるといいかもしれない。よくわかっていない。

ここで次のようなダイアログが出たら、Launch on Hardwareを選んでOKを押します。

Zynq_pspl_27

そうそう。もうひとつ準備することがありました。ZED BoardのUARTと書かれたコネクタにUSBケーブルを接続したら、CyrpressのUSB2UARTドライバをインストール(http://japan.cypress.com/?rID=63794)しておきます。こうすることで、ZYNQのMIO[48:49]につながったレガシーUARTを、USB経由で読み取れるようになります。

さて、USB2UARTの仮想COMポートをTeraTermなどで開いて115200bpsに設定しておけば、CPUの動作開始とともにHello Worldというメッセージを受け取れるでしょう。

Zynq_pspl_29   

ここまでくれば一安心。

TeraTermに切り替えたくない場合は、Eclipseの中にあるターミナル機能を使うといいかもしれません。

下のほうにある領域をTerminal 1に切り替えて、設定ボタンを押せば、ターミナルの設定画面になります。

Zynq_pspl_31

ここで、COMポート番号を指定して、速度を115200bpsに、文字コードをUTFにしておきます。

この機能を使えば、Eclipseの中だけで、コードを書いたり、実行したり、結果を確認できたりするので結構便利です。

Zynq_pspl_30_2

「ZYNQのPS/PL通信をやってみた(4) GPIOテストコードを書く」へ続く。

| | コメント (0)

ZYNQのPS/PL通信をやってみた(2) HDLのカスタマイズ

ZYNQのPS/PL通信をやってみた(1) XPSでコア生成」の続きです。

では、適当に作ったLEDチカチカの回路と前回作ったcpu_top.vhdを組み合わせてみましょう。

① 基本的には自分でmain.vhdを新規作成し、その中に先ほどのcomponent cpuをインスタンシエートするというスタイルを採ります。

次のようなコードになります。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;

library UNISIM;
use UNISIM.VComponents.all;

entity main is
    Port (  clk_ip : in  STD_LOGIC;
            led_op : out  STD_LOGIC_VECTOR (7 downto 0);
            MIO : inout std_logic_vector(53 downto 0);
            PS_SRSTB_pin : in std_logic;
            PS_CLK_pin : in std_logic;
            PS_PORB_pin : in std_logic;
            DDR_Clk : inout std_logic;
            DDR_Clk_n : inout std_logic;
            DDR_CKE : inout std_logic;
            DDR_CS_n : inout std_logic;
            DDR_RAS_n : inout std_logic;
            DDR_CAS_n : inout std_logic;
            DDR_WEB : out std_logic;
            DDR_BankAddr : inout std_logic_vector(2 downto 0);
            DDR_Addr : inout std_logic_vector(14 downto 0);
            DDR_ODT : inout std_logic;
            DDR_DRSTB : inout std_logic;
            DDR_DQ : inout std_logic_vector(31 downto 0);
            DDR_DM : inout std_logic_vector(3 downto 0);
            DDR_DQS : inout std_logic_vector(3 downto 0);
            DDR_DQS_n : inout std_logic_vector(3 downto 0);
            DDR_VRN : inout std_logic;
            DDR_VRP : inout std_logic
    );
end main;

architecture Behavioral of main is

    -- 自分で作ったロジック
    signal clk : std_logic;
    signal led : STD_LOGIC_VECTOR(7 downto 0);
    signal timer  : std_logic_vector(19 downto 0);

    -- 間をつなぐ信号
    signal gpin   : std_logic_vector(31 downto 0);
    signal gpout  : std_logic_vector(31 downto 0);

    -- 自動生成されたひな形
    component cpu is
    port (
        processing_system7_0_MIO : inout std_logic_vector(53 downto 0);
        processing_system7_0_PS_SRSTB_pin : in std_logic;
        processing_system7_0_PS_CLK_pin : in std_logic;
        processing_system7_0_PS_PORB_pin : in std_logic;
        processing_system7_0_DDR_Clk : inout std_logic;
        processing_system7_0_DDR_Clk_n : inout std_logic;
        processing_system7_0_DDR_CKE : inout std_logic;
        processing_system7_0_DDR_CS_n : inout std_logic;
        processing_system7_0_DDR_RAS_n : inout std_logic;
        processing_system7_0_DDR_CAS_n : inout std_logic;
        processing_system7_0_DDR_WEB_pin : out std_logic;
        processing_system7_0_DDR_BankAddr : inout std_logic_vector(2 downto 0);
        processing_system7_0_DDR_Addr : inout std_logic_vector(14 downto 0);
        processing_system7_0_DDR_ODT : inout std_logic;
        processing_system7_0_DDR_DRSTB : inout std_logic;
        processing_system7_0_DDR_DQ : inout std_logic_vector(31 downto 0);
        processing_system7_0_DDR_DM : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_DQS : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_DQS_n : inout std_logic_vector(3 downto 0);
        processing_system7_0_DDR_VRN : inout std_logic;
        processing_system7_0_DDR_VRP : inout std_logic;
        axi_gpout_GPIO_IO_O_pin : out std_logic_vector(31 downto 0);
        axi_gpin_GPIO_IO_I_pin : in std_logic_vector(31 downto 0)
    );
    end component;

    attribute BOX_TYPE : STRING;
    attribute BOX_TYPE of cpu : component is "user_black_box";

begin

    -- 自分で作ったロジック(LEDちかちか)
    clk <= clk_ip;

    process(clk) begin
        if(clk'event and clk='1') then
            timer <= timer + 1;
            if(timer = 0) then
                led <= led + 1;
            end if;
        end if;
    end process;

    led_op <= led;
    -- 間をつなぐ信号     gpin <= x"000000" & led;     -- 自動生成されたひな形     cpu_i : cpu port map (         processing_system7_0_MIO => MIO,         processing_system7_0_PS_SRSTB_pin => PS_SRSTB_pin,         processing_system7_0_PS_CLK_pin => PS_CLK_pin,         processing_system7_0_PS_PORB_pin => PS_PORB_pin,         processing_system7_0_DDR_Clk => DDR_Clk,         processing_system7_0_DDR_Clk_n => DDR_Clk_n,         processing_system7_0_DDR_CKE => DDR_CKE,         processing_system7_0_DDR_CS_n => DDR_CS_n,         processing_system7_0_DDR_RAS_n => DDR_RAS_n,         processing_system7_0_DDR_CAS_n => DDR_CAS_n,         processing_system7_0_DDR_WEB_pin => DDR_WEB,         processing_system7_0_DDR_BankAddr => DDR_BankAddr,         processing_system7_0_DDR_Addr => DDR_Addr,         processing_system7_0_DDR_ODT => DDR_ODT,         processing_system7_0_DDR_DRSTB => DDR_DRSTB,         processing_system7_0_DDR_DQ => DDR_DQ,         processing_system7_0_DDR_DM => DDR_DM,         processing_system7_0_DDR_DQS => DDR_DQS,         processing_system7_0_DDR_DQS_n => DDR_DQS_n,         processing_system7_0_DDR_VRN => DDR_VRN,         processing_system7_0_DDR_VRP => DDR_VRP,         axi_gpout_GPIO_IO_O_pin => gpout,         axi_gpin_GPIO_IO_I_pin => gpin     ); end Behavioral;

この中の、clk_ipとled_opが自分で作った入出力ポートで、それ以外はcpu_topのひな形から持ってきたものです。ただし、各信号の頭についているprocessing_system7_0_は長くなるので省きました。

processing_system7_0_というのはCPUのモジュールから生えているポートで、基本的にそのままFPGAの外に出します。

axi_*というポートはソフトマクロなので、PLでどのようにでも料理できます。このaxi_*を、32bitのstd_logic_vector型信号のgpoutとgpinにつなぎ、gpinにチカチカしているLEDの値を入れます。

これでHDLの記述は終わりました。

② とりあえずimplementしてみると、そのまま通ってしまいます。そしてGenerate Programming Filesでエラーが出て停止します。それは、UCFにIOSTANDARDやLOCの記述がないからです。

Zynq_pspl_16

PSはハードマクロなので、どのポートがどこのピンから出るかは一意に決まるのですが、そういう融通は利かせてくれません。使わないPSのポートも含めてUCFを書かなければなりません。

③一番手っ取り早いのが、(プロジェクトのフォルダ)\cpu\data\ps7_constraints.ucfを使うことです。XPSが、今回のPS部分のピン配置を自動的に作成してくれているので、それを使うのが一番早いです。

なお、入出力ポートにあるclk_ipはZEDボード上の100MHzのクロックでPLのクロックです。led_opはFPGAのLEDポートにつながっているLEDです。これらはPL用の信号なので、自動配置はされません。ZED Boardのマニュアルと回路図を見ながら手作業で書いていきます。

④ できあがったUCFファイルはこんな感じです。

NET "clk_ip" TNM_NET = TNM_clk_ip;
TIMESPEC "TS_clk_ip" = PERIOD "TNM_clk_ip" 10 ns;

NET "PS_PORB_pin" LOC = "B5" | IOSTANDARD = "LVCMOS33";  
NET "PS_CLK_pin" LOC = "F7" | IOSTANDARD = "LVCMOS33";  
NET "PS_SRSTB_pin" LOC = "C9" | IOSTANDARD = "LVCMOS18";  

NET   "clk_ip"  LOC = "Y9" | IOSTANDARD = LVCMOS33;
NET "led_op<0>" LOC = "T22" | IOSTANDARD = "LVCMOS33";  
NET "led_op<1>" LOC = "T21" | IOSTANDARD = "LVCMOS33";  
NET "led_op<2>" LOC = "U22" | IOSTANDARD = "LVCMOS33";  
NET "led_op<3>" LOC = "U21" | IOSTANDARD = "LVCMOS33";  
NET "led_op<4>" LOC = "V22" | IOSTANDARD = "LVCMOS33";  
NET "led_op<5>" LOC = "W22" | IOSTANDARD = "LVCMOS33";  
NET "led_op<6>" LOC = "U19" | IOSTANDARD = "LVCMOS33";  
NET "led_op<7>" LOC = "U14" | IOSTANDARD = "LVCMOS33";  

##################################

NET "MIO[*]"   IOSTANDARD = LVCMOS18;
NET "MIO<53>" LOC = C12;
NET "MIO<10>" LOC = G7;
NET "MIO<11>" LOC = B4;
NET "MIO<12>" LOC = C5;
NET "MIO<13>" LOC = A6;
NET "MIO<14>" LOC = B6;
NET "MIO<15>" LOC = E6;
NET "MIO<20>" LOC = A8;
NET "MIO<16>" LOC = D6;
NET "MIO<21>" LOC = F11;
NET "MIO<17>" LOC = E9;
NET "MIO<22>" LOC = A14;
NET "MIO<18>" LOC = A7;
NET "MIO<23>" LOC = E11;
NET "MIO<19>" LOC = E10;
NET "MIO<24>" LOC = B7;
NET "MIO<0>" LOC = G6;
NET "MIO<30>" LOC = A11;
NET "MIO<25>" LOC = F12;
NET "MIO<1>" LOC = A1;
NET "MIO<31>" LOC = F9;
NET "MIO<26>" LOC = A13;
NET "MIO<2>" LOC = A2;
NET "MIO<32>" LOC = C7;
NET "MIO<27>" LOC = D7;
NET "MIO<3>" LOC = F6;
NET "MIO<33>" LOC = G13;
NET "MIO<28>" LOC = A12;
NET "MIO<4>" LOC = E4;
NET "MIO<34>" LOC = B12;
NET "MIO<29>" LOC = E8;
NET "MIO<5>" LOC = A3;
NET "MIO<40>" LOC = E14;
NET "MIO<35>" LOC = F14;
NET "MIO<6>" LOC = A4;
NET "MIO<41>" LOC = C8;
NET "MIO<36>" LOC = A9;
NET "MIO<7>" LOC = D5;
NET "MIO<42>" LOC = D8;
NET "MIO<37>" LOC = B14;
NET "MIO<8>" LOC = E5;
NET "MIO<43>" LOC = B11;
NET "MIO<38>" LOC = F13;
NET "MIO<9>" LOC = C4;
NET "MIO<44>" LOC = E13;
NET "MIO<39>" LOC = C13;
NET "MIO<45>" LOC = B9;
NET "MIO<50>" LOC = D13;
NET "MIO<46>" LOC = D12;
NET "MIO<51>" LOC = C10;
NET "MIO<52>" LOC = D10;
NET "MIO<47>" LOC = B10;

NET "MIO[49]"   IOSTANDARD = LVCMOS18 | DRIVE = "8" | SLEW = "slow" | LOC = "C14" ; #  UART 1 / rx / MIO[49]
NET "MIO[48]"   IOSTANDARD = LVCMOS18 | DRIVE = "8" | SLEW = "slow" | LOC = "D11" ; #  UART 1 / tx / MIO[48]
NET "DDR_WEB"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "R4" ; 
NET "DDR_VRP"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "N7" ; 
NET "DDR_VRN"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "M7" ; 
NET "DDR_RAS_n"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "R5" ; 
NET "DDR_ODT"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "P5" ; 
NET "DDR_DRSTB"   IOSTANDARD = SSTL15 | SLEW = "FAST" | LOC = "F3" ; 
NET "DDR_DQS[3]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "V2" ; 
NET "DDR_DQS[2]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "N2" ; 
NET "DDR_DQS[1]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "H2" ; 
NET "DDR_DQS[0]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "C2" ; 
NET "DDR_DQS_n[3]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "W2" ; 
NET "DDR_DQS_n[2]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "P2" ; 
NET "DDR_DQS_n[1]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "J2" ; 
NET "DDR_DQS_n[0]"   IOSTANDARD = DIFF_SSTL15_T_DCI | SLEW = "FAST" | LOC = "D2" ; 
NET "DDR_DQ[9]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "G1" ; 
NET "DDR_DQ[8]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "G2" ; 
NET "DDR_DQ[7]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "F1" ; 
NET "DDR_DQ[6]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "F2" ; 
NET "DDR_DQ[5]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "E1" ; 
NET "DDR_DQ[4]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "E3" ; 
NET "DDR_DQ[3]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "D3" ; 
NET "DDR_DQ[31]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "Y1" ; 
NET "DDR_DQ[30]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "W3" ; 
NET "DDR_DQ[2]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "B2" ; 
NET "DDR_DQ[29]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "Y3" ; 
NET "DDR_DQ[28]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "W1" ; 
NET "DDR_DQ[27]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "U2" ; 
NET "DDR_DQ[26]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "AA1" ; 
NET "DDR_DQ[25]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "U1" ; 
NET "DDR_DQ[24]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "AA3" ; 
NET "DDR_DQ[23]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "R1" ; 
NET "DDR_DQ[22]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "M2" ; 
NET "DDR_DQ[21]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "T2" ; 
NET "DDR_DQ[20]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "R3" ; 
NET "DDR_DQ[1]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "C3" ; 
NET "DDR_DQ[19]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "T1" ; 
NET "DDR_DQ[18]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "N3" ; 
NET "DDR_DQ[17]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "T3" ; 
NET "DDR_DQ[16]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "M1" ; 
NET "DDR_DQ[15]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "K3" ; 
NET "DDR_DQ[14]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "J1" ; 
NET "DDR_DQ[13]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "K1" ; 
NET "DDR_DQ[12]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "L3" ; 
NET "DDR_DQ[11]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "L2" ; 
NET "DDR_DQ[10]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "L1" ; 
NET "DDR_DQ[0]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "D1" ; 
NET "DDR_DM[3]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "AA2" ; 
NET "DDR_DM[2]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "P1" ; 
NET "DDR_DM[1]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "H3" ; 
NET "DDR_DM[0]"   IOSTANDARD = SSTL15_T_DCI | SLEW = "FAST" | LOC = "B1" ; 
NET "DDR_CS_n"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "P6" ; 
NET "DDR_CKE"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "V3" ; 
NET "DDR_Clk"   IOSTANDARD = DIFF_SSTL15 | SLEW = "FAST" | LOC = "N4" ; 
NET "DDR_Clk_n"   IOSTANDARD = DIFF_SSTL15 | SLEW = "FAST" | LOC = "N5" ; 
NET "DDR_CAS_n"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "P3" ; 
NET "DDR_BankAddr[2]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "M6" ; 
NET "DDR_BankAddr[1]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "L6" ; 
NET "DDR_BankAddr[0]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "L7" ; 
NET "DDR_Addr[9]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "H5" ; 
NET "DDR_Addr[8]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "J5" ; 
NET "DDR_Addr[7]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "J6" ; 
NET "DDR_Addr[6]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "J7" ; 
NET "DDR_Addr[5]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "K5" ; 
NET "DDR_Addr[4]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "K6" ; 
NET "DDR_Addr[3]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "L4" ; 
NET "DDR_Addr[2]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "K4" ; 
NET "DDR_Addr[1]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "M5" ; 
NET "DDR_Addr[14]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "G4" ; 
NET "DDR_Addr[13]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "F4" ; 
NET "DDR_Addr[12]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "H4" ; 
NET "DDR_Addr[11]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "G5" ; 
NET "DDR_Addr[10]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "J3" ; 
NET "DDR_Addr[0]"   IOSTANDARD = SSTL15 | SLEW = "SLOW" | LOC = "M4" ; 

結局、手書きしたのは最初の16行のみで、あとは自動生成したり、フォルダの奥から探してきました。

⑤ ISEのメインメニューからProject->Cleanup Project Filesをやってから、改めてSynthesisとImplementを行えば、Generate Programming Filesまで通るようになります。

これでZYNQのPSと通信可能なPLを持ったBitStreamができました。

Zynq_pspl_18

この時点でBitSteamをFPGAにダウンロードすれば、PSが動いていなくてPLだけ動きます。PSとPLは独立しているので、別々に動くことができるからです。この回路ではLEDがチカチカするでしょう。

ZYNQのPS/PL通信をやってみた(3) SDKを使う」へ続く。

| | コメント (9)

ZYNQのPS/PL通信をやってみた(1) XPSでコア生成

先週末はZED Boardを使ってZYNQと戯れていました。自分で作ったFPGAのロジック(PLという)に、どうやってZYNQ上のARM(PSという)からアクセスすればよいのか、ずっと悩んでいたのですが、ようやくそのやり方がわかりました。

XILINXが用意しているチュートリアルを見ると、Plan Aheadを使うことになっているのですが、ISE派の私にはPlan Aheadのメリットはよくわかりません。そこでISEとEDK(XPS)だけを使ってやってみました。

今日は3回に分けて、そのやりかたを示します。

① まず、普通にISEを起動します。私はISE14.5を使用しています。
プロジェクト名はzedemoとして、デバイスはZYNQの7020、パッケージは484、速度は-1にしておきます。
最初はプロジェクトは空の状態です。

Zynq_pspl_1

② そうしたら、New Sourceをやって、Embedded Processorを追加します。File nameはcpuにしておきました。

Zynq_pspl_2_2 

③ しばらくすると、XPSが起動してこの画面になります。この時点ではほとんどの内蔵ペリフェラルは設定されていません。

Zynq_pspl_3

④ importと書かれたボタンを押すと、特定のハードウェア用の設定ファイルが読み込まれます。ここでZedBoardを選んでOKボタンを押します。

Zynq_pspl_4

⑤ Zed Boardの設定が取り込まれ、いろいろカラフルになりました。I/O Periferalsのところに色がついたのがすぐにわかります。

Zynq_pspl_5

⑥ そうしたら、PL部分と通信するための、汎用入力ポートと出力ポートを作ります。まずは出力ポートを作りましょう。画面左側のIP一覧からGeneral Purpose IOの中にあるAXI General Pur...を選び、Add IPを行います。

Zynq_pspl_6

⑦ しばらくするとIPコアの設定ダイアログが出ます。ここでは名前をaxi_gpoutに変えました。で、OKを押します。

Zynq_pspl_7

⑧ 次の画面ではprocessing_system7_0を選んで(デフォルト)OKボタンを押します。CPUコア0に直結するという意味だと思います。

Zynq_pspl_8

⑨ 次に入力専用ポートを作ります。もういちどAdd IPを行って、AXI General Pur...を選びます。今度のダイアログでは、インスタンス名をaxi_gpinにして、Channel 1 is Input Onlyをチェックしておきます。

Zynq_pspl_9

⑩ XPSのメイン画面でPortsタブを開き、axi_gpinとaxi_gpoutを開いてGPIO_IOと書かれた信号を選び、No Connectionにします。これらの信号はFPGAのロジック部分に出てくるのですが、inoutの属性だといろいろ厄介なので、入力専用または出力専用にするためです。

Zynq_pspl_10

⑪ そして、GPINのほうではGPIO_IO_Iを選んでMake Externalし、GPOUTのほうではGPIO_IO_Oを選んでMake Externalします。これでFPGAのPL部とつながるポートができました。

Zynq_pspl_11

⑫ メインメニューのProjectからDesign Rule Checkを行い(おまじない)ます。

Zynq_pspl_12

⑬ Projectから、Exprort Hardware Design to SDKを実行します。ダイアログが出たらExport OnlyかExport & Launch SDKのどちらかを押します。SDKを今起動するか後から起動の違いでしかありません。

Zynq_pspl_13

⑭ XPSを閉じてISEに戻ります。デザインや設定は自動的に保存されているようなので気にしなくても大丈夫です。
先ほど作ったcpuというコアがプロジェクトに追加されています。これを選択して、Generate Top HDL Sourceを選び、Runを行います。

Zynq_pspl_14

⑮ cpu_top.vhdというVHDLファイルが生成されました。これは先ほどのコアをインスタンシエートするひな形のVHDLファイルです。このcpu_top.vhdは直接編集して使うのではなく、ここで生成された記述を自分で作ったVHDLの中に埋め込んで使います。

Zynq_pspl_15

これで、32bitの出力専用ポートと、32bitの入力専用ポートが備わったZED Boardのハードウェアデザインのひな形ができました。

ZYNQのPS/PL通信をやってみた(2) HDLのカスタマイズ」 へ続く

| | コメント (0)

2013.07.06

ZED BoardでもXADCを試してみた

Artix-7で試したXADCをZED Boardでも試してみました。

まず、内蔵ステータスレジスタの読み出し。

Zed_xadc1

温度=0xa07e(42.8℃), VCCINT=0x54a6(0.992V) ,VCCAUX=0x996a(1.798V), VCCBRAM=0x54b7(0.993V) なので、少し小さめに出ています。

温度を汎用I/Oピンに出してバウンダリスキャンで見てみると、やはり下4bitが激しく動いていて8bit程度の精度しかないように思えました。

Zed_xadc2

ZED BoardはVREFを専用IC(MAX6037AAUK12+T)で作っているのですが、それでも12bitの精度は出ていないようなので、あまりXADCには期待しないほうが良いのかもしれません。

| | コメント (0)

2013.07.04

Artix-7の内蔵ADCで外部入力を実験してみた

まず、特電Artix-7ボードにはDPとAGNDいう端子がピンヘッダの横に出ています。ここにポテンショメータをつけて電圧を測ってみました。

Artixxadc

FPGAの中にはXADCの結果出力をピンヘッダに出すだけの簡単なデザインを入れておきます。そして、ピンヘッダに出てきたピンの値を、MITOUJTAGのバウンダリスキャンを使って読み取り、MITOUJTAGをロジアナモードにしてアナログ表示させるというわけです。

まずは、コア電圧の1.0Vをポテンショメータで分圧した電圧を測ってみます。

すると、こんな感じ。

Artixcorepot

うーん。5bitか6bit程度の精度しか出ていません。

次に、アナログ用の1.8V(デジタル1.8VからLCフィルタで綺麗にしたやつ)をポテンショメータに入れ、アナログ入力の端子に47pFのコンデンサをつけてみます。

Artixvauxpot_2

辛うじて8bit程度の性能が出ているような気がします。

ポテンショメータまでのリード線が長いためか、外部からのアナログ入力はノイズまみれになります。

もう少しリード線を短くしたり、コンデンサをつけたりしたほうがいいのかもしれません。

結論としてわかったことは、

① VREFN=VREFP=AGNDにすれば内蔵リファレンスが使用されるようになりますが、精度を期待するなら専用のADCとVREFを外部に付けたほうがいいということです。

② 外部リファレンスを使うには、XADCの端子はBGAの内側すぎるので簡単には使えません。無理してまでXADCを使うメリットはないと思います。

③ Artix-7にはVP/VNというアナログ入力専用端子と、汎用のGPIOと兼用のアナログ入力端子があります。前者のほうがインピーダンスが低いので、高速かつ正確に測定できるそうです。VP/VNを使いましょう。

④ MIGを使ってDDR3を使うと、温度を監視するためXADCが使われるので、ユーザ回路でもXADCを使えるかどうか不明です。つまり、FPGA内のXADCを使って何かを計測してDDR3 SDRAMに蓄えるような回路は簡単には作れないということではないかと思います。

⑤ そもそもノイズまみれのディジタル回路の中心で高精度AD変換を期待するほうが間違っていると思います。XADCは電源・温度監視用と割り切って、計測用の本格的なAD変換をしたいなら専用のチップを外付けしたほうがいいんじゃないかと思います。

| | コメント (2)

Artix-7で内蔵ADCを使ってみる

Artix-7には12bit 1MspsのADコンバータが内蔵されています。これをXADCといいます。今日はXADCの実験をしてみました。

XADCを使うのはとても簡単です。プリミティブをインスタンシエートしてDCLKに50MHz程度のクロックを与えれば、自動的に温度、コア電源(VCCINT)、AUX電源(VCCAUX)、BRAM電源(VCCBRAM)を測ってくれます。

内蔵シーケンサが勝手にスキャンして、その結果をレジスタファイルに入れてくれるのです。その変換結果は、XADCのDADDRというポートに読み出したいレジスタのアドレスを入れれば、XADCのDOというポートから出てきます。

こうして、読み出しているときのFPGA内部の波形を示します。

Artixxadc_2


レジスタファイルは全部で128個あって、中には温度や電圧の最大値や最小値を記録するものもあります。

測定結果をExcelのシートに入れて、人間が読める形にしてみると。。。

Artixxadc_3


なんとなく妥当な値になりました。

ただ、デフォルトでは、温度(チャネル0)とVCCINT(チャネル1)、VCCAUX(チャネル2)、VCCBRAM(チャネル6)しか測ってくれません。ユーザ用のVP入力を測りたい場合にはシーケンサに有効ビットを立てる必要があります。

   XADC_inst : XADC
   generic map (
      -- INIT_40 - INIT_42: XADC configuration registers
      INIT_40 => X"0000",
      INIT_41 => X"2000",
      INIT_42 => X"0800",
      -- INIT_48 - INIT_4F: Sequence Registers
      INIT_48 => X"7f01",

これで、チャネル3(VP/VN入力),4(VREFP),5(VREFN)も変換されるようになります。

いわゆるシステムモニターができるわけです。

| | コメント (0)

2013.07.03

Artix-7でDDR3メモリにアクセスする

結構苦労しましたが、Artix-7でDDR3メモリにアクセスすることができました。

Artixddr3

Spartan-6のときには、DDR2やDDR3へのアクセスはMCBというハードマクロでできたのですが、Artix-7にはMCBがありません。そのため、ソフトコアを使ってアクセスしなければなりません。

Artix-7のI/OにはISERDES、OSERDESがあって4倍速や8倍速でデータを出し入れしてくれる上、FIFOまで入っているようです。これらを使うととても高性能なI/Oが作れるのですが、そういったものの使い方を調べてDDR3の回路を全部自分で書くのはとても大変なので、XILINXのCoreGenの中にMIGというコアがあって、DDR3の基本的な部分までは作れるようになっています。

Artix-7でDDR3にアクセスするにはMIGを使うことになります。

ただ、MIGの使い方を理解するのがとても大変なのです。MIGで困った点は、

  • MCBとはポートがまるで違う
  • 大きなFIFOが入っていない
  • シングルポート
  • パラメータでは自由に設定できるけれども、入力クロックの周波数をある範囲内にしないとタイミングエラーが収束しない
  • アドレスバスのビット幅が1bit大きくなっている

です。

まず、MIGのポート(ユーザインタフェース側)は、このようになっています。

   -- Inputs
   -- Single-ended system clock
   sys_clk_i                      : in    std_logic;
   -- Single-ended iodelayctrl clk (reference clock)
   clk_ref_i                                : in    std_logic;
   -- user interface signals
   app_addr             : in    std_logic_vector(ADDR_WIDTH-1 downto 0);
   app_cmd              : in    std_logic_vector(2 downto 0);
   app_en               : in    std_logic;
   app_wdf_data         : in    std_logic_vector((nCK_PER_CLK*2*PAYLOAD_WIDTH)-1 downto 0);
   app_wdf_end          : in    std_logic;
   app_wdf_mask         : in    std_logic_vector((nCK_PER_CLK*2*PAYLOAD_WIDTH)/8-1 downto 0)  ;
   app_wdf_wren         : in    std_logic;
   app_rd_data          : out   std_logic_vector((nCK_PER_CLK*2*PAYLOAD_WIDTH)-1 downto 0);
   app_rd_data_end      : out   std_logic;
   app_rd_data_valid    : out   std_logic;
   app_rdy              : out   std_logic;
   app_wdf_rdy          : out   std_logic;
   app_sr_req           : in    std_logic;
   app_sr_active        : out   std_logic;
   app_ref_req          : in    std_logic;
   app_ref_ack          : out   std_logic;
   app_zq_req           : in    std_logic;
   app_zq_ack           : out   std_logic;
   ui_clk               : out   std_logic;
   ui_clk_sync_rst      : out   std_logic;

このapp_addrに、読み書きしたいアドレスを与え、cmdに"000"または"001"を与えると、そのアドレスに対して読み書きが行われるという単純なものです。

データは配線幅の8倍となります。DDR3 SDRAMの配線が8bitならば、MIGのユーザポートは64bitになります。DDR3は1回のメモリサイクルで8ワードとってくるので8倍になるようです。

ui_clockは、ユーザ回路のクロックでMIGから出てきますが、これはDDR3メモリの速度の4分の1になります。つまり、DDR3メモリを8bit 800MHzで動かすならば、ユーザインタフェースは64bit 200MHzとなります。

DDR3メモリの速度は、Artix-7のスピードグレードが-1または-2のものは800まで、Artix-7のスピードグレードが-3のものは1066までです。これはISERDES、OSERDESの性能によるもののようです。つまり、1066MHzでDDR3にアクセスしたいならば-3のものを選ばなければなりません。

あと、MCBは、6つくらいのポートがあって、64ワードのFIFOが各Read/Writeバッファに入っていましたが、MIGではそういうものは一切ありません。ただ、ReadやWriteのリクエストは数段は蓄えられるようではありますが、基本的にはFIFOは自分で用意しなければなりません。

そういう違いはありますが、一応、

Artixddr3arch

という構造を作ってデータの読み書きを行いました。特電Artix-7ボードは256MByteのメモリが乗っているので、この全域を乱数で埋めて読んで照合して、エラーがでないことを確認できました。

上の図のFIFOから先はモジュール化して、再利用できるようにしたいと思います。

次はUSB3.0とDDR3 SDRAMをつなぐことにします。

| | コメント (0)

« 2013年6月 | トップページ | 2013年8月 »