約半年前に、FPGAのプログラミングに挑戦することにしました。 それ以前は、回路に遭遇したことはありませんでした。 マイクロコントローラー(Atmega328p、STM32)の使用経験はほとんどありませんでした。 FPGAに慣れるという決定の直後に、使用する言語を選択するという疑問が生じました。 厳密なタイピングのため、VHDLで選択が行われました。 初心者として、私は動作中のデバイスではなく、合成段階で可能な限り多くの問題をキャッチしたかったのです。
なぜ正確に7セグメントディスプレイなのか? LEDの点滅はすでに疲れており、点滅のロジックは興味深いものではありません。 ディスプレイを制御するロジックは、一方ではLEDを点滅させるよりも複雑であり(つまり、それを書くことはより興味深い)、他方では実装が非常に簡単です。
デバイスの作成プロセスで使用したもの:
- FPGAアルテラCyclone II(私はそれが絶望的に古くなっていることを知っていますが、中国人はそれを一銭で買うことができます)
- Quartus IIバージョン13.0.0(私の知る限り、これはCyclone IIをサポートする最新バージョンです)
- シミュレーターModelSim
- シフトレジスタ付きの7セグメントディスプレイ
挑戦する
サイクルで0〜9の数字を表示するデバイスを作成します。1秒に1回、ディスプレイに表示される値が1増加します。
このロジックを実装するには多くの方法があります。 このデバイスをモジュールに分割します。各モジュールは何らかのアクションを実行し、このアクションの結果は次のモジュールに送信されます。
モジュール
- このデバイスは時間をカウントできる必要があります。 時間を数えるために、「遅延」モジュールを作成しました。 このモジュールには、1つの着信信号と1つの発信信号があります。 モジュールは、FPGA周波数信号を受信し、入力信号の指定された数の周期の後、出力信号の値を反対に変更します。
- デバイスは0〜9を読み取る必要があります。これにはbcd_counterモジュールが使用されます。
- ディスプレイ上のセグメントを点灯するには、ディスプレイのシフトレジスタでセグメントに対応するビットを0に設定し、セグメントをビットにクリアするために1を書き込む必要があります(ディスプレイには反転ロジックがあります)。 bcd_2_7segデコーダーは、必要なビットのインストールとリセットを処理します。
- 送信モジュールはデータ転送を担当します。
ホストデバイスは、モジュール間の信号の正しい送信を制御し、データ転送の完了時にrclk信号を生成します。
わかりやすくするために、このデバイスの図を示します
図からわかるように、デバイスには1つの着信信号(clk)と3つの発信信号(sclk、dio、rclk)があります。 clk信号には、2つの信号分割器(sec_delayおよびtransfer_delay)があります。 周期が1秒の発信信号は、sec_delayデバイスから出ます。 この信号の立ち上がりエッジで、カウンター(bcd_counter1)が表示用の次の数値の生成を開始します。 数値が生成された後、デコーダー(bcd_2_7seg1)は、数値のバイナリ表現をディスプレイ上の点灯セグメントと非点灯セグメントに変換します。 これは、トランスミッター(transmitter1)を使用して、ディスプレイに送信されます。 トランスミッターはtransfer_delayデバイスを使用してクロックされます。
コード
VHDLでデバイスを作成するには、エンティティとアーキテクチャの2つのコンポーネントの構築が使用されます。 エンティティは、デバイスを操作するためのインターフェイスを宣言します。 アーキテクチャは、デバイスのロジックを記述します。
遅延デバイスのエンティティは次のようになります
entity delay is -- entity, generic generic (delay_cnt: integer); -- port(clk: in std_logic; out_s: out std_logic := '0'); end entity delay;
汎用フィールドを使用して、デバイスを目的の遅延に設定できます。 また、ポートフィールドでは、デバイスの着信信号と発信信号を記述します。
遅延デバイスのアーキテクチャは次のとおりです。
-- architecture , -- entity 0 architecture delay_arch of delay is begin delay_proc: process(clk) variable clk_cnt: integer range 0 to delay_cnt := 0; variable out_v: std_logic := '0'; begin -- if(rising_edge(clk)) then clk_cnt := clk_cnt + 1; if(clk_cnt >= delay_cnt) then -- switch/case VHDL case out_v is when '0' => out_v := '1'; when others => out_v := '0'; end case; clk_cnt := 0; -- out_s out_v out_s <= out_v; end if; end if; end process delay_proc; end delay_arch;
プロセスセクション内のコードは順番に実行され、他のコードは並行して実行されます。 カッコ内のprocessキーワードの後に、指定されたプロセスの開始を変更することでシグナルが示されます(感度リスト)。
bcd_counterデバイスは、実行ロジックに関して、遅延デバイスと同じです。 したがって、これについては詳しく説明しません。
これは、デコーダのエンティティとアーキテクチャがどのように見えるかです
entity bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0) := X"0"; disp_out: out std_logic_vector(7 downto 0) := X"00"); end entity bcd_to_7seg; architecture bcd_to_7seg_arch of bcd_to_7seg is signal not_bcd_s: std_logic_vector(3 downto 0) := X"0"; begin not_bcd_s <= not bcd; disp_out(7) <= (bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1) and bcd(0)); disp_out(6) <= (bcd(2) and not_bcd_s(1) and bcd(0)) or (bcd(2) and bcd(1) and not_bcd_s(0)); disp_out(5) <= not_bcd_s(2) and bcd(1) and not_bcd_s(0); disp_out(4) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1) and bcd(0)) or (bcd(2) and not_bcd_s(1) and not_bcd_s(0)) or (bcd(2) and bcd(1) and bcd(0)); disp_out(3) <= (bcd(2) and not_bcd_s(1)) or bcd(0); disp_out(2) <= (not_bcd_s(3) and not_bcd_s(2) and bcd(0)) or (not_bcd_s(3) and not_bcd_s(2) and bcd(1)) or (bcd(1) and bcd(0)); disp_out(1) <= (not_bcd_s(3) and not_bcd_s(2) and not_bcd_s(1)) or (bcd(2) and bcd(1) and bcd(0)); disp_out(0) <= '1'; end bcd_to_7seg_arch;
このデバイスのすべてのロジックは並行して実行されます。 チャンネルのビデオの1つで、このデバイスの公式を取得する方法について話しました。 誰も気にしない、ここにビデオへのリンクがあります 。
トランスミッターデバイスでは、シリアルロジックとパラレルロジックを組み合わせます
entity transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0) := X"00"; digit: in std_logic_vector(7 downto 0) := X"00"; sclk, dio: out std_logic := '0'; ready: buffer boolean := true); end entity transmitter; architecture transmitter_arch of transmitter is constant max_int: integer := 16; begin sclk <= clk when not ready else '0'; send_proc: process(clk, enable, ready) variable dio_cnt_v: integer range 0 to max_int := 0; variable data_v: std_logic_vector((max_int - 1) downto 0); begin -- dio clk if(falling_edge(clk) and (enable or not ready)) then if(dio_cnt_v = 0) then -- , -- data_v := digit_pos & digit; ready <= false; end if; if(dio_cnt_v = max_int) then dio_cnt_v := 0; ready <= true; dio <= '0'; else dio <= data_v(dio_cnt_v); dio_cnt_v := dio_cnt_v + 1; end if; end if; end process send_proc; end transmitter_arch;
sclk信号に、送信機に入るclk信号の値をリダイレクトしますが、これはデバイスが現在データを送信している場合のみです(signal ready = false)。 それ以外の場合、sclk信号値は0になります。データ転送の開始時(enable = true信号)、2つの8ビットベクトル(digit_posおよびdigit)からのデータを結合して、デバイスに16ビットベクトル(data_v)を入力して送信します。このベクトルからのデータ、クロックごとに1ビット、送信ビットの値を発信信号dioに設定します。 このデバイスの興味深い点は、dioのデータがclk信号のトレーリングエッジに設定されており、sclk信号のリーディングエッジが到着すると、ピンdioからのデータがディスプレイのシフトレジスタに書き込まれることです。 送信が完了したら、送信準備完了<= true信号を送信が完了した他のデバイスに設定します。
ディスプレイデバイスのエンティティとアーキテクチャは次のようになります
entity display is port(clk: in std_logic; sclk, rclk, dio: out std_logic := '0'); end entity display; architecture display_arch of display is component delay is generic (delay_cnt: integer); port(clk: in std_logic; out_s: out std_logic := '0'); end component; component bcd_counter is port(clk: in std_logic; bcd: out std_logic_vector(3 downto 0)); end component; component bcd_to_7seg is port(bcd: in std_logic_vector(3 downto 0); disp_out: out std_logic_vector(7 downto 0)); end component; component transmitter is port(enable: in boolean; clk: in std_logic; digit_pos: in std_logic_vector(7 downto 0); digit: in std_logic_vector(7 downto 0); sclk, dio: out std_logic; ready: buffer boolean); end component; signal sec_s: std_logic := '0'; signal bcd_counter_s: std_logic_vector(3 downto 0) := X"0"; signal disp_out_s: std_logic_vector(7 downto 0) := X"00"; signal tr_enable_s: boolean; signal tr_ready_s: boolean; signal tr_data_s: std_logic_vector(7 downto 0) := X"00"; -- , tr_ready_s -- rclk signal disp_refresh_s: boolean; signal transfer_clk: std_logic := '0'; begin sec_delay: delay generic map(25_000_000) port map(clk, sec_s); transfer_delay: delay generic map(10) port map(clk, transfer_clk); bcd_counter1: bcd_counter port map(sec_s, bcd_counter_s); bcd_to_7seg1: bcd_to_7seg port map(bcd_counter_s, disp_out_s); transmitter1: transmitter port map(tr_enable_s, transfer_clk, X"10", tr_data_s, sclk, dio, tr_ready_s); tr_proc: process(transfer_clk) variable prev_disp: std_logic_vector(7 downto 0); variable rclk_v: std_logic := '0'; begin if(rising_edge(transfer_clk)) then -- if(tr_ready_s) then -- if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s; -- tr_data_s <= disp_out_s; -- tr_enable_s <= true; end if; else disp_refresh_s <= true; -- -- , -- tr_enable_s <= false; end if; if(rclk_v = '1') then disp_refresh_s <= false; end if; if(tr_ready_s and disp_refresh_s) then rclk_v := '1'; else rclk_v := '0'; end if; rclk <= rclk_v; end if; end process tr_proc; end display_arch;
このデバイスは他のデバイスを制御します。 ここで、補助信号を宣言する前に、使用するコンポーネントを宣言します。 アーキテクチャ自体(beginキーワードの後)で、デバイスインスタンスを作成します。
- sec_delay-遅延コンポーネントのインスタンス。 発信信号はsec_sにルーティングされます。
- transfer_delay-遅延コンポーネントのインスタンス。 発信信号はtransfer_clk信号に送信されます。
- bcd_counter1-bcd_counterコンポーネントのインスタンス。 発信信号はbcd_counter_sにルーティングされます。
- bcd_to_7seg1-bcd_to_7segコンポーネントのインスタンス。 発信信号はdisp_out_sにルーティングされます。
- sender1は、トランスミッタコンポーネントのインスタンスです。 発信信号はsclk、dio、tr_ready_s信号に送信されます。
コンポーネントインスタンスの後に、プロセスが宣言されます。 このプロセスは、いくつかの問題を解決します。
- トランスミッタがビジーでない場合、プロセスはデータ転送の開始を初期化します。
if(tr_ready_s) then if(not (prev_disp = disp_out_s)) then prev_disp := disp_out_s; -- -- tr_data_s <= disp_out_s; -- tr_enable_s <= true; end if; else ...
- トランスミッタがビジーの場合(tr_ready_s = false)、プロセスは信号disp_refresh_s <= trueの値を設定します(この信号は、転送が完了した後、ディスプレイ上のデータを更新する必要があることを示します)。 信号値tr_enable_s <= falseも設定されます。これは、転送が完了する前に行われない場合、送信機にアップロードされたデータが送信されます
- データ転送の完了後にrclk信号を設定およびリセットします
if(rclk_v = '1') then disp_refresh_s <= false; end if; if(tr_ready_s and disp_refresh_s) then rclk_v := '1'; else rclk_v := '0'; end if; rclk <= rclk_v;
タイミングチャート
これは、数字1をディスプレイの最初の位置に転送するタイミング図です。
データ「10011111」が最初に送信されます。 ディスプレイ上の番号の位置は「00010000」です(このパラメーターは定数X「10」として送信機に到達します)。 どちらの場合も、右端のビット(lsb)が最初に送信されます。
すべてのコードはgithubで表示できます。 サフィックスが* _tb.vhdのファイルは、対応するコンポーネントのデバッグファイルです(たとえば、transmitter_tb.vhdはトランスミッタのデバッグファイルです)。 念のため、それらもgithubにアップロードしました。 このコードはダウンロードされ、実際のボードで動作しました。 気にする人は、 ここでコードの図を見ることができます (15:30から)。 ご清聴ありがとうございました。