FPGA
Field Programmable Gate Arayの略。
マイコンと同じようにソフトウェアで動作を変えられる。
マイコンと大きな違いは、CPUが実行する1行1行のプログラムを書くのではなく、
内部の素子をどうつなげるかを記述することで、目的の回路を作るという点。
並列処理が得意な一方で、マイコンのような順次構文を苦手とする。
入手しやすいFPGA
残念ながら個人でFPGAの入手性はあまりよくなく、
秋月で購入できるものも数種類程度。
ここでは少し高いけど実用的と思われる、
Cmod A7-35T(1万弱)を使って解説してみる。
XilinxのFPGA,Artix-7シリーズを搭載しており、
48pinのDIPになっているのでロボットにも載せられるサイズ。
開発環境
Xilinxの開発環境Vivadoは登録が必要なものの無償ダウンロードが可能。
Vivadoは毎年アップデートされていて、性能が上がっている。
まず公式ページより、Vivado HLxをダウンロードする。
それが50Mで若干重いと思ったら、
インストーラなだけでソフトそのものは5G程度ある。
ソフトの起動であったり、論理合成だったり結構動作が重いので、デスクトップPC推奨。
ソフトの準備
本来FPGAの入出力の情報をすべて設定しなければいけないところ、
Cmod A7-35Tはすでに用意されている。
まずはCmod A7 GPIO Demoをダウンロードする。
まず設定をまとめたプロジェクトファイルを作成しなければならない。
上記のファイルをダウンロードし圧縮ファイルを展開する。
Vivadoを開き、ToolsからRun Tcl Scriptを選ぶと選択ウィンドウが出るので、
projフォルダ内にある.tclという拡張子のファイルを選ぶ。
すると設定が行われたプロジェクトファイルが出来上がる。
プロジェクトの編集画面が開くのでざっくりと内容を説明。
左側が上から順に行っていく論理合成やファイル生成の選択画面。
右側がソースコードの一覧やプロジェクトの概要、生成の結果等が表示される。
プロジェクトを開けたのはいいが、バージョン違いのためかファイルが読み込まれないので、Sourcesの+マーク(ADD Sources)でファイルを読み込む。
サンプルのsrcフォルダ内がソースファイルとなっているので、Add or Create Constraintsでxdcのファイルを、
Add or Create design sourcesで拡張子がxci,vhdのファイルを読み込む。
読み込むとSourcesの一覧の中に読み込まれたファイルが分類されて表示される。
Copy Sources into projectをチェックしていると、新しく作られたフォルダにコピーされたファイルで編集が始まるので、流用するときは注意。
xdcファイルはFPGAのピン割り付けや起動時の読み込み動作、論理合成時の条件など論理合成に必要な情報をまとめたファイル。
xciのファイルはIP(ソフトが用意したマクロファイルのようなもの)の設定をまとめたファイル。
ソフトのバージョンが異なると更新する必要があるので、右クリックからUpgrade IPを選ぶ。
続いてトップモジュールの設定を行う。
Settings(config)>General>Top module nameに、今回のvhdlファイルの名前を入れる。
ついでにTarget LanguageをVHDLに変えておくといい。
FPGAのソフトは主にVHDL又はVerilogで書くことになる。
今回vhdの拡張子のファイルがあるので、LanguageはVHDLとなる。
いよいよ論理合成を行っていく。
Run Synthesisを実行。
うまくいけば、そのままImplementaionも実行。
そのままGenerate Bitstreamに進みたいところだが、追加の設定が必要。
XilinxのFPGAの特徴として、プログラムは本体ではなく外付けのメモリに書いて起動時に読み込むので、
その設定を行う。
今回はSPIx4にしたいので、
Config>Bitstream>Addtional bitstream settingで、SPI_BUSWIDTHを4に変更。
ついでに書込み速度を3MHzから上げておくと立ち上がり時間が短くなる。
そのあとGenerate Bitstreamを実行。
それなりに重い処理なので、ノートパソコンだと時間がかかるかも。
成功すればCompletedウィンドウが出るので、
Generate Memory Configuration Fileを選ぶ。
以下のように入力していく。
ファイルフォーマットはMCS,BIN,HEXどれでもいいが、
無難なものはMCSかHEX
メモリは4MB
今回生成する任意のファイル名を入力する。
InterfaceはSPIx4
Load bitstream filesにチェックを入れるとファイルが選択できるようになる。
runs>impl>GPIO_DEMO.bitというファイルができているので、こちらを選ぶ。
これでOKを押せば問題なければ拡張子がMCS,BIN,HEXいずれかのファイルが出来上がっている。
これで書込みの準備が完了。
書込み
ソフトを書き込むには、USB microBのケーブルでパソコンとCmod A7をつないだ状態で、Open Hardwear Managerを開く。
上にOpen targetという選択肢があるので選択すると、
Cmod A7が接続されていればFPGAの型番が表示される。
FPGAを右クリックしてProgramを選択すれば、とりあえず実行できるが、あくまでFPGA上に展開されただけで電源を落とすと消えてしまう。
FPGA本体ではなく、メモリに記録するためには、Hardweareのxc7a35t_0を右クリックで、
Add Configration Memory Deviceを選び、N25Q32-3.3V-spi-x1_x2_x4を見つける。
すると、Hardwearの一覧にメモリが追加されるので、追加されたメモリの右クリックメニューから、
Program Configration Memory Deviceを選ぶ。
ウィンドウが出るので、
ファイル名や書き込みの設定を選び、OKをすれば書き込まれる。
書き込み設定は、消去、ブランクチェック、プログラム、ベリファイをチェックする。
PRMファイルはブランクでOK。
書き込まれると、電源で再起動してもプログラムが実行されます。
かわロボ向けソフトの修正
ここからは実際の動作に合わせてプログラムを書き換えたいと思います。
かわロボでは基本的にプロポの受信機とアンプの間にFPGAをはさみ、信号を変換することになる。
そこで、まずは受信機の波形確認を行ってみたい。
まずはPROJECT MANEGAERに戻り、Sourcesを確認。
今回のファイルは、GPIO_demoと、
その下にinst_clk、inst_btn_debounce、inst_UART_TX_CTRL、RGB_Core1の4つがぶら下がった構造になっている。
RTL ANALAYSISのOpen Elaborated Designを開くと、内部の回路の構成を見ることができる。
FPGAのソフトの作り方として、(一般的でもありますが)
よく使われそうな部分は別のファイルにブロック化して作成する。
今回のソフトで特徴的な部分は一番上の階層(GPIO_demo.vhd)、
再利用できそうな部分はその下の階層(下の4つのファイル)として、一番上のファイルから呼び出す。
そうするとデバッグや再利用が楽になる。
まずは入力端子の設定。
まずはxdcファイルを編集。
どの端子にも入出力設定が可能であるため、とりあえず1番ピンに割り振る。
xdcファイルは不要なピンの設定がコメントアウトされているため、
設定したいピンの行のコメントを解除する。
xdcファイルのコメント解除は、行の最初のシャープ記号#を削除すればいい。
今回は1番ピンに割り当てられている、M3ピンの行のコメントを解除する。
ピン名称は(get_ports )pio[01]となっているので、そのまま使用したいところだが、[01]は配列のため今回は削除。
GPIO.vhdに戻る。
entity GPIO_demoの中に、コメント解除したピンの設定を以下のように追加する。
pin : in STD_LOGIC
前後の行の関係でセミコロンが必要になるので注意。
次にPWM入力を変換するモジュールを作る。
Add or Create Design Sourcesから、Create Fileを選び、ファイル名を入力する。
そのあと、モジュールの入出力設定の画面が出てくるので、以下を参考に入出力を設定する。
pwm_in in pwm入力を入れるところ
clk in 時間計測するクロックを入れるところ
sign out ベースクロックからプラス方向かマイナス方向かを入れるところ
power_out out(10bit) 出力
EMG in 緊急時に信号を0にするところ
out_en out 更新フラグ
画面が戻ると追加したファイルがトップモジュールと同じ高さに並んでいる。
この後トップモジュールで追加したモジュールを呼び出せば勝手に順序を変えてくれる。
それでは追加したモジュールの編集を開始する。
まずは計測のベースとなるクロックを設定する。
基板上には12MHzのクロックが取り付けられているが、内部のPLLで100MHzのクロックが使用できる。
しかしPWMの1.52ms±0.4msを計測するには十分すぎるほど早いので減速する。
減速するのは簡単で、カウンタを回せばいい。
RBG_controller.vhdに参考となるカウンタが実装されているので、真似して実装する。
100分の1で1MHzであれば、1520±400とms単位で取れるのでいいところだろう。
立下りのエッジを検出して、カウンタの更新と消去を行う。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
USE IEEE.NUMERIC_STD.ALL;
entity pwm_in is
Port ( clk : in STD_LOGIC;
pwm_in : in STD_LOGIC;
sign : out STD_LOGIC;
power_out : out STD_LOGIC_VECTOR (9 downto 0);
out_en : out STD_LOGIC;
EMG : in STD_LOGIC;
count : out STD_LOGIC_VECTOR(15 downto 0));
end pwm_in;
architecture Behavioral of pwm_in is
constant PrsCountMax: std_logic_vector(7 downto 0) := std_logic_vector(to_unsigned(99, 8));
signal PrsCount: std_logic_vector(7 downto 0) := (others => '0');
signal prsClk : std_logic;
constant in_count_max:std_logic_vector(15 downto 0) := std_logic_vector(to_unsigned(20000, 16));
signal in_count:std_logic_vector(15 downto 0);
signal pwm_in_edge : std_logic_vector(3 downto 0);
signal in_count_over:std_logic;
signal power_out_in:std_logic_vector(15 downto 0);
signal power_out_in1:std_logic_vector(15 downto 0);
constant power_out_backlash_count : std_logic_vector(15 downto 0) := std_logic_vector(to_unsigned(20, 16));
constant pwm_in_base_count : std_logic_vector(15 downto 0):= std_logic_vector(to_unsigned(1520, 16));
constant power_out_max : std_logic_vector(15 downto 0):= std_logic_vector(to_unsigned(1000, 16));
begin
base_clk_prs:process(clk)
begin
if(rising_edge(clk)) then
if(PrsCount < PrsCountMax) then
PrsCount <= PrsCount + 1;
prsClk <= '0';
else
PrsCount <= (others => '0');
prsClk <= '1';
end if;
end if;
end process;
pwm_in_count:process(prsClk)
begin
if(rising_edge(prsClk)) then
if((pwm_in = '1') and (in_count < in_count_max))then
in_count <= in_count + 1;
in_count_over <= '0';
elsif (pwm_in = '1')then
in_count <= in_count;
in_count_over <= '1';
elsif(pwm_in_edge = "1000")then
in_count <= (others =>'0');
in_count_over <= in_count_over;
count <= in_count;
else
in_count <= in_count;
in_count_over <= in_count_over;
end if;
end if;
end process;
pwm_in_edge_update:process(prsClk)
begin
if(rising_edge(prsClk)) then
pwm_in_edge(3) <= pwm_in_edge(2);
pwm_in_edge(2) <= pwm_in_edge(1);
pwm_in_edge(1) <= pwm_in_edge(0);
pwm_in_edge(0) <= pwm_in;
end if;
end process;
power_out_update:process(prsClk)
begin
if(rising_edge(prsClk)) then
if(in_count_over = '1' or EMG = '1')then
power_out <= (others => '0');
power_out_in <= (others => '0');
sign <= '0';
out_en <= '1';
elsif(pwm_in_edge = "1110")then
if(in_count >= pwm_in_base_count)then
power_out_in <= in_count - pwm_in_base_count;
sign <= '0';
out_en <= '0';
else
power_out_in <= pwm_in_base_count - in_count;
sign <= '1';
out_en <= '0';
end if;
elsif(pwm_in_edge = "1100")then
if(power_out_in > power_out_backlash_count)then
power_out_in1 <= power_out_in - power_out_backlash_count;
else
power_out_in1 <= (others => '0');
end if;
out_en <= '0';
elsif(pwm_in_edge = "1000")then
if(power_out_in1 > power_out_max)then
power_out <= power_out_max(9 downto 0);
else
power_out <= power_out_in1(9 downto 0);
end if;
out_en <= '1';
else
out_en <= '0';
end if;
end if;
end process;
end Behavioral;
ついでにLEDのPWM減光モジュールを作っておいて、実装。
こちらもカウンタを回して比較するだけでOK。
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
USE IEEE.NUMERIC_STD.ALL;
entity LED_control is
Port ( LED_pin : out STD_LOGIC;
clk : in STD_LOGIC;
power : in STD_LOGIC_VECTOR (15 downto 0));
end LED_control;
architecture Behavioral of LED_control is
signal powerCounter : std_logic_vector( 15 downto 0);
constant powerCountMax: std_logic_vector(15 downto 0) := std_logic_vector(to_unsigned(1000, 16));
begin
led_counting:process(clk)
begin
if(rising_edge(clk)) then
if(powerCounter < powerCountMax)then
powerCounter <= powerCounter + '1';
else
powerCounter <= (others => '0');
end if;
end if;
end process;
led_out_update:process(clk)
begin
if(rising_edge(clk)) then
if(power < powerCounter)then
LED_pin <= '0';
else
LED_pin <= '1';
end if;
end if;
end process;
end Behavioral;
あとはトップモジュールでこれらのモジュールをつなぐ。
PWM_in1 : pwm_in port map(
clk => CLK100,
pwm_in => pio,
EMG => BTN(0),
sign => LED(0),
power_out => led_power( 9 downto 0),
out_en => convert_en,
count => pwm_hex
);
led_power(15 downto 10) <= (others => '0');
LED_out1: LED_control port map(
clk => CLK100,
power => led_power,
LED_pin => LED(1)
);
(追加分の抜粋)
正常に実装できていれば、プロポのスティックに応じてLEDの明るさが変わるだろう。
詰まったところ
Vivado 2019.1で、Bitstream生成がうまくいかない。
止めるまでlogの更新は無し。
止めて表示されるエラーメッセージは「1.PID not found」、検索してもよくわからなかった。
PIDはプロセス識別子で、Vivadoから別のVivadoを画面無しで立ち上げて、生成を行っている模様。そのためセキュリティソフト等でアクセスがブロックされているかもしれない。
まずはファイアウォールでアクセスを許可。
それでもうまくいかなったので、implフォルダを確認。
このフォルダはImplementaion開始時は空でも問題ないので、残留物があれば削除。
その上で実行しても止まるようであれば、まずstop.rstファイルを削除。
runme.batファイルをテキストエディタで編集し、「@echo off」を削除して最後に「pause」を追加。
そのうえで実行すると、「runme.logが書込みできない」みたいなエラーが出ていたので、runme.logを削除。
もう一度runme.batを実行するとVivadoの生成が動き始めた。
とりあえずここまで。
最終更新:2019年10月14日 11:06