2012年8月15日水曜日

PicoBlazeでピコピコしよう (Xilinx社のデバイスが搭載されたFPGAボードをジャンク箱で眠らせている人にもお勧め)

PicoBlazeってなぁに?

PicoBlazeとは、Xilinxが提供しているソフト・マクロで構成されたマイクロ・コントローラです。
最大4K命令の実行が可能で、命令長は18ビット固定、1命令あたり2クロック・サイクルで処理されます。

PicoBlazeは、非常にシンプルな8ビット・プロセッサです。
I/Oは自分で構成する事ができ、入出力それぞれ独立した8ビットのポート番号を持ちます。
割り込みなんて1系統しかありません。

最大クロック周波数は、デバイスとデザイン(配置配線、タイム・スペック)に依存するものの、Spartan-6(-2)で105MHz、Spartan-6(-3)で138MHz、Vertex-6(-3)で240MHzが得られます。

とはいえ、パフォーマンスはさほど重要なポイントではありません。
というか、パフォーマンス目当てでこんな物を使ってはいけません。


FPGAとプロセッサを並行して使う事で、論理記述言語のみで処理する場合に比べて、格段に設計が楽になったりします。(当然ですが、全てのケースでそうだなんて言いません。そういう場合もあるというだけです。)

プロセッサの場合、シーケンシャルな処理を記述するのは簡単で、単に処理を上から順に書くだけで済みます。(実行はFPGAに比べて格段に遅いけどね。)
FPGAでシーケンシャルな処理を構成する場合、ステートマシンを設計したり、ジェネリックで色々なパラメータを上位から渡せるようにした後でそれらを上位でパイプラインを構成して組み合わせたりと、何だかんだを手作業でやる事になるのが通例です。

「FPGAで構成したクロック単位でキビキビ動作する回路と、PicoBlaze上で動作するゆるーいソフトウェアを組み合わせる事で、従来にないメリットが得る」という使い方が、PicoBlazeの美味しいところです。

PicoBlazeはEDKなど有償ライセンスなしで開発できるのも嬉しいところ。
自宅のプチ・プロジェクトでも安心して使えるのがPicoBlazeなのです。

PicoBlazeのリソースをダウンロードする

PicoBlazeのリソースは、Xilinx社のウェブからダウンロードする事ができます。
ダウンロードにはサイン・インが必要です。
http://www.xilinx.com/ipcenter/processor_central/picoblaze/member/index.htm

初めにダウンロードする場合、登録が必要になっています。


ターゲットデバイスを選択して、住所などを入力します。


ライセンス情報が表示されます。


登録が完了するとラウンジに入室です。


ここからお目当てのデバイスのリソースをダウンロードします。
既に7シリーズ用のリソースもあります。


今回はLX-9 MicroBoardで使用するので、KCPSM6をダウンロードしました。
Release4の中身は以下のようになっていました。


どんな風に開発するの?

PicoBlazeの開発フローを簡単に説明します。
  1. PicoBlaze向けのソフトウェアをアセンブラで記述する。
  2. 記述したソフトウェアをアセンブルして、プログラムROM用のモジュールを得る。
  3. プログラムROM用のモジュールとPicoBlazeソフト・コアをプロジェクトに追加する。
  4. シンセサイズとインプリメンテーションを実行する。
まぁ、ざっくり言ってしまうと、FPGAの開発フローの前段に余計な物がくっついたという感じです。
PicoBlazeプロセッサ向けに追加されたフローは、アセンブラの記述とアセンブルの実行です。

このプロセスで得られるのは、PicoBlazeソフト・コア本体と、自分で記述したソフトウェアのインストラクションが格納されたモジュールです。後は、ROMとソフト・コア本体、そして自分が欲しい周辺回路を論理記述言語で接続して使用するだけです。

命令格納モジュール(下図で言うyour_program)とPicoBlaze(下図で言うkcpsm6)の関係は以下です。プロセッサ・コアからアドレスを与えられた命令格納モジュールが、命令をプロセッサ・コアに引き渡す構成です。


使用イメージを簡単に掴む為に、実際にインプリメンテーションを通した後の様子を回路図で見てみましょう。


上記を見ておわかりのように、ユーザ回路の一部にインストラクション格納モジュールとPicoBlaze本体が挟まっただけの構成です。

本当に適当な例(さっきの回路)

ここで、本当に適当な例(さっきの回路)を挙げてみましょう。
トップはsw1の状態を入力ポートのビット0に渡し、出力ポートのビット3から0をport_idが0の時にラッチしてLEDを点灯させているだけの回路です。

入力ポートをどのように使用するのか、出力ポートをどのように使用するのか、についてはプロセッサ上で動作するプログラムに任されています。

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

entity main is
    Port (
             led1 : out std_logic;
             led2 : out std_logic;
             led3 : out std_logic;
             led4 : out std_logic;
             clk : in std_logic;
             sw1 : in std_logic);
end main;

