VHDLで簡単にトリガーをシミュレーションする方法

# VHDLでトリガーを生成する方法

ここでは，VHDLでトリガーを生成する方法を紹介したい．ただし，事前に，[VHDLの簡単なシミュレーション方法(初学者向け)](https://qiita.com/yamadasuzaku/items/d67fedd4fea6ce351718) を読んで動かせていることが前提となります．

このでのトリガーとは，ある条件を満たすイベントが起こったと判定される事象で，その瞬間のちょっと前のデータから一定の長さのデータを保存することを目標とします．例えば， edge triggerを設定して，トリガーがかかったら 1024 サンプル分の波形を保存する，というような目的を想定しています．ここでは，シフトレジスタを使った簡単なトリガーシステムの紹介になります．

雷雲観測プロジェクトの[GROWTH-DAQ (by湯浅くん）](https://github.com/growth-team/GROWTH-DAQ) を参考にしています．

## シフトレジスタのシミュレーション方法

レジスタとはデータを一時的に記憶するメモリのことです．シフトレジスタとは，記憶しているデータの桁を左右にシフトさせることができるレジスタのことです．これを使うと，例えば，A/D変換された信号を常に1ms分を保存しておくようなことが出来ます．具体的には，カメラで画像に変化がないか監視するシステムの場合，変化が起こったと判定されたときに，その瞬間からではなくて，その時点から1ms過去のデータから保存することができます．

## コード

### 設定値などの共通パラメータ

定数(具体的には，AD/DAのbit数，delayの幅など)は，デバイスに依存して変わりうるので，ベタ書きしないで，C言語でいうヘッダーファイルに書き込んでおく．ここでは，次のような共通に使われるファイルを用いる．

``` VHDL:UserModule_LibrarySy1.vhdl
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

package UserModule_LibrarySy1 is
  constant ADCResolution : integer := 14;      --10bit ADC
  constant WidthOfDepthOfDelay    : integer := 7;  --max depth=2^7=128  
  constant MaximumOfDelay         : integer := 32;    --32clk delay
end UserModule_LibrarySy1;
```

### シフトレジスタのVHDL

シフトレジスタの御本尊である．
冒頭の， use work.UserModule_LibrarySy1.all; の work は今いるディレクトリくらいの意味で，
python で言う所の， import UserModule_LibrarySy1 くらいのニュアンスです．


``` VHDL:UserModule_ChannelModule_DelaySy1.vhdl
-- UserModule_ChModule_Delay.vhdl
-- Delay incoming ADC data

library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
-- use work.iBus_Library.all;
-- use work.iBus_AddressMap.all;
--library IEEE;
--use IEEE.numeric_std.all;
use work.UserModule_LibrarySy1.all;

entity UserModule_ChModule_DelaySy1 is
  port(
    DepthOfDelay : in  std_logic_vector(WidthOfDepthOfDelay-1 downto 0);
    --
    AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
    AdcDataOut              : out std_logic_vector(ADCResolution-1 downto 0);
    --clock and reset
    Clock                   : in  std_logic;
    GlobalReset             : in  std_logic
    );
end UserModule_ChModule_DelaySy1;

architecture Behavioral of UserModule_ChModule_DelaySy1 is

  --Signals
  signal Trigger        : std_logic                         := '0';
  signal a              : integer range 0 to MaximumOfDelay := 0;
  --Registers
  signal SampleRegister : std_logic_vector(15 downto 0)     := (others => '0');
  signal InputRegister  : std_logic_vector(15 downto 0)     := (others => '0');
  signal OutputRegister : std_logic_vector(15 downto 0)     := (others => '0');
  signal tmpAdcDataOut  : std_logic_vector(ADCResolution-1 downto 0); -- just for timing check

  type   AdcDataVector is array (integer range <>) of std_logic_vector(ADCResolution-1 downto 0);
  signal AdcDataArray : AdcDataVector(MaximumOfDelay downto 0) := (others=> (others=>'0'));

begin
  MainProcess : process (Clock, GlobalReset)
  begin
    tmpAdcDataOut <= AdcDataIn; -- just for timing check 
    if (GlobalReset = '0') then
    elsif (Clock'event and Clock = '1') then
      -- Shift register
      AdcDataArray <= AdcDataArray(MaximumOfDelay-1 downto 0) & AdcDataIn;
      report "[S] AdcDataOut is updated";
      -- Select output
      if (a < MaximumOfDelay) then
        AdcDataOut <= AdcDataArray(a);
      end if;

      if (conv_integer(DepthOfDelay) < MaximumOfDelay) then
        a <= conv_integer(DepthOfDelay);
      else
        a <= 0;
      end if;
    end if;
  end process;  

end Behavioral;
```

### シフトレジスタをシミュレートするVHDLのテストベンチ

シミュレーションをする上では，クロックや，波形を自分で与える必要あります．また， debug がしやすいように，各所に report 文 (pythonで言うところのprint文) を入れて，中身をチェックします．


``` VHDL:
---------------------------------------------------
--Declarations of Libraries
---------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
--use ieee.numeric_std.ALL;
--use work.iBus_Library.all;
--use work.iBus_AddressMap.all;
use work.UserModule_LibrarySy1.all;

-- use std.env.finish;

entity sr_tb is
end sr_tb; 

architecture sim of sr_tb is

  constant clock_period : time := 10 ns;

  -- Generics
  constant RAM_DEPTH : natural := 100000;

  -- from Library
  signal DepthOfDelay      : std_logic_vector(WidthOfDepthOfDelay-1 downto 0) := "0000011";

  -- DUT signals
  signal Clock : std_logic := '1';
  signal GlobalReset : std_logic := '1';
  signal AdcDataIn : std_logic_vector(ADCResolution - 1 downto 0) := (others => '0');
  signal AdcDataOut : std_logic_vector(ADCResolution - 1 downto 0):= (others => '0');

  signal counter : integer range RAM_DEPTH - 1 downto 0;

  component UserModule_ChModule_DelaySy1
    port(
      DepthOfDelay : in  std_logic_vector(WidthOfDepthOfDelay-1 downto 0);
      --
      AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
      AdcDataOut              : out std_logic_vector(ADCResolution-1 downto 0);
      --clock and reset
      Clock                   : in  std_logic;
      GlobalReset             : in  std_logic
      );
  end component;


begin

  -- Process for generating the clock
  Clock <= not Clock after clock_period / 2;

  inst_DelayModule : UserModule_ChModule_DelaySy1
    port map(
      DepthOfDelay => DepthOfDelay,
      --
      AdcDataIn               => AdcDataIn,
      AdcDataOut              => AdcDataOut,
      --clock and reset
      Clock                   => Clock,
      GlobalReset             => GlobalReset
      );


  PROC_SEQUENCER : process is 
  begin
    report "[T] start";
    counter <= 0;    
    GlobalReset <= '0';    
    wait for 5 * clock_period;    
--    wait until rising_edge(Clock);
    GlobalReset <= '1';    
    wait for 2 * clock_period;    

    report "[T] fill FIFO";
    -- Fill the FIFO
    while (counter < 100) loop
      AdcDataIn <= AdcDataIn + '1';
      wait until rising_edge(Clock);
      counter <= counter + 1; 
    end loop;

    wait for 10 * clock_period;    

    for i in 0 to AdcDataOut'LENGTH loop
        report "AdcDataOut("&integer'image(i)&") value is" & std_logic'image(AdcDataOut(i));
    end loop;

    report "[S] shift register = " & integer'image(conv_integer(unsigned((AdcDataIn))));
    report "[T] end FIFO";
    
    wait for 5 * clock_period;
  assert (false) report "Simulation End!" severity failure;
--      finish;
  end process;

end architecture;

```

std_logic_vector を一気に report 文で１発で出力はできないようで，

``` VHDL:
    for i in 0 to AdcDataOut'LENGTH loop
        report "AdcDataOut("&integer'image(i)&") value is" & std_logic'image(AdcDataOut(i));
    end loop;
```
というように for loop を回して，一つずつ出力しています．


### コンパイル

UserModule_LibrarySy1.vhdl がヘッダーファイルのようにみんなが参照するので，これを真っ先にコンパイルする．他は通常通りでOKです．

``` bash:
#!/bin/sh 
ghdl -a --ieee=synopsys UserModule_LibrarySy1.vhdl
ghdl -a --ieee=synopsys UserModule_ChannelModule_DelaySy1.vhdl
ghdl -a --ieee=synopsys sr_tb.vhd
ghdl -e --ieee=synopsys sr_tb
ghdl -r --ieee=synopsys sr_tb --vcd=sr_tb.vcd 
```

### 動作の概念図

動作の概念図としては，

![sim_shiftregister1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/f52b28a0-c368-33fc-288b-5dd3eefcac2d.png)

のような感じです．　AdcDataArray がシフトしたデータを蓄積するレジスタになっていて，遅れた情報はここにたまる．例えば，う~んと長い情報を貯めためたければ，AdcDataArrayを長くすればよい． DepthOfDelayで指定した分だけ遅れた情報が取得できるので，これを調整することで，delayの長さを可変にできる．

### 実行結果

左のパネルから，トップモジュールと下位モジュールの全ての変数を選択して，出力させる．

![sim_shiftregister2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/996f4e79-4a5f-06d8-4435-71da60bf2c05.png)

adcdataout は， adcdatain から少し遅れてデータが流れていることを確認しよう． tmpadcdataout は， process 分の動作チェック用につけただけ．これは半クロック遅れて更新されている．


# トリガーをかける方法


## コード

### ヘッダーファイル

みやすさのために，Delayの深さを変えただけ．

``` VHDL:UserModule_LibrarySy1.vhdl
---------------------------------------------------
--Declarations of Libraries
---------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

---------------------------------------------------
--Package for UserModules
---------------------------------------------------
package UserModule_LibrarySy1 is

  ---------------------------------------------------
  --Global variables
  ---------------------------------------------------
  constant ADCResolution : integer := 14;      --10bit ADC
  constant WidthOfDepthOfDelay    : integer := 7;  --max depth=2^7=128  
  constant MaximumOfDelay         : integer := 1024;    --32clk delay

end UserModule_LibrarySy1;
```


### Delay信号の生成

上の例と違って，みやすさのために，結構大きなdelayをさせるようにパラメータを微調整しただけ．

``` VHDL:UserModule_ChannelModule_DelaySy1.vhdl
-- UserModule_ChModule_Delay.vhdl
-- Delay incoming ADC data

library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
-- use work.iBus_Library.all;
-- use work.iBus_AddressMap.all;
--library IEEE;
--use IEEE.numeric_std.all;
use work.UserModule_LibrarySy1.all;

entity UserModule_ChModule_DelaySy1 is
  port(
    DepthOfDelay : in  std_logic_vector(WidthOfDepthOfDelay-1 downto 0);
    --
    AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
    AdcDataOut              : out std_logic_vector(ADCResolution-1 downto 0);
    --clock and reset
    Clock                   : in  std_logic;
    GlobalReset             : in  std_logic
    );
end UserModule_ChModule_DelaySy1;

architecture Behavioral of UserModule_ChModule_DelaySy1 is

  --Signals
  signal Trigger        : std_logic                         := '0';
  signal a              : integer range 0 to MaximumOfDelay := 0;
  --Registers
  signal SampleRegister : std_logic_vector(15 downto 0)     := (others => '0');
  signal InputRegister  : std_logic_vector(15 downto 0)     := (others => '0');
  signal OutputRegister : std_logic_vector(15 downto 0)     := (others => '0');
  signal tmpAdcDataOut  : std_logic_vector(ADCResolution-1 downto 0); -- just for timing check

  type   AdcDataVector is array (integer range <>) of std_logic_vector(ADCResolution-1 downto 0);
  signal AdcDataArray : AdcDataVector(MaximumOfDelay downto 0) := (others=> (others=>'0'));

begin

  MainProcess : process (Clock, GlobalReset)
  begin

    tmpAdcDataOut <= AdcDataIn; -- just for timing check 

    if (GlobalReset = '0') then
    elsif (Clock'event and Clock = '1') then
      -- Shift register
      AdcDataArray <= AdcDataArray(MaximumOfDelay-1 downto 0) & AdcDataIn;
--      report "[S] AdcDataOut is updated";

      -- Select output
      if (a < MaximumOfDelay) then
        AdcDataOut <= AdcDataArray(a);
      end if;

      if (conv_integer(DepthOfDelay) < MaximumOfDelay) then
        a <= conv_integer(DepthOfDelay);
      else
        a <= 0;
      end if;

    end if;

  end process;  
end Behavioral;
```

### トリガーの生成

単純なレベルトリガーの例です． state machine としては， Idle と Triggered を行き来するだけです．
トリガーがかかったら 1024 sample を delay した信号から出力する．

``` VHDL:UserModule_ChannelModule_TrigSy1.vhdl
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use work.UserModule_LibrarySy1.all;

entity UserModule_ChModule_TrigSy1 is
  generic (
    recordlength: integer := 10
    );
  port(
    AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
    AdcDataDelayed          : in  std_logic_vector(ADCResolution-1 downto 0);    
    TrigDataOut             : out std_logic_vector(ADCResolution-1 downto 0);
    --clock and reset
    Clock                   : in  std_logic;
    GlobalReset             : in  std_logic
    );
end UserModule_ChModule_TrigSy1;

architecture Behavioral of UserModule_ChModule_TrigSy1 is

  --Signals
  signal Trigger        : std_logic                         := '0';  
  signal Threshold : std_logic_vector(15 downto 0)          := x"0010";  
  signal tcounter : integer range 9000 downto 0 := 0;

  type UserModule_StateMachine_State is
    (Initialize, Idle, Triggered);
  signal UserModule_state : UserModule_StateMachine_State := Initialize;

begin


  --UserModule main state machine
  MainProcess : process (Clock, GlobalReset)
    variable t, s: integer;
  begin
    --is this process invoked with GlobalReset?
    if (GlobalReset = '0') then
      UserModule_state <= Initialize;
      --is this process invoked with Clock Event?
    elsif (Clock'event and Clock = '1') then

    case UserModule_state is 
    when Initialize => if Trigger = '0' then
         UserModule_state <= Idle;
         else
         UserModule_state <= Triggered;
        end if;

    when Idle => if Trigger = '0' then
         t := conv_integer(Threshold);
         s := conv_integer(AdcDataIn);
        if (s > t) then
          Trigger <= '1'; 
          UserModule_state <= Triggered;
          tcounter <= 0;
        else
          Trigger <= '0'; 
        end if;
         else
         UserModule_state <= Triggered;
        end if;

    when Triggered => if Trigger = '0' then
         UserModule_state <= Idle;
         else
         TrigDataOut <= AdcDataDelayed;
         tcounter <= tcounter + 1;
         if tcounter > recordlength then
         Trigger <= '0'; 
         tcounter <= 0;
         UserModule_state <= Idle;
         end if;
        end if;
    end case;

    end if;
  end process;
end Behavioral;
```


### シミュレーション

ノコギリ波を適当に入力する．


``` VHDL:srtrig_tb.vhd
---------------------------------------------------
--Declarations of Libraries
---------------------------------------------------
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
use work.UserModule_LibrarySy1.all;

entity srtrig_tb is
end srtrig_tb; 

architecture sim of srtrig_tb is

  constant clock_period : time := 10 ns;

  -- Generics
  constant RAM_DEPTH : natural := 100000;

  -- from Library
  signal DepthOfDelay      : std_logic_vector(WidthOfDepthOfDelay-1 downto 0) := "1100000";

  -- DUT signals
  signal Clock : std_logic := '1';
  signal GlobalReset : std_logic := '1';
  signal AdcDataIn : std_logic_vector(ADCResolution - 1 downto 0) := (others => '0');
  signal AdcDataOut : std_logic_vector(ADCResolution - 1 downto 0):= (others => '0');
  signal TrigDataOut : std_logic_vector(ADCResolution - 1 downto 0):= (others => '0');

  signal counter : integer range RAM_DEPTH - 1 downto 0;

  component UserModule_ChModule_DelaySy1
    port(
      DepthOfDelay : in  std_logic_vector(WidthOfDepthOfDelay-1 downto 0);
      --
      AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
      AdcDataOut              : out std_logic_vector(ADCResolution-1 downto 0);
      --clock and reset
      Clock                   : in  std_logic;
      GlobalReset             : in  std_logic
      );
  end component;

  component UserModule_ChModule_TrigSy1
    generic(
      recordlength : integer
      );
    port(
      AdcDataIn               : in  std_logic_vector(ADCResolution-1 downto 0);
      AdcDataDelayed          : in  std_logic_vector(ADCResolution-1 downto 0);
      TrigDataOut              : out std_logic_vector(ADCResolution-1 downto 0);
      --clock and reset
      Clock                   : in  std_logic;
      GlobalReset             : in  std_logic
      );
  end component;


begin

  -- Process for generating the clock
  Clock <= not Clock after clock_period / 2;

  inst_DelayModule : UserModule_ChModule_DelaySy1
    port map(
      DepthOfDelay => DepthOfDelay,
      --
      AdcDataIn               => AdcDataIn,
      AdcDataOut              => AdcDataOut,
      --clock and reset
      Clock                   => Clock,
      GlobalReset             => GlobalReset
      );

  inst_TrigModule : UserModule_ChModule_TrigSy1
    generic map(
      recordlength => 256
      )
    port map(
      AdcDataIn               => AdcDataIn,
      AdcDataDelayed          => AdcDataOut,
      TrigDataOut              => TrigDataOut,
      --clock and reset
      Clock                   => Clock,
      GlobalReset             => GlobalReset
      );


  PROC_SEQUENCER : process is 
  begin
    report "[T] start";
    counter <= 0;    
    GlobalReset <= '0';    
    wait for 5 * clock_period;    
--    wait until rising_edge(Clock);
    GlobalReset <= '1';    
    wait for 2 * clock_period;    

    report "[1] fill FIFO";
    -- Fill the FIFO
    while (counter < 100) loop
      AdcDataIn <= AdcDataIn + '1';
      wait until rising_edge(Clock);
      counter <= counter + 1; 
    end loop;
    counter <= 0; 
    AdcDataIn <= conv_std_logic_vector(0,AdcDataIn'LENGTH);

    wait for 400 * clock_period;    

    report "[2] fill FIFO";

    while (counter < 150) loop
      AdcDataIn <= AdcDataIn + '1';
      wait until rising_edge(Clock);
      counter <= counter + 1; 
    end loop;
    counter <= 0; 

    AdcDataIn <= conv_std_logic_vector(0,AdcDataIn'LENGTH);

    wait for 300 * clock_period;    

    report "[3] fill FIFO";


    while (counter < 130) loop
      AdcDataIn <= AdcDataIn + '1';
      wait until rising_edge(Clock);
      counter <= counter + 1; 
    end loop;
    counter <= 0; 

    AdcDataIn <= conv_std_logic_vector(0,AdcDataIn'LENGTH);
    wait for 400 * clock_period;    

    report "[4] fill FIFO";

    while (counter < 120) loop
      AdcDataIn <= AdcDataIn + '1';
      wait until rising_edge(Clock);
      counter <= counter + 1; 
    end loop;
    counter <= 0; 

    report "[5] fill FIFO";

    wait for 10 * clock_period;    

    for i in 0 to AdcDataOut'LENGTH loop
        report "AdcDataOut("&integer'image(i)&") value is" & std_logic'image(AdcDataOut(i));
    end loop;

    report "[S] shift register = " & integer'image(conv_integer(unsigned((AdcDataIn))));
    report "[T] end FIFO";
    
    wait for 5 * clock_period;
  assert (false) report "Simulation End!" severity failure;
--      finish;
  end process;
end architecture;
```

## コンパイル方法

``` bash:compile_trig.sh
#!/bin/sh 
ghdl -a --ieee=synopsys UserModule_LibrarySy1.vhdl
ghdl -a --ieee=synopsys UserModule_ChannelModule_DelaySy1.vhdl
ghdl -a --ieee=synopsys UserModule_ChannelModule_TrigSy1.vhdl
ghdl -a --ieee=synopsys srtrig_tb.vhd
ghdl -e --ieee=synopsys srtrig_tb
ghdl -r --ieee=synopsys srtrig_tb --vcd=srtrig_tb.vcd 
```

## 実行結果

ここで、表示をシグナルを選んで右クリックして、 Data Format ==> Analog ==> Step にすると、波形が見えるので、波形をみたい場合はアナログ表示にして確認してみよう。

![スクリーンショット 2020-11-28 14.21.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/7cd09583-ad9f-157d-97de-812b8b7372c2.png)

調整すると、

![スクリーンショット 2020-09-24 20.09.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/b511600a-41a8-6762-5725-ecb4e84a7e42.png)

のように見えて、波形とそれが少しシフトしたものが見えているのが確認できる。