ラベル FPGA の投稿を表示しています。 すべての投稿を表示
ラベル FPGA の投稿を表示しています。 すべての投稿を表示

2012年8月17日金曜日

有機ELディスプレイ搭載スイッチIS-C15ANP4をPicoBlazeで制御しよう

先日からピコピコとPicoBlazeを触っていたわけですが、某所で日本開閉器工業社製の有機ELディスプレイ搭載スイッチIS-C15ANP4をPicoBlazeから制御したいと依頼を受けました。


メーカのウェブからダウンロードできる資料には、初期化時に使用するパラメータが列挙されています。このスイッチのディスプレイ・コントローラには、SOLOMON SYSTECH社のSSD1331が搭載されているようです。初期化コードをスイッチに送ってやるだけでディスプレイ・コントローラの初期化は完了です。後は、データを送ればそのまま絵として表示されます。


さて、今回のPicoBlazeのデザインは、先のLEDをピコピコさせたものをそのまま流用しました。
という事はBit Bangで制御するのね、ヒドイ!


ということで、トップのデザインです。

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

entity main is
    Port (
             oled_ss : out std_logic;
             oled_res : out std_logic;
             oled_dc  : out std_logic;
             oled_sck : out std_logic;
             oled_sdi : 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 c15anp4
    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: c15anp4
    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;

   oled_ss  <= port000(0);
   oled_res <= port000(1);
   oled_dc  <= port000(2);
   oled_sck <= port000(3);
   oled_sdi <= port000(4);

end RTL;

回路はLX-9 MicroBlazeのPMODのピンに超適当に配線です。

NET "oled_ss"  LOC = D18;
NET "oled_res" LOC = D17;
NET "oled_dc"  LOC = G14;
NET "oled_sck" LOC = F14;
NET "oled_sdi" LOC = F15;


さて、後はI/Oをバンバン打つだけです。

          ; =========================================================
          ; Port000[0] : SS- : Slave Select (Low Active)
          ; Port000[1] : RES : Reset        (Low Active)
          ; Port000[2] : D/C : Data/Command (Data=High, Command=Low)
          ; Port000[3] : SCK : Serial Clock (Up Edge Trigger)
          ; Port000[4] : SDI : Serial Data
          ; =========================================================
          ; ---------------------------------------------------------
          ; Register Name
          ; ---------------------------------------------------------

          ; SPI Data Register
          NAMEREG s0, SPIDATA
          ; Port Data Register
          NAMEREG s1, PORTDAT

          ; Delay Counter No.1
          NAMEREG s2, DLYCNT1
          ; Delay Counter No.2
          NAMEREG s3, DLYCNT2
          ; Delay Counter No.3
          NAMEREG s4, DLYCNT3

          ; Send Counter No.1
          NAMEREG s5, SNDCNT1
          ; Send Counter No.2
          NAMEREG s6, SNDCNT2

          ; Temporary Register No.1
          NAMEREG s7, TEMP_I
          ; Temporary Register No.2
          NAMEREG s8, TEMP_J

          ; ---------------------------------------------------------
          ; Constant Variables
          ; ---------------------------------------------------------
          CONSTANT PORT000, 00
          CONSTANT OLED_SS, 01
          CONSTANT OLED_RES, 02
          CONSTANT OLED_DC, 04
          CONSTANT OLED_SCK, 08
          CONSTANT OLED_SDI, 10
MAIN:
          CALL INIT
TESTLOOP:
          CALL INIT_ADDR
          CALL FILLR
          CALL LONGDELAY

          CALL INIT_ADDR
          CALL FILLG
          CALL LONGDELAY

          CALL INIT_ADDR
          CALL FILLB
          CALL LONGDELAY

          CALL INIT_ADDR
          CALL FILLW
          CALL LONGDELAY

          JUMP TESTLOOP
INIT_ADDR:
          ; ---------------------------------------------------------
          ; Setup Column Address
          ; ---------------------------------------------------------
          LOAD SPIDATA, 15
          CALL SEND_CMD
          LOAD SPIDATA, 10
          CALL SEND_CMD
          LOAD SPIDATA, 4F
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Setup Row Address
          ; ---------------------------------------------------------
          LOAD SPIDATA, 75
          CALL SEND_CMD
          LOAD SPIDATA, 00
          CALL SEND_CMD
          LOAD SPIDATA, 2F
          CALL SEND_CMD

          RETURN

FILLR:
          LOAD TEMP_I, 30
FILLR1:
          LOAD TEMP_J, 40
FILLR2:
          ; ---------------------------------------------------------
          ; Byte0 = B[4:0] + G[5:3]
          ; Byte1 = G[2:0] + R[4:0]
          ; ---------------------------------------------------------
          LOAD SPIDATA, 00
          CALL SEND_DAT
          LOAD SPIDATA, 1F
          CALL SEND_DAT
          SUB TEMP_J, 01
          JUMP NZ, FILLR2
          SUB TEMP_I, 01
          JUMP NZ, FILLR1
          RETURN
FILLG:
          LOAD TEMP_I, 30
FILLG1:
          LOAD TEMP_J, 40
FILLG2:
          ; ---------------------------------------------------------
          ; Byte0 = B[4:0] + G[5:3]
          ; Byte1 = G[2:0] + R[4:0]
          ; ---------------------------------------------------------
          LOAD SPIDATA, 07
          CALL SEND_DAT
          LOAD SPIDATA, C0
          CALL SEND_DAT
          SUB TEMP_J, 01
          JUMP NZ, FILLG2
          SUB TEMP_I, 01
          JUMP NZ, FILLG1
          RETURN
FILLB:
          LOAD TEMP_I, 30
FILLB1:
          LOAD TEMP_J, 40
FILLB2:
          ; ---------------------------------------------------------
          ; Byte0 = B[4:0] + G[5:3]
          ; Byte1 = G[2:0] + R[4:0]
          ; ---------------------------------------------------------
          LOAD SPIDATA, F8
          CALL SEND_DAT
          LOAD SPIDATA, 00
          CALL SEND_DAT
          SUB TEMP_J, 01
          JUMP NZ, FILLB2
          SUB TEMP_I, 01
          JUMP NZ, FILLB1
          RETURN
FILLW:
          LOAD TEMP_I, 30
FILLW1:
          LOAD TEMP_J, 40
FILLW2:
          ; ---------------------------------------------------------
          ; Byte0 = B[4:0] + G[5:3]
          ; Byte1 = G[2:0] + R[4:0]
          ; ---------------------------------------------------------
          LOAD SPIDATA, FF
          CALL SEND_DAT
          LOAD SPIDATA, FF
          CALL SEND_DAT
          SUB TEMP_J, 01
          JUMP NZ, FILLW2
          SUB TEMP_I, 01
          JUMP NZ, FILLW1
          RETURN
INIT:
          ; =========================================================
          ; SS- : Slave Select (Low Active)
          ; RES : Reset        (Low Active)
          ; D/C : Data/Command (Data=High, Command=Low)
          ; SCK : Serial Clock (Up Edge Trigger)
          ; SDI : Serial Data
          ; =========================================================

          ; ---------------------------------------------------------
          ; Reset Active
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OUTPUT PORTDAT, PORT000
          CALL DELAY

          ; ---------------------------------------------------------
          ; Reset Inactive
          ; ---------------------------------------------------------
          LOAD PORTDAT, OLED_RES
          OUTPUT PORTDAT, PORT000
          CALL DELAY

          ; ---------------------------------------------------------
          ; Setup Column Address
          ; ---------------------------------------------------------
          LOAD SPIDATA, 15
          CALL SEND_CMD
          LOAD SPIDATA, 10
          CALL SEND_CMD
          LOAD SPIDATA, 4F
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Setup Row Address
          ; ---------------------------------------------------------
          LOAD SPIDATA, 75
          CALL SEND_CMD
          LOAD SPIDATA, 00
          CALL SEND_CMD
          LOAD SPIDATA, 2F
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Setup Contrast Color A
          ; ---------------------------------------------------------
          LOAD SPIDATA, 81
          CALL SEND_CMD
          LOAD SPIDATA, 19
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Setup Contrast Color B
          ; ---------------------------------------------------------
          LOAD SPIDATA, 82
          CALL SEND_CMD
          LOAD SPIDATA, 14
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Setup Contrast Color C
          ; ---------------------------------------------------------
          LOAD SPIDATA, 83
          CALL SEND_CMD
          LOAD SPIDATA, 24
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Master Current Control
          ; ---------------------------------------------------------
          LOAD SPIDATA, 87
          CALL SEND_CMD
          LOAD SPIDATA, 0F
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Display Start Line
          ; ---------------------------------------------------------
          LOAD SPIDATA, A1
          CALL SEND_CMD
          LOAD SPIDATA, 00
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Display Offset
          ; ---------------------------------------------------------
          LOAD SPIDATA, A2
          CALL SEND_CMD
          LOAD SPIDATA, 10
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Display Mode
          ; ---------------------------------------------------------
          LOAD SPIDATA, A4
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Multiplex Ratio
          ; ---------------------------------------------------------
          LOAD SPIDATA, A8
          CALL SEND_CMD
          LOAD SPIDATA, 2F
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Dim Mode Setting
          ; ---------------------------------------------------------
          LOAD SPIDATA, AB
          CALL SEND_CMD
          LOAD SPIDATA, 12
          CALL SEND_CMD
          LOAD SPIDATA, 0C
          CALL SEND_CMD
          LOAD SPIDATA, 14
          CALL SEND_CMD
          LOAD SPIDATA, 12
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Master Configuration
          ; ---------------------------------------------------------
          LOAD SPIDATA, AD
          CALL SEND_CMD
          LOAD SPIDATA, 8E
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Phase Period Adjustment
          ; ---------------------------------------------------------
          LOAD SPIDATA, B1
          CALL SEND_CMD
          LOAD SPIDATA, 44
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Display Clock Divider
          ; ---------------------------------------------------------
          LOAD SPIDATA, B3
          CALL SEND_CMD
          LOAD SPIDATA, A0
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Enable Linear Gray Scale
          ; ---------------------------------------------------------
          LOAD SPIDATA, B9
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Precharge Level
          ; ---------------------------------------------------------
          LOAD SPIDATA, BB
          CALL SEND_CMD
          LOAD SPIDATA, 12
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set VCOMH
          ; ---------------------------------------------------------
          LOAD SPIDATA, BE
          CALL SEND_CMD
          LOAD SPIDATA, 28
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Color Depth = 64K
          ; ---------------------------------------------------------
          LOAD SPIDATA, A0
          CALL SEND_CMD
          LOAD SPIDATA, 70
          CALL SEND_CMD

          ; ---------------------------------------------------------
          ; Set Display Normal
          ; ---------------------------------------------------------
          LOAD SPIDATA, AF
          CALL SEND_CMD

          RETURN
SEND_DAT:
          ; =========================================================
          ; SS- : Slave Select (Low Active)
          ; RES : Reset        (Low Active)
          ; D/C : Data/Command (Data=High, Command=Low)
          ; SCK : Serial Clock (Up Edge Trigger)
          ; SDI : Serial Data
          ; =========================================================
SEND_DAT_SLAVE_ACTIVE:
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
SEND_DAT_SETUP:
          LOAD SNDCNT1, 08
          LOAD SNDCNT2, SPIDATA
SEND_DAT_LOOP:
          SL0 SNDCNT2
          JUMP C, SEND_DAT_H
          JUMP SEND_DAT_L
SEND_DAT_H:
          ; ---------------------------------------------------------
          ; SDI=HIGH, SCK=LOW
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
          OR PORTDAT, OLED_SDI
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; SDI=HIGH, SCK=HIGH
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
          OR PORTDAT, OLED_SCK
          OR PORTDAT, OLED_SDI
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; NEXT
          ; ---------------------------------------------------------
          JUMP SEND_DAT_NEXT
SEND_DAT_L:
          ; ---------------------------------------------------------
          ; SDI=LOW, SCK=LOW
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; SDI=LOW, SCK=HIGH
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
          OR PORTDAT, OLED_SCK
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; NEXT
          ; ---------------------------------------------------------
          JUMP SEND_DAT_NEXT
SEND_DAT_NEXT:
          SUB SNDCNT1, 01
          JUMP NZ, SEND_DAT_LOOP
SEND_DAT_END:
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_DC
          OR PORTDAT, OLED_SS
          OUTPUT PORTDAT, PORT000
          RETURN
SEND_CMD:
          ; =========================================================
          ; SS- : Slave Select (Low Active)
          ; RES : Reset        (Low Active)
          ; D/C : Data/Command (Data=High, Command=Low)
          ; SCK : Serial Clock (Up Edge Trigger)
          ; SDI : Serial Data
          ; =========================================================
SEND_CMD_SLAVE_ACTIVE:
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
SEND_CMD_SETUP:
          LOAD SNDCNT1, 08
          LOAD SNDCNT2, SPIDATA
SEND_CMD_LOOP:
          SL0 SNDCNT2
          JUMP C, SEND_CMD_H
          JUMP SEND_CMD_L
SEND_CMD_H:
          ; ---------------------------------------------------------
          ; SDI=HIGH, SCK=LOW
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_SDI
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; SDI=HIGH, SCK=HIGH
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_SCK
          OR PORTDAT, OLED_SDI
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; NEXT
          ; ---------------------------------------------------------
          JUMP SEND_CMD_NEXT
SEND_CMD_L:
          ; ---------------------------------------------------------
          ; SDI=LOW, SCK=LOW
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; SDI=LOW, SCK=HIGH
          ; ---------------------------------------------------------
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_SCK
          OUTPUT PORTDAT, PORT000
          CALL DELAY
          ; ---------------------------------------------------------
          ; NEXT
          ; ---------------------------------------------------------
          JUMP SEND_CMD_NEXT
SEND_CMD_NEXT:
          SUB SNDCNT1, 01
          JUMP NZ, SEND_CMD_LOOP
SEND_CMD_END:
          LOAD PORTDAT, 00
          OR PORTDAT, OLED_RES
          OR PORTDAT, OLED_SS
          OUTPUT PORTDAT, PORT000
          RETURN
DELAY:
          LOAD DLYCNT1, 01
DELAY1:
          LOAD DLYCNT2, 01
DELAY2:
          SUB DLYCNT2, 01
          JUMP NZ, DELAY2
          SUB DLYCNT1, 01
          JUMP NZ, DELAY1
          RETURN
LONGDELAY:
          LOAD DLYCNT1, 80
LONGDELAY1:
          LOAD DLYCNT2, FF
LONGDELAY2:
          LOAD DLYCNT3, FF
LONGDELAY3:
          SUB DLYCNT3, 01
          JUMP NZ, LONGDELAY3
          SUB DLYCNT2, 01
          JUMP NZ, LONGDELAY2
          SUB DLYCNT1, 01
          JUMP NZ, LONGDELAY1
          RETURN

もう完全にPicoBlazeの使い方としてはおかしいですが、第一段階としては気にしません。
上記のコードで「赤」、「緑」、「青」、「白」と順に表示を繰り返す事ができます。




リソースの使用率ですが、以下のようになりました。


Bit Bangはあんまりなので、SPIモジュールを作ってやってデータを渡すのが良いでしょう。
データを渡して、転送開始入力信号と転送完了出力信号が出てくるようなモジュールです。

フォントなどを内蔵すればもっと手軽に表示が楽しめそう。
上記を雛型にすればこういった拡張も簡単です。

ちなみに、今回はBit Bangの基本動作を外部のロジック・アナライザで確認した後、セットアップとホールド、クロックサイクルを見ながらアセンブラ・コードをチューニングし、波形が期待通りに出力されている事を確認した後で、最後にIS-C15ANP4を接続するという形で作業しました。(ChipScope使えよという感じです。)


PicoBlazeのアセンブラも、最初のバージョンより気が利くようになっていて、一度立ち上げてプログラム・ファイル名を入力しておけば「R」をタイプするだけで再コンパイルできるようになっています。


なので、アセンブルの後で生成されたVHDLををプロジェクトにコピーするバッチファイルを作っておけば、「アセンブル→再インプリメンテーション」の流れをスムーズにこなす事ができます。


こんな感じで(超適当だけど)半田付けを含めて小1時間ほどでPicoBlazeからIS-C15ANP4に絵が出せるようになりました。


念のために書いておくと、IS-C15ANP4には決められた電源シーケンスがあります。
製品に使用する場合、きちんとシーケンスを守るように設計しましょう。


最後にグリーンな表示。

ちなみに、先ほどの初期化コードの場合、2バイトで1ピクセルを表現するようになっています。
BとRが5ビット、Gが6ビットで、1バイト目がB[4:0] + G[5:3]、2バイト目がG[2:0] + R[4:0]です。


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いじりなんていうのも、たまには面白いのではないでしょうか。