architecture RTL of main is

  component kcpsm6
    generic(                 hwbuild : std_logic_vector(7 downto 0) := X"00";
                    interrupt_vector : std_logic_vector(11 downto 0) := X"3FF";
             scratch_pad_memory_size : integer := 64);
    port (                   address : out std_logic_vector(11 downto 0);
                         instruction : in std_logic_vector(17 downto 0);
                         bram_enable : out std_logic;
                             in_port : in std_logic_vector(7 downto 0);
                            out_port : out std_logic_vector(7 downto 0);
                             port_id : out std_logic_vector(7 downto 0);
                        write_strobe : out std_logic;
                      k_write_strobe : out std_logic;
                         read_strobe : out std_logic;
                           interrupt : in std_logic;
                       interrupt_ack : out std_logic;
                               sleep : in std_logic;
                               reset : in std_logic;
                                 clk : in std_logic);
  end component;

  component led_toggle
    generic(             C_FAMILY : string := "S6";
                C_RAM_SIZE_KWORDS : integer := 1;
             C_JTAG_LOADER_ENABLE : integer := 0);
    Port (      address : in std_logic_vector(11 downto 0);
            instruction : out std_logic_vector(17 downto 0);
                 enable : in std_logic;
                    rdl : out std_logic;
                    clk : in std_logic);
  end component;

signal address : std_logic_vector(11 downto 0);
signal instruction : std_logic_vector(17 downto 0);
signal port_id : std_logic_vector(7 downto 0);
signal in_port : std_logic_vector(7 downto 0);
signal out_port : std_logic_vector(7 downto 0);
signal read_strobe : std_logic;
signal write_strobe : std_logic;

signal port000 : std_logic_vector(7 downto 0);

begin

  processor: kcpsm6
    generic map (                 hwbuild => X"00",
                         interrupt_vector => X"3FF",
                  scratch_pad_memory_size => 64)
    port map(
                address => address,
               instruction => instruction,
                   port_id => port_id,
               interrupt => '0',
              write_strobe => write_strobe,
                  out_port => out_port,
               read_strobe => read_strobe,
                   in_port => in_port,
                     sleep => '0',
                     reset => '0',
                       clk => clk);

  program_rom: led_toggle
    generic map(             C_FAMILY => "S6",
                    C_RAM_SIZE_KWORDS => 1,
                 C_JTAG_LOADER_ENABLE => 0)
    port map(      address => address,
               instruction => instruction,
                    enable => '1',
                       clk => clk);

   out_ratch : process(clk) is
   begin
       if clk'event and clk='1' then
           if write_strobe='1' then
               if port_id ="00000000" then
                   port000 <= out_port;
               end if;
           end if;
       end if;
   end process out_ratch;

   in_ratch : process(clk) is
   begin
       if clk'event and clk='1' then
           if read_strobe='1' then
               if port_id ="00000000" then
                   in_port(0) <= not sw1;
               end if;
           end if;
       end if;
   end process in_ratch;

   led1 <= port000(0);
   led2 <= port000(1);
   led3 <= port000(2);
   led4 <= port000(3);

end RTL;

次にプロセッサ上のプログラムです。

レジスタは16レジスタが2バンクある構成になっています。



デフォルト・バンクは'A'です。
これらのレジスタは、ユーザが自由に使用する事ができます。
単にs0とかs1とかだと分かりにくいのですが、NAMEREGを使う事で名前でレジスタにアクセスする事ができます。


        NAMEREG s0, TEMP
        NAMEREG s1, DLYCNT1
        NAMEREG s2, DLYCNT2
        NAMEREG s3, PVAL000
        CONSTANT PORT000, 00
INIT:
        LOAD PVAL000, 00
LOOP:
        CALL DELAY
        INPUT TEMP, PORT000
        COMPARE TEMP, 01
        JUMP Z, LOOP
LOOP1:
        CALL DELAY
        INPUT TEMP, PORT000
        COMPARE TEMP, 00
        JUMP Z, LOOP1
LOOP2:
        ADD PVAL000, 01
        OUTPUT PVAL000, PORT000
        JUMP LOOP
DELAY:
        LOAD DLYCNT1, FF
DELAY1:
        LOAD DLYCNT2, FF
DELAY2:
        SUB DLYCNT2, 01
        JUMP NZ, DELAY2
        SUB DLYCNT1, 01
        JUMP NZ, DELAY1
        RETURN

上記の例では、スイッチを押すたびにプロセッサ上のレジスタの値をインクリメントし、インクリメントした値を8ビットのポートに出力するようにしました。



今回は非常に単純な例を示したので、「そんなのVHDLだけで良い」と言われかねませんが、プロセッサによる並列処理を介在させる事でハードウェア側の設計を単純にする事ができる例があります。I2CやSPIなど、同じプロトコルでも複数の種類があるような場合や、そもそもステートがあまりに多い場合にも、全体制御をプロセッサにさせる事で、ハードウェア側の設計が楽になる事は間違いありません。(ここでは性能について一切触れませんが。)

また、ソフト・コア・プロセッサが動作するというだけで、少しワクワクした気分になるのは自分だけではないでしょう。無償で得られる開発環境で、チクチクピコピコPicoBlazeいじりなんていうのも、たまには面白いのではないでしょうか。

0 件のコメント:

コメントを投稿