2020年9月21日月曜日

ESP32/arduino:WiFi接続時のスマホ画面とPC画面の切り替え

目的:

WiFi接続時、スマホからのアクセス画面をスマホ用にする。(スマホ対応)

背景:

WiFi接続時、スマホから接続した場合に ボタン間隔が狭い等、操作しにくい場合がある。
この為、PCからアクセスした場合とスマホからアクセスした場合で画面表示を変えて操作しやすくする。

方法:

HTML を レスポンシブデザインとして、ウィンドウ幅によりデザインを分ける。
  1. viewportを設定する。
    <head> ~ </head> 部分に、以下の様な metaタグ を記述する。
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
    
  2. ウィンドウ幅に応じて styleタグ を分ける。
    以下の様に、styleタグを 分ける。
    <style media="screen and (min-width: 769px)">
     ....  (PC用 設定)
    </style>
    <style media="screen and (max-width:768px)">
     ....  (スマホ用 設定)
     </style>
  3. 必要に応じて、HTML を修正する。 
* ウィンドウ幅で切り替えているため、PC でも ブラウザの幅を狭めれば スマホ用表示になる。

 例:

ESP32/arduino:LED調光_WEBからと外付けのボリュームからを切り替えて調光
をスマホ対応にした 時の画面 と HTML を示す。(スケッチ、レスポンス用HTML は変更なし)
PC用 画面
スマホ用画面
*スマホ時は、全体に間隔を広げ、ラジオボタンの形状を変更している。

<!DOCTYPE html><html lang='ja'>
<head> 
  <meta charset='UTF-8'>
  <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0">
  <style media="screen and (min-width: 769px)">
       #base          {font-size:16pt;text-align:center;width:300px;border:solid 4px #93ff93; }
       #radio_box     {font-size:12pt;float:left ;text-align:left; width:45%; }
       #disp_box      {font-size:12pt;float:right;text-align:left; width:50%; }
       #brightness    {font-size:12pt;text-align:right; }
       #val_box       {font-size:10pt;text-align:center; clear:both ;}
       #ctl_box       {text-align:center; }
       input.radio    {margin-left:8px; width:30px;}
       input.value    {margin-left:8px; width:30px;}
       input.setbutton{margin-left:8px; width:40px;}
       input.slider   {margin-left:8px; width:250px;}
       input.button   {margin:0px 15px; width:100px;}
  </style>
  <style media="screen and (max-width:768px)">
       #base             {font-size:20pt;text-align:center; width:97%; border:solid 4px #008000; }
       #radio_box        {font-size:18pt;float:left ;text-align:left; width:45%; }
       #disp_box         {font-size:18pt;float:right;text-align:left; width:50%; line-height:30pt; }
       #brightness       {font-size:18pt;text-align:right; }
       #val_box          {font-size:16pt;text-align:center; clear:both ; width:100% ; }
       #ctl_box          {text-align:center; height: 100px }
       input.radio       {margin-left:8px; width:30px; vertical-align:middle ; display:none; }
       input.radio:checked + label.lbl { background: #00FF00 ; color: #FF0080 ; }
       label.lbl         {display:block ; margin: 0px calc(90% - 120px) 10px 10% ; text-align:center ; height:50px; line-height:50px; border: 1px solid #006DD9; border-radius:8px;}
       output.brightness {margin-right:50px }
       input.value       {margin-left:8px; margin-top:30px; width:50px; height:30px ; font-size:16pt; }
       input.setbutton   {margin-left:8px; width:50px; height:30px ; font-size:16pt; }
       input.slider      {margin-top:20px; margin-bottom:20px; width:95%; height:80px }
       input.button      {margin:0px 5% ; width:30%; height:50px ; font-size:18pt; background: #008000 ; color:#ffffff ; }
  </style>
  <title>Color LED Controller</title>
</head>

<body>
<div id="base">
  <p>LED ON/OFF</p>
  <div id="radio_box">
    <form method="get"> 
      <input class='radio' type='radio' name='remote' id="rad_lo" value='local' $checked_lo onclick='disp_ctrl(this.value); submit(this.value)'>
      <label for="rad_lo" class="lbl">Local<br></label>
      <input class='radio' type='radio' name='remote' id="rad_rm" value='remote' $checked_rm onclick='disp_ctrl(this.value); submit(this.value)'>
      <label for="rad_rm" class="lbl">Remote<br></label>
    </form>
  </div>
  <div id="disp_box">
      <span> brightness value</span><br>
      <div id="brightness"><output class='brightness' id="o1">$led_brightness</output></div>
  </div>
  <div id="val_box">
    <form method="get">
      <span > LED brightness (0-255)</span> 
      <input class='value'  type='text' name='led_v' value=$led_brightness id='led_v' disabled>
      <input class='setbutton' type='submit' name='set' id='set' value='SET' disabled>
    </form> 
    <form method='get'> 
      <input class="slider" type="range" name='led_s' id='led_s' value=$led_brightness min="0" max="255" step="1" disabled
        onchange="setval(this.value);" oninput="setval(this.value)"; onmouseup="submit(this.form)": ontouchend="submit(this.form)">
    </form> 
  </div>
  <div id="ctl_box">
    <form method='get'>
      <input class='button' type='submit' name='on' id="on" value='ON' disabled >
      <input class='button' type='submit' name='off' id="off" value='OFF' disabled><br>
    </form>
  </div>
</div>


<script>
var polling = null ;

function setval(ledval){
 var xhr = new XMLHttpRequest();
        xhr.open("get", "?slid="+ledval );
        xhr.setRequestHeader('Cache-Control', 'no-cache');
        xhr.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');
        xhr.responseType = 'document' ;

        xhr.onreadystatechange = function() {
          if( (xhr.readyState == 4) && (xhr.status == 200) ) {
            document.getElementById('led_v').value = xhr.response.getElementById("output1").innerHTML;
            document.getElementById('o1').value = xhr.response.getElementById("output1").innerHTML;
          }
        }

        ntimeout = function(e) {
          xhr.abort() ;
        }
        xhr.send();
}


function getval(){
 var xhrget = new XMLHttpRequest();
        xhrget.open("get", "?pol" );
        xhrget.setRequestHeader('Cache-Control', 'no-cache');
        xhrget.setRequestHeader('If-Modified-Since', 'Thu, 01 Jun 1970 00:00:00 GMT');
        xhrget.responseType = 'document' ;

        xhrget.onreadystatechange = function() {
          if( (xhrget.readyState == 4) && (xhrget.status == 200) ) {
            document.getElementById('o1').value = xhrget.response.getElementById("output1").innerHTML;
          }
        }

        ntimeout = function(e) {
          xhrget.abort() ;
        }
        xhrget.send();
}

function disp_ctrl( radioid ) {
   if(radioid == 'remote') {
      document.getElementById('led_v').disabled = false;
      document.getElementById('set').disabled = false;
      document.getElementById('led_s').disabled = false;
      document.getElementById('on').disabled = false;
      document.getElementById('off').disabled = false;
      clearInterval(polling);
   } else {
      document.getElementById('led_v').disabled = true;
      document.getElementById('set').disabled = true;
      document.getElementById('led_s').disabled = true;
      document.getElementById('on').disabled = true;
      document.getElementById('off').disabled = true;
      polling = setInterval(getval,100) ;
   }

}

window.onload = function() {
    if(document.getElementById("rad_rm").checked) {
      document.getElementById('led_v').disabled = false;
      document.getElementById('set').disabled = false;
      document.getElementById('led_s').disabled = false;
      document.getElementById('on').disabled = false;
      document.getElementById('off').disabled = false;
      clearInterval(polling);
    } else if(document.getElementById("rad_lo").checked) {
      document.getElementById('led_v').disabled = true;
      document.getElementById('set').disabled = true;
      document.getElementById('led_s').disabled = true;
      document.getElementById('on').disabled = true;
      document.getElementById('off').disabled = true;
      polling = setInterval(getval,100) ;
    }
}

</script>

</body>
</html>

以下、覚書など。
  ( 行# 4 )
viewportの設定。

  ( 行# 5 ~ 17 )
styleタグに メディア属性を設定。769px 以上の幅の場合は PC画面とし、従来の設定のままとする。但し、行#10 は html 内で font-size を変更していた為、style の設定を変更。

  ( 行# 18 ~ 33 )
スマホ用の styleタグ。
styleタグに メディア属性を設定。768px 以下の幅の場合は スマホ画面とし、スマホ用の設定を記述。
全体に 間隔が広がるように margin、font-size 等の設定を変更。
ラジオボタンの形状変更の為、通常のラジオボタンの表示を消し ( display:none; )、
Label タグ を設定して、"input.radio:checked + label.lbl" (行#26) で、chkeck 時の動作 (Label の 色を変更)

  ( 行# 42 ~ 45 )
スマホ用に ラジオボタンの形状を変更する為、labelタグを追加し、表示文字列は Label で設定。

  ( 行# 50 )
表示位置をずらすため、class の設定を追加。(PC画面には影響なし)

  ( 行# 50 )
font-size を style で設定する為に削除。


2020年9月5日土曜日

verilog : Vivado で SVA(アサーション) を試す 2

目的:

Vivado で SVA を使用した時の 波形表示 や 発火回数 表示 ができるようにする。

概要:

一案として、アサーション記述に Pass/Fail 時の処理を記述し、波形表示を行った。
また、シミュレーション終了時に 発火回数 と FAIL 回数の表示を行った。
Vivado で SVA(アサーション) を試す」のサンプルを修正して確認を行った。

波形表示:

少し面倒ではあるが、アサーション記述に以下の様な処理を記述することで波形に表示できることを確認した。(「Vivado で SVA(アサーション) を試す」の sva_1.sv を修正)
// -----------------------------------------------------
//   Sample module
//
//   Module Name : sva_1
//   Version     : 0.00
// -----------------------------------------------------

module sva_1 ( clk, clr, start, en, outpls) ;
  input clk ;
  input clr ;
  input start ;
  input en ;
  input outpls ;

  // internal signal declaration ---------
  logic [15:0] a_test1_fire ;
  logic [15:0] a_test1_pass = 0 ;
  logic [15:0] a_test1_fail = 0 ;

  assign a_test1_fire = a_test1_pass + a_test1_fail ;

  // sequence description ----------------
  sequence S1 ;
    outpls[*4] ##1 !outpls[*5] ;
  endsequence

  // property description ---------------
  property p_test1 ;
    @(posedge clk ) disable iff(clr)
      $rose(start) |-> ##1 S1[*6] ;
  endproperty

  // assertion description
//  a_test1 : assert property ( p_test1 ) $display("a_test1 : OK") ;
  a_test1 : assert property ( p_test1 ) begin
              a_test1_pass = a_test1_pass + 1 ;
              $display("a_test1 : OK") ;
            end
            else begin
              a_test1_fail = a_test1_fail + 1 ;
              $display("a_test1 : FALE") ;
            end
  endmodule
発火回数、PASS回数、FAIL回数 の変数(信号)を定義し、アサーション記述で PASS回数、FAIL回数をカウントし、「OK」,「FAIL」の画面表示を行う。
発火回数は、PASS回数 + FAIL 回数 とする。
シミュレーション実行後、各回数の変数(信号) が波形で表示でき、値の変化点が PASS, FAIL の ポイント となる。
sva_2.sv も同様に修正する。

終了時の発火等回数の表示:

テストパターンの終了時、各アサーションの発火等の回数を $display で表示する。
// +-------------------------------------------------------------------------+
// | test pattern                                                            |
// |                                                                         |
// |   test name : test_1                                                    |
// +-------------------------------------------------------------------------+
//

initial begin
  #200
  // H:4clk,L:5clk  x 5cycle -----
  @(posedge clk) #1
      pls_start    = 1'b1  ;
      h_width      = 8'd4  ;
      l_width      = 8'd5  ;
      pls_num      = 8'h5  ;
  @(posedge clk) #1
      pls_start    = 1'b0  ;
  @(negedge i_plsgen.plsen)
  #100
  // H:4clk,L:5clk  x 6cycle -----
  @(posedge clk) #1
      pls_start    = 1'b1  ;
      h_width      = 8'd4  ;
      l_width      = 8'd5  ;
      pls_num      = 8'h6  ;
  @(posedge clk) #1
      pls_start    = 1'b0  ;
  @(negedge i_plsgen.plsen)
  #100
  // H:4clk,L:5clk  x 7cycle -----
  @(posedge clk) #1
      pls_start    = 1'b1  ;
      h_width      = 8'd4  ;
      l_width      = 8'd5  ;
      pls_num      = 8'h7  ;
  @(posedge clk) #1
      pls_start    = 1'b0  ;

  @(negedge i_plsgen.plsen)
  #100

  // test end -------------------
  $display ("") ;
  $display ("---- assertion result ------") ;
  $display ("[SVA] a_test1 : fire = %d , FAIL = %d",
                testbench.i_plsgen.i_sva_1.a_test1_fire,
                testbench.i_plsgen.i_sva_1.a_test1_fail ) ;
  $display ("[SVA] a_test2 : fire = %d , FAIL = %d",
                testbench.i_plsgen.i_sva_2.a_test2_fire,
                testbench.i_plsgen.i_sva_2.a_test2_fail ) ;

  $finish ;
  end
終了時に $display で表示する為、最後までシミュレーションが実行されないと回数表示はされない。

出力結果:

 実行結果の波形を以下に示す。
 発火 (PASS, FAIL) したタイミングが波形で判る。

実行結果の LOG を以下に示す。
****** xsim v2020.1 (64-bit)
  **** SW Build 2902540 on Wed May 27 19:54:35 MDT 2020
  **** IP Build 2902112 on Wed May 27 22:43:36 MDT 2020
    ** Copyright 1986-2020 Xilinx, Inc. All Rights Reserved.

source xsim.dir/test_1/xsim_script.tcl
# xsim {test_1} -autoloadwcfg -runall
Vivado Simulator 2020.1
Time resolution is 1 ps
run -all

Time: 1140 ns Started: 220 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:36
a_test1 : FALE

Time: 2340 ns Started: 1260 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:36
a_test1 : OK

Time: 2360 ns Started: 1260 ns Scope: /testbench/i_plsgen/i_sva_2 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_2.sv Line:36
a_test2 : OK

Time: 3560 ns Started: 2480 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:36
a_test1 : OK

Time: 3580 ns Started: 2480 ns Scope: /testbench/i_plsgen/i_sva_2 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_2.sv Line:36
a_test2 : FALE

---- assertion result ------
[SVA] a_test1 : fire =     3 , FAIL =     1
[SVA] a_test2 : fire =     2 , FAIL =     1
$finish called at time : 3841 ns : File "/mnt/d/UserData/hobby/logic/study/test3/testbench/tp.sv" Line 52
exit
INFO: [Common 17-206] Exiting xsim at Fri Sep  4 01:02:27 2020...
~
~

最後に発火回数 と FAIL 回数 が表示されている。

2020年8月28日金曜日

WSL : WSL2 を使用する

目的:

WSL2 を使用できるようにする

概要:

Windows10 バージョン 1909 でも WSL2 が使用できるようになった為、WSL2 に更新にする。
 更新は以下を実施し、WSL2 が使用できることを確認した。
  1. 累積的更新プログラム「KB4566116 」を適用
  2. "仮想マシン プラットフォーム" のオプション コンポーネントを有効にする
  3. WSL2の有効化
  4. ディストリビューションの更新 (Debian を更新)
  5. wsltty を最新版に更新 
  6. GUI 設定 (DISPLAY 変更, VcXsrv 設定変更)

更新手順:

  1. 累積的更新プログラム「KB4566116 」を適用
    「設定」-「更新とセキュリティ」から アップデートを行う。
     
  2. "仮想マシン プラットフォーム" のオプション コンポーネントを有効にする
    1. powershellを管理者権限で起動
      スタートメニューから 「Windows powershell」-「Windows powershell」を右クリックし、「管理者として実行する」を左クリック
    2. "仮想マシン プラットフォーム" のオプション コンポーネントを有効にする
      powershell で次のコマンドを実行
      dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    3. PS C:\WINDOWS\system32> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
      
      展開イメージのサービスと管理ツール
      バージョン: 10.0.18362.900
      
      イメージのバージョン: 10.0.18363.1049
      
      機能を有効にしています
      [==========================100.0%==========================]
      操作は正常に完了しました。
      
    4. PC を再起動
      再起動すると、Windows の機能の更新が行われる
       
  3. WSL2の有効化
    WSL2をデフォルトへ設定
    powershell で次のコマンドを実行
    wsl --set-default-version 2
    PS C:\WINDOWS\system32> wsl --set-default-version 2
    WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください
      
  4. ディストリビューションの更新
    1. 現在のディストリビューションのバージョンを確認
      powershell で次のコマンドを実行
      wsl --list --verbose
      (更新プログラム適用前は、 --verbose は 使用できない。)
      PS C:\WINDOWS\system32> wsl --list --verbose
        NAME      STATE           VERSION
      * Debian    Stopped         1
        Ubuntu    Stopped         1
      
      debian と ubuntu が あり、共に VERSION 1
    2. ディストリビューションの更新
      ここでは Debian を更新してみる。 powershell で次のコマンドを実行
      wsl --set-version Debian 2
      PS C:\WINDOWS\system32> wsl --set-version Debian 2
      変換中です。この処理には数分かかることがあります...
      WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください
      変換が完了しました。
      
    3. 再度バージョンを確認
      wsl --list --verbose
      PS C:\WINDOWS\system32> wsl --list --verbose
        NAME      STATE           VERSION
      * Debian    Stopped         2
        Ubuntu    Stopped         1
      
      debian の VERSION が 2 に変わる。
       
  5. wsltty を最新版に更新
    wsltty を使用している場合、最新版( 現時点で 3.3.0 ) にしないと動作しない。
     
  6. GUI 設定 (DISPLAY , VcXsrv 設定) の変更
    WSL2 では ネットワーク構成が変更されている為、DISPLAY 設定が
    DISPLAY=:0.0
    では エラーが発生する。
    以下の設定に変更する必要がある。
    export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0
    また、VcXsrv設定で,
    「Additional parameters for VcXsrv」(設定画面 3画面目) に -ac を入力する。
以上で、Debian が WSL2 が使用できるようになった。

尚、Ubuntu は 更新していないため、従来の WSL で起動した。

その他:

(2020/08/30 追記)

ネットワークドライブアクセス不可

現象:
 ネットワークドライブ 2つの内、1つへのアクセスができなくなった。
 アクセスできなくなったのは NAS( SMB1.0)。
 Raspbery pi のディスク ( SMB 2 ?) は アクセスできている。
対応:
  1.  WSL2 に cifs-utils をインストール
    sudo apt install cifs-utils
  2.  /etc/fstab を修正
    NAS の マウントの設定を以下の様にして  drvfs から cifs に 変更
    //サーバー名/共有名 /mnt/<マウント先ディレクトリ名> cifs vers=1.0,username=<ユーザ名>,password=<パスワード>,iocharset=utf8,rw,uid=1000,gid=1000,defaults 0 0
  3. ディストリビューションの再起動
    パワーシェル から WSL 終了コマンドを入力 後、起動し、起動時にマウントされることを確認する。
    wsl -t <ディストリビューション名>
これで、Debian は アクセス可能になった。
但し、WSL1 にすると、cifs での アクセスはできなくなる。
Ubuntu も同様にしたが、起動時に マウントができていない。
sudo mount -a
ではマウントできる。対応調査中

2020年8月16日日曜日

verilog : Vivado で SVA(アサーション) を試す

目的:

Vivado で SVA を使用してみる。

結果:

Vivado で SVA 並列アサーション を記述し、アサーションができることを確認した。
  • 2021/11/4 追記
    とくに問題なくアサーションできている。
  • 2020/10/16 追記
    前回から30日以上経過しているが、アサーション結果表示できている。
  • 2020/09/04 追記
    再実行で アサーションの表示が また 出力されるようになった。
    先日、Windows版の vivado を Update したが、この影響か ?
  • 2020/08/29 追記
    再実行したところ、アサーションの表示が 出力されない。
    WEB PACK ではサポートされず、30日間無償評価で使用できていたと思われる。
但し、
  1.  property に 引数 を付けるとエラー が発生する。
  2. アサーションの結果は 波形には表示されない。
  3. アサーションの結果は Fail 時 のみ ログ, ディスプレイ に表示される。
    ( 特に処理の記述を行わない場合 )
となる。
以下のパターンで試しただけなので、他にも注意点があるかもしれないし、上の点も 設定, 記述方法等があるかもしれないが、SVA は使用可能。
(尚、波形への表示については「Vivado で SVA(アサーション) を試す 2」を参照。)

SVA 適用手順:

Vivado の コマンドラインでのシミュレーション環境 をベースに SVA を適用した。
( SVA 適用後の サンプルは こちら を参照。)
  1. ディレクトリ構成
    アサーション格納用として assertion ディレクトリを作成
  2. アサーション用ファイルの作成
    アサーション用ファイルとして
    1. SVA ファイル ( アサーション記述のモジュール )
    2. bind ファイル ( SVA のモジュールを rtl に接続する記述 )
    3. bind リスト ( bind ファイルを インクルードする為の リスト )
    を作成する。
  3. bind ファイルのインクルード
    テストベンチに 上 の bind リスト を インクルードする。
  4. プロジェクトファイル (vlog.prj) 修正
    vlog.prj に SVA ファイル を追加する。
以上で、SVA が適用される。

SVA 適用例:

SVA 適用例を以下に示す。
  1. テストベンチ ( Vivado の コマンドラインでのシミュレーション環境からの差分 )
      end
    
    // include test patteern  ------------
    `include "tp.sv"
    
    // include assertion bind list -------
    `include "./assertion/bind.list"
    
    
    endmodule
    

  2. bind リスト
    `include "./assertion/bind_sva.sv"
    
    bind ファイルのインクルード文を記述する。
     
  3. bind ファイル
    bind testbench.i_plsgen sva_1 i_sva_1(
      .clk    (clk),
      .clr    (clr),
      .start  (pls_start),
      .en     (plsen),
      .outpls (output_pls)
    ) ;
    
    bind testbench.i_plsgen sva_2 i_sva_2(
      .clk    (clk),
      .clr    (clr),
      .start  (pls_start),
      .en     (plsen),
      .outpls (output_pls)
    ) ;
    
    ここでは、 2つのアサーションファイルをバインドしている。
    それぞれ RTL (i_plsgen) にバインド
    testbench には バインドできない ?
     
  4. SVA ファイル
    アサーションファイルを以下に示す。
    尚、アサーションの適不適は不問とする。
    ( RTL は パルス生成回路 )
    // -----------------------------------------------------
    //   Sample module
    //
    //   Module Name : sva_1
    //   Version     : 0.00
    // -----------------------------------------------------
    
    module sva_1 ( clk, clr, start, en, outpls) ;
      input clk ;
      input clr ;
      input start ;
      input en ;
      input outpls ;
    
      // sequence description ----------------
      sequence S1 ;
        outpls[*4] ##1 !outpls[*5] ;
      endsequence
    
      // property description ---------------
      property p_test1 ;
        @(posedge clk ) disable iff(clr)
          $rose(start) |-> ##1 S1[*6] ;
      endproperty
    
      // assertion description
      a_test1 : assert property ( p_test1 ) ;
    
      endmodule
    
    sva_1 は、 start 信号の立ち上がりの次クロックから、"H 4サイクル, L 5サイクル"のシーケンスを 6回繰り返すことをチェックする。
     
    // -----------------------------------------------------
    //   Sample module
    //
    //   Module Name : sva_2
    //   Version     : 0.00
    // -----------------------------------------------------
    
    module sva_2 ( clk, clr, start, en, outpls) ;
      input clk ;
      input clr ;
      input start ;
      input en ;
      input outpls ;
    
      // sequence description ----------------
      sequence S1 ;
        outpls[*4] ##1 !outpls[*5] ;
      endsequence
    
      // property description ---------------
      property p_test2 ;
        @(posedge clk ) disable iff(clr)
          $rose(start) |-> ##1 S1[*6] |-> ##1 $fell(en) ;
      endproperty
    
      // assertion description ---------------
      a_test2 : assert property ( p_test2 ) ;
    
      endmodule
    
    sva_2 は、 start 信号の立ち上がりの次クロックから、"H 4サイクル, L 5サイクル"のシーケンスを 6回繰り返した後、次サイクルで en が 立ち下がることをチェックする。
    尚、sva_1, sva_2 の 2ファイルに分ける必要は無いが (通常は 1つのファイル(モジュール) に a_test1 と a_test2 の 両方を記述する) 、サンプルの為 2つのファイルに分けている。
      
  5. プロジェクトファイル (vlog.prj)
    verilog xil_defaultlib  \
    "./rtl/plsgen.v" \
    "./rtl/D_CNT.v" \
    
    sv xil_defaultlib  \
    "./testbench/testbench.sv" \
    "./testbench/param.sv" \
    "./assertion/sva_1.sv" \
    "./assertion/sva_2.sv" \
    
    verilog xil_defaultlib "/tools/Xilinx/Vivado/2020.1/data/verilog/src/glbl.v"
    
    プロジェクトファイルに sva_1.sv, sva_2.sv を追加する。
     

SVA 適用結果:

SVA 適用後の シミュレーション実行結果を以下に示す。
****** xsim v2020.1 (64-bit)
  **** SW Build 2902540 on Wed May 27 19:54:35 MDT 2020
  **** IP Build 2902112 on Wed May 27 22:43:36 MDT 2020
    ** Copyright 1986-2020 Xilinx, Inc. All Rights Reserved.

source xsim.dir/test_1/xsim_script.tcl
# xsim {test_1} -autoloadwcfg -runall
Vivado Simulator 2020.1
Time resolution is 1 ps
run -all
ERROR: Assertion failed.
Time: 1140 ns Started: 220 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:28
ERROR: Assertion failed.
Time: 3580 ns Started: 2480 ns Scope: /testbench/i_plsgen/i_sva_2 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_2.sv Line:28
$finish called at time : 3841 ns : File "/mnt/d/UserData/hobby/logic/study/test3/testbench/tp.sv" Line 42
exit
INFO: [Common 17-206] Exiting xsim at Sun Aug 16 15:08:32 2020...
この時の波形

テストパターンは、3回 パルスを生成。
1回目は "H" 4 クロック, "L" 5クロック を 5回繰り返し
 6回目の"H" が無い為、a_test1 が Fail
 a_test2 は 6回目 の "H" が無く 前提部が不成立の為、発火しない。(Fail しない)
2回目は H4 クロック, L5クロック を 6回繰り返し
 a_test1, a_test2 共に 発火し、Pass。

3回目は H4 クロック, L5クロック を 7回繰り返し
 6回目の "H" がある為、a_test1 は発火し、Pass
 6回目の "H"→"L" の後 7回目が開始し、en (plsen) が 立ち下がらない為、Fail
期待通り、1回目, 3回目 のパルス生成で Fail し、エラー出力されている。

発火確認:

上の結果では、Fail した場合は ERROR 表示されるが、Fail しない場合は 何も表示されない。
この為、Pass したのか 発火しなかったのかの区別がつかない。
発火した場合に表示する様に SVA ファイルを修正してみる。
  // assertion description
  a_test1 : assert property ( p_test1 ) $display("a_test1 : OK") ;

  endmodule
  // assertion description ---------------
  a_test2 : assert property ( p_test2 ) $display("a_test2 : OK") ;

  endmodule
assert文 に 処理 ($display 等) を追加する。
処理追加後の結果は下の通り。

****** xsim v2020.1 (64-bit)
  **** SW Build 2902540 on Wed May 27 19:54:35 MDT 2020
  **** IP Build 2902112 on Wed May 27 22:43:36 MDT 2020
    ** Copyright 1986-2020 Xilinx, Inc. All Rights Reserved.

source xsim.dir/test_1/xsim_script.tcl
# xsim {test_1} -autoloadwcfg -runall
Vivado Simulator 2020.1
Time resolution is 1 ps
run -all
ERROR: Assertion failed.
Time: 1140 ns Started: 220 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:28

Time: 2340 ns Started: 1260 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:28
a_test1 : OK

Time: 2360 ns Started: 1260 ns Scope: /testbench/i_plsgen/i_sva_2 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_2.sv Line:28
a_test2 : OK

Time: 3560 ns Started: 2480 ns Scope: /testbench/i_plsgen/i_sva_1 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_1.sv Line:28
a_test1 : OK
ERROR: Assertion failed.
Time: 3580 ns Started: 2480 ns Scope: /testbench/i_plsgen/i_sva_2 File: /mnt/d/UserData/hobby/logic/study/test3/assertion/sva_2.sv Line:28
$finish called at time : 3841 ns : File "/mnt/d/UserData/hobby/logic/study/test3/testbench/tp.sv" Line 42
exit
INFO: [Common 17-206] Exiting xsim at Sun Aug 16 16:19:29 2020...

実行結果は、
1回目 (started: 220nS) は a_test1 Fail し、a_test2 は発火なし。
2回目 (started: 1260nS) は a_test1, a_test2 共に発火し、Fail なし。
3回目 (Started: 2480nS) は a_test1 は発火して Failなし, a_test2 は Fail。
となり、発火したことを確認できる。

できれば、波形に 表示されるといいのだが、、、
 → 表示案 : Vivado で SVA(アサーション) を試す 2 

2020年8月14日金曜日

verilog : Vivado の コマンドラインでのシミュレーション環境

目的:

Vivado のシミュレーション をコマンドラインで 実行する環境を作る 。
 ( Icarus Verilog と同様な環境でシミュレーションできるようにする )


概要:

Ivarus Verilog 用に作成した環境と同様の環境とする。

ディレクトリ構成

以下の様にディレクトリを分ける。
rtl         : 論理合成可能な RTL を格納する
testbench   : テストベンチ を格納する
testpattern : テストパターン を格納する。
log         : コンパイル,シミュレーション時の LOG を格納する。
xsim_dir    : コンパイル時に生成される作業ディレクトリ。
wave        : シミュレーション結果の波形ファイルを格納する。

コンパイル, シミュレーション実行

カレントディレクトリに Makefile、vlog.prj を作成し、
make TESTNAME=test_1
の様に テストパターンを指定してコンパイル、シミュレーションを実行できる様にする。
コンパイル、シミュレーション時に実行 LOG を log ディレクトリ下に生成/移動する。
シミュレーション結果の波形は、wave ディレクトリに 移動した <TESTNAME>.vcd を gtkwave 等で確認する。

vlog.prj は、verilog のファイル構成を記したファイル。
コンパイル、シミュレーションは vivado のプロジェクトは作成せず、コマンド実行のみで行う。

詳細:

ディレクトリ構成は、概要の通り。
サンプルの環境は こちら を参照。(rtl, testbench の内容についての説明は省略)
サンプルは パルス生成回路。
テストパターンは test_1. sv と test_2.sv
make TESTNAME=test_1
make TESTNAME=test_2
でそれぞれ異なるパルス出力 (output_pls) が発生。

尚、vivado のインストールは、
Windows : WSL に Vivado をインストールする
を参照。

テストベンチ構成

テストパターン(項目) に依存しない共通部分を testbench.sv に記述。
テストパターン(項目)毎の記述は、パターンごとにファイルを作成し、testpattern ディレクトリに格納する。
make 実行時に、実行するテストパターン(項目)のファイルを testbench ディレクトリにtp.sv のファイル名でコピーし、testbench.sv から include で 呼び出す。

vlog.prj 例

vlog.prj は、以下を記述する。
verilog <work_library> <file_names>...
sv <work_library> <file_names> ...
verilog xil_defaultlib  \
"./rtl/plsgen.v" \
"./rtl/D_CNT.v" \

sv xil_defaultlib  \
"./testbench/testbench.sv" \
"./testbench/param.sv" \

verilog xil_defaultlib "/tools/Xilinx/Vivado/2020.1/data/verilog/src/glbl.v"
* /tools/Xilinx/ は、Vivado をインストールしたディレクトリ
vlog.prj にvhdl のソースを記述する。
verilog のファイルは verilog に、SystemVerilog のファイルは sv に記述する。
また、glbl.v を記述する必要がある。


Makefile 例

# ==============================================

TESTNAME = default

# -- SHELL setting -------------------------
SHELL=/bin/bash -o pipefail

# -- dir setting ---------------------------
TBDIR  = ./testbench
TPDIR  = ./testpattern
WAVE   = ./wave
LOG    = ./log

# -- compile option ------------------------
VLOGOPT  = -log $(LOG)/xvlog.log
VLOGOPT += -prj vlog.prj

ELABOPT  = -timescale 1ns/100ps
ELABOPT += -log $(LOG)/xelab.log
ELABOPT += -debug typical
ELABOPT += -L xil_defaultlib

# -- simulation option ---------------------
SIMOPT   = -log $(LOG)/simulate.log
SIMOPT  += -R

# -- wave file name ------------------------
VCD_NAME = wave.vcd

# -- Description ---------------------------
all : clean comp  sim

comp :
 # --- testpattern copy to tp.sv ----------------------------
ifeq ($(TESTNAME),default)
 echo >| $(TBDIR)/tp.sv
else
 cp $(TPDIR)/$(TESTNAME).sv $(TBDIR)/tp.sv
endif
 # --- analysis ---------------------------------------------
 xvlog $(VLOGOPT) 2>&1 | tee $(LOG)/comp.log
 # --- elaborate --------------------------------------------
 xelab $(ELABOPT) xil_defaultlib xil_defaultlib.testbench  -s $(TESTNAME) 
 # --- move log file ----------------------------------------
 mv *.pb $(LOG)/. 2> /dev/null ; true
 mv webtalk.* $(LOG)/. 2> /dev/null ; true

sim :
 # --- simulation -------------------------------------------
 xsim $(SIMOPT) $(TESTNAME)
 # --- move log file ----------------------------------------
 mv *.log $(LOG)/. 2> /dev/null ; true
 mv *.jou $(LOG)/. 2> /dev/null ; true
 # --- move wave file ----------------------------------------
 mv *.wdb $(WAVE)/. 2> /dev/null ; true
 mv $(VCD_NAME ) $(WAVE)/$(TESTNAME).vcd

clean :
 rm $(LOG)/* 2> /dev/null ; true

Make file 内容
TESTNAME : テストパターンファイル名の拡張子を除いた部分。初期値として "default" を設定。
SHELL setting :Shell 設定。パイプ中でのエラーコードを返す様にする。
dir setting : 各ディレクトリ名を設定。
compile option : コンパイルオプション (xvlog, xelab のオプション )を指定する。simulation option : シミュレーションオプション (xsim のオプション)を指定する。
wave file name : 波形ファイル名。(テストベンチに記述しているファイル名)Description : make での 実行内容。

実行方法
make TESTNAME=<テストパターンファイル名(拡張子を除く)
で、テストパターンファイルに記述したテスト内容で コンパイル、シミュレーションし、gtkwave を起動する。
実行は、
  1. log ファイル を消去する。
  2. コンパイルを以下の順に行う。
    1. テストパターン ファイルを tp.sv のファイル名で testbench ディレクトリにコピーする。
      TESTNAME を指定しない場合( make のみで実行 ) は、空の tp.sv を作成する。
    2. コンパイル (xvlog) を実行する。
    3. エラボレーション (xelab) を実行する。
    4. ログファイルを LOGディレクトリに移動する。
  3. シミュレーション (xsim) を実行する。
    実行後、ログファイルを ログディレクディレクトリに移動する。
    また、結果 (波形ファイル wave.vcd) を wave ディレクトリ下に <テストパターン名>.vcd として移動する。
の順に行う。
波形の確認は gtkwave で行う。wave ディレクトリで、
gtkwave <テストパターン名>.vcd



2020年8月11日火曜日

WSL : WSL に Vivado をインストールする

目的:

WSL (Debian) に Vivado をインストールする。
 

結果:

CUI でインストールすることで、Vivado をインストールすることができた。
但し、
  1. コンパイル時など、スタックサイズを変更できない旨の Warning が発生。
    ( WSL では スタックサイズの変更はできないらしい )
    ( WSL2 では Warning は発生しない ) * 2020/08/28 追記
  2. Debian は 公式サポートされていない。 できれば、Ubuntu 等の方が良いかも。
  3. 作業ディレクトリを NAS上にすると、ファイルがコピーできない、ファイルが無い等のエラーでコンパイルできない。Dドライブ等の内臓ディスク上は OK。

    (マウントの仕方が悪い ?  vi 等では普通に編集かのうだが、、)

インストール手順:

  1. インストーラをダウンロードする。(Windows)
    ダウンロードする為には、アカウントを作成する必要が有る。

GUIインストールの場合

  1. Xサーバを起動する。(Windows)
  2. ダウンロードしたインストーラを起動する。(WSL)
    (Windows で d:\download\xilinx に ダウンロードした場合の例)
    cd /mnt/d/download/xilinx
    ./Xilinx_Unified_2020.1_0602_1208_Lin64.bin

    自環境(Debian) では インストーラのGUI は開かず、下の様なメッセージが発生。
    Exception in thread "SPLASH_LOAD_MESSAGE" java.lang.IllegalStateException: no splash screen available
            at java.desktop/java.awt.SplashScreen.checkVisible(Unknown Source)
            at java.desktop/java.awt.SplashScreen.getBounds(Unknown Source)
            at java.desktop/java.awt.SplashScreen.getSize(Unknown Source)
            at com.xilinx.installer.gui.H.run(Unknown Source)
    Exception in thread "main" java.lang.IllegalStateException: no splash screen available
            at java.desktop/java.awt.SplashScreen.checkVisible(Unknown Source)
            at java.desktop/java.awt.SplashScreen.close(Unknown Source)
            at com.xilinx.installer.gui.G.b(Unknown Source)
            at com.xilinx.installer.gui.InstallerGUI.G(Unknown Source)
            at com.xilinx.installer.gui.InstallerGUI.e(Unknown Source)
            at com.xilinx.installer.api.InstallerLauncher.main(Unknown Source)
    
    この為、CUI でインストールを行った。
    また、Debianでのインストールの為、以下のメッセージ(ダイアログボックス)が表示された。
    尚、Ubuntu の WSL で試したところ、GUI のインストーラが起動した。
    ( インストーラが起動するまで 数分 かかった。)
    Ubuntu でのインストールは行わなかったが、インストーラに従えばGUIで インストールできると思われる。

CUIインストールの場合


  1. インストーラの抽出
    --noexec  --target <抽出ディレクトリ> のオプションを付けてインストーラを起動する。
    [Windows で d:\download\xilinx に ダウンロードした場合の例:]
    cd /mnt/d/download/xilinx
    ./Xilinx_Unified_2020.1_0602_1208_Lin64.bin --noexec --target ~/xilinx-installer
    これで、~/xilinx-installer ディレクトにインストール用ファイルが抽出される。
  2. ユーザ名、パスワードの入力
    抽出されたディレクトリに移動し、 ./xsetup -b AuthTokenGen を実行する。
    cd ~/xilinx-installer
    ./xsetup -b AuthTokenGen
    で、User ID, Password ( Xilinx のアカウント登録した ID(メールアドレス) と パスワード) を入力する。
    ( ./xsetup コマンド 実行してから UserID, Password 入力まで 数分 かかった。
     以降、コマンド入力後、気長に待つ必要あり。(環境に依存するかも) )
  3. 設定ファイルの作成
    ./xsetup -b ConfigGen
    で、インストール用の設定ファイルを作成する。
    実行すると以下の様な選択項目が表示されるため、選択する。
    Select a Product from the list: に Vivado (2) を選択
    Select an Edition from the list: に WebPACK (1) を選択
    選択後、設定ファイルのパス,ファイル名 が表示される。
  4. 設定ファイルの編集
    3. で生成された設定ファイル (テキストファイル) を編集する。
    ( 今回は、インストール先を  /tools/Xilinx  に修正した。 )
  5. インストールの実行
    sudo ./xsetup --agree XilinxEULA,3rdPartyEULA,WebTalkTerms --batch Install --config <設定ファイルのパス/ファイル名>
    で、Vivado のダウンロートとインストールを開始する。
    2020.1 での ダウンロードサイズ は 16.12GB だった。

    インストール終了後、Xilinx Information Center が起動したため、
     [File] → [Exit]  で、これを閉じた。

Vivado 実行:

以下の様に、インストールディレクトリの settings64.sh を ソースし、vivado を実行する。( インストール先 : /tools/Xilinx, Ver 2020.1 の場合 )
source /tools/Xilinx/Vivado/2020.1/settings64.sh
vivado

vivado の実行で、GUI で Vivado が起動する。

2020年8月4日火曜日

raspberry pi : NFSサーバーのインストール

目的:

NFSサーバーをインストールし 接続した USB HDD を NFS で共有できるようにする。

手順:

以下の手順で nfs-kernel-server をインストールし、共有ディレクトリの設定を行う。
  1. パッケージリストの更新、パッケージのアップデートを行う。
    sudo apt update
    sudo apt upgrade
     
    この時、カーネルの更新が行われた場合は、再起動する。
    sudo reboot
     
  2. nsf-kernel-server のインストールを行う。
    sudo apt install nfs-kernel-server
     
  3. /etc/export を編集し、共有ディレクトリを設定する。
    sudo vi /etc/exports

    以下の書式でディレクトリを追記する。
    <共有ディレクトリ> <ホスト1>(オプション) <ホスト2>(オプション) …

    例:
    /mnt/hdd1 192.168.1.0/255.255.255.0(rw,sync)

  4. 設定を反映する
    exportfs -ra

以上で、NFSサーバーのインストール、設定は終了。

クライアント側の設定については、
  Windows10の場合 はこちら
  WSL では NFS クライアント は使用できなさそう。


/etc/export 書式 :

<共有ディレクトリ> <ホスト1>(オプション) <ホスト2>(オプション) …

<共有ディレクトリ>
共有させたいディレクトリを記載
<ホスト>
共有ディレクトリにアクセスするクライアントを記載
ホスト名, IPアドレス, IPネットワーク  等を記載
IPネットワークは、
192.168.1.0/24
192.168.1.0/255.255.255.0
の様な形式。
(オプション)
主なオプションを以下に示す。
rw             : 読み出し、書き込み 可能
ro             : 読み出し専用 (default)
sync           : 同期書き込みを有効にする (default)
async          : 非同期書き込み(遅延書き込み)を有効にする
root_squash    : root としての書き込みを行わない様にする。(default)
                 (rootのuidとgidをanonymousのuidとgidに割り当てる。)
no_root_squash : root_squashを無効にする。
all_squash     : すべてのuidとgidをanonymousのuidとgidに割り当てる。
anonuid=uid    : anonymousに割り当てるuidを指定する。
anongid=gid    : anonymousに割り当てるgidを指定する。
secure         : 1023番以前のポートの接続のみ許可する。 (default)
insecure       : 1024番以降のポートの接続も許可する。


Windows10 : NFSクライアントのインストール

目的:

Windows10 に NFSクライアント をインストールし、Linux に接続した USB HDD を NFS で共有できるようにする。

手順:

以下の手順で NFSクライアント を有効化し、NFS アクセスの設定を行う。
  1. NFS クライアントの有効化
    1. スタート(画面左下) を右クリックし、「アプリと機能」を押下
    2. 右上 関連設定 の 「プログラムと機能」を押下
    3. 左側にある 「Windowsの機能の有効化または無効化」を押下
    4. 「NFS用サービス」の 「NFSクライアント」にチェックをして、「OK」を押下
       
  2. マウント
    1. スタート(画面左下) を右クリックし、「コマンドプロンプト」を押下
      (管理者権限ではない方)
    2. mount <NFSサーバ>:<exportディレクトリ> <マウントドライブ>
      で、NFSサーバーのディレクトリをマウントする。
      例:
      mount 192.168.1.91:/mnt/hdd1 w:¥

  3. アンマウント
    1. umount <マウントドライブ>
      例:
      umount w:¥

アクセス時の uid, gid:

マウント時の uid, gid を指定するには、¥Windows¥System32¥drivers¥etc ディレクトリに passwd ファイル, group ファイルを 作成する必要がある。
作成しない場合の uid, gid は 適当な値(?) になっている。
uid, gid の指定は、以下の様な ファイルを作成して行う。

uid :

¥Windows¥System32¥drivers¥etc¥passwd
に、
<Windows ユーザ名>:x:<uid>:<gid>:<コメント>::
の形式で記入する。
例: (Windows ユーザ名=hoge, uid=1001, gid=1001 の場合)
hoge:x:1001:100:hoge::

gid :

¥Windows¥System32¥drivers¥etc¥group
に、
<Windows 所属グループ名>:x:<gid>:
の形式で記入する。
例: (Windows ユーザ名=Administrators, gid=1001 の場合)
BUILTIN¥Administrators:x:1001:
( Administrators の場合は、前に BUILTIN¥ を付ける )

passwd, group ファイルを作成/保存したら、内容を反映させる為に 一度 アンマウント して、再度 マウント を行う。

以上

2020年7月28日火曜日

WSL : WSL の停止、起動 の 方法

目的:
WSL の停止, 起動 の方法を記す。

手順:
管理者権限で PowerShell  を 起動し、以下のコマンドを実行する。
WSL 停止 : net stop LxssManager
WSL 起動 : net start LxssManager

管理者権限の PowerShell は、「Start」メニュー の
「Windows PowerShell」- 「Windows PowerShell」
を右クリックし、「管理者として実行する」を選択して 起動する。

2020年7月26日日曜日

verilog : Icarus Verilog でのシミュレーション環境

目的:

Icarus Verilog でのシミュレーション環境を構築する。


背景:

  1. rtl, テストベンチ,コンパイル結果,波形ファイル等を同じディレクトリに混在させたくない。
  2. 異なる テストパターン でのシミュレーションを手軽に実行したい。
  3. シミュレーション時のコマンド入力を簡単にしたい。
これらの為、シミュレーション環境の整備を行う。尚、以下は、現時点での自環境の例を示す。

概要:

ディレクトリ構成


以下の様にディレクトリを分ける。
rtl         : 論理合成可能な RTL を格納する
testbench   : テストベンチ を格納する
testpattern : テストパターン を格納する。
log         : コンパイル,シミュレーション時の LOG を格納する。
sim_work    : コンパイル結果等の 中間ファイルを格納する。
wave        : シミュレーション結果の波形ファイルを格納する。

コンパイル, シミュレーション実行

Makefile を作成し、
make TESTNAME=test_1
の様に テストパターンを指定してコンパイル、シミュレーションを実行できる様にする。
コンパイル、シミュレーション時に実行 LOG を log ディレクトリ下に生成/移動する。

詳細:

ディレクトリ構成は、概要の通り。
サンプルの環境は こちら を参照。(rtl, testbench の内容についての説明は省略)
サンプルは パルス生成回路。
テストパターンは test_1. sv と test_2.sv
make TESTNAME=test_1
make TESTNAME=test_2
でそれぞれ異なるパルス出力 (output_pls) が発生。

尚、icarus verilog,  gtkwave のインストールは、
コンパイル/シミュレーション環境のインストール (WSL)
を参照。

テストベンチ構成

テストパターン(項目) に依存しない共通部分を testbench.sv に記述。
テストパターン(項目)毎の記述は、パターンごとにファイルを作成し、testpattern ディレクトリに格納する。
make 実行時に、実行するテストパターン(項目)のファイルを testbench ディレクトリにtp.sv のファイル名でコピーし、testbench.sv から include で 呼び出す。

Makefile 例

# ==============================================

TESTNAME = default

# -- SHELL setting -------------------------
SHELL=/bin/bash -o pipefail

# -- dir -----------------------------------
RTLDIR = ./rtl
TBDIR  = ./testbench
TPDIR  = ./testpattern
OUTDIR = ./sim_work
WAVE   = ./wave
LOG    = ./log

# -- rtl source ----------------------------
RTL  = $(RTLDIR)/plsgen.v
RTL += $(RTLDIR)/D_CNT.v

# -- testbench source ----------------------
TESTBENCH  = $(TBDIR)/testbench.sv

# -- compile option ------------------------
CMPOPT  = -g 2012
# CMPOPT += -v

# -- simulation option ---------------------
SIMOPT   = -l $(LOG)/simlog.log

# -- compile file name ---------------------
CMP_NAME = work

# -- wave file name ------------------------
VCD_NAME = wave.vcd

# -- Description ---------------------------
all : pre comp sim wav

pre :
ifeq ($(TESTNAME),default)
 echo >| $(TBDIR)/tp.sv
else
 cp $(TPDIR)/$(TESTNAME).sv $(TBDIR)/tp.sv
endif

comp :
 iverilog $(CMPOPT) -o $(OUTDIR)/$(CMP_NAME) $(TESTBENCH) $(RTL) 2>&1 | tee $(LOG)/comp.log

sim :
 vvp $(SIMOPT) $(OUTDIR)/$(CMP_NAME)
 mv $(VCD_NAME)  $(WAVE)/$(TESTNAME).vcd

wav :
 gtkwave $(WAVE)/$(TESTNAME).vcd

clean :
 rm -f $(LOG)/*
 rm -f $(OUTDIR)/*

Make file 内容
TESTNAME : テストパターンファイル名の拡張子と除いた部分。初期値として "defaule"を設定。
SHELL setting :Shell 設定。パイプ中でのエラーコードを返す様にする。
dir : 各ディレクトリ名を設定。
rtl source : ここに コンパイルする RTL を列挙する。
testbench source : ここに テストベンチ の RTL を列挙する。
compile option : コンパイルオプション (iverilog のオプション )を指定する。
simulation option : シミュレーションオプション (vvp のオプション)を指定する。
compile file name : コンパイル結果のファイル名。
wave file name : 波形ファイル名。(テストベンチに記述しているファイル名)
Description : make での 実行内容。

実行方法
make TESTNAME=<テストパターンファイル名(拡張子を除く)
で、テストパターンファイルに記述したテスト内容で コンパイル、シミュレーションし、gtkwave を起動する。
実行は、
  1. テストパターン ファイルを tp.sv のファイル名で testbench ディレクトリにコピーする。
    TESTNAME を指定しない場合( make のみで実行 ) は、空の tp.sv を作成する。
    (pre)
  2.  コンパイル (iverilog) を実行する。
    この時、出力を log 下にも ファイル(comp.log) として出力する。
  3. シミュレーション (vvp) を実行する。
    実行時の log を log 下に sim.log として出力する。
    結果 (波形ファイル wave.vcd) を wave ディレクトリ下に <テストパターン名>.vcd として移動する。
  4. gtkwave で <テストパターン名>.vcd を開く。
の順に行う。

2020年7月24日金曜日

Windows : キーボード入力が切り替わる

目的:

キーボード入力が英語キーボードに切り替わる原因と対策を記す

背景:

キーボードがいつの間にか英語キーボード 変わっていて、記号等が期待通りに入力できなくなった。

原因:

EXCEL で図形の編集を行った際に SHIFT, ALT の同時押しを行った事で キーボード入力が切り替わったと思われる。

Windows10 では Default で "左Alt + shift" が キーボード入力切り替えのホットキー に割り当てられている。

Excel では、 図形の移動時に
 shift キー : 水平/垂直 移動時に使用
 alt キー   : 図形を セルに合わせる時に使用
し、shift と alt を 同時に使用することも可能。

この為、"左Alt + shift" を押下した時にキーボード入力が切り替わった。
(図形を選択してから "左Alt + shift" 押下なら切り替わらない ? )


対策:

キーボード入力切り替えのホットキー割り当てを解除する。

手順

  1. タスクバー左下の 「スタート」を右クリックし、「設定」押下
  2. 「デバイス」-「入力」-「キーボードの詳細設定」-「入力言語のホットキー」で「テキストサービスと入力言語」のウィンドウを表示
  3. 「入力言語を切り替える」を選択し、「キーシーケンスの変更(C)...」を押下
  4. 「入力言語の切り替え」を「割り当てなし」にして 「OK」を押下


2020年7月22日水曜日

WSL : WSL で Network ドライブをマウントする

目的:
WSL で ネットワークドライブをマウントする

手順:
  1. マウントポイントを作成する
    cd /mnt
    sudo mkdir <マウントディレクトリ>

    例 : nwdrv1 という名前のマウントポイント(ディレクトリ) を作成。
    sudo mkdir nwdrv1
     
  2. ネットワークドライブをマウントする。
    sudo mount -t drvfs <デバイス名> <マウントポイント>

    例 : \\hogehoge\hoge を /mnt/nwdrv1 にマウントする
    sudo mount -t drvfs '\\hogehoge\hoge' /mnt/nwdrv1
     ( \ が含まれる場合、 ’ で括る )

    -t drvfs は マウントするデバイスのタイプで、WSL での Windows のドライブタイプ
     
  3. マウントポイントのシンボリックリンクを 適当な場所に作成する。
    ln -s <マウントポイント下 の任意のディレクトリ>

    例 : ホームディレクトリ (~/.) で
    ln -s /mnt/nwdrv1/abc
    で、ホームディレクトリに abc ディレクトリ(シンボリックリンク) ができる。
     (abc は ネットワークドライブに存在するディレクトリ)

    シンボリックリンクを作成しなくても良いが、ホームディレクトリの下 などに作成しておくと、アクセスが楽。
     
  4. /etc/fstab に設定して自動マウントする様にする。
    sudo /etc/fstab
    で、
    <デバイス名> <マウントポイント> drvfs defaults 0 0
    を記述する。
    例:
    \\hogehoge\hoge /mnt/nwdrv1 drvfs defaults 0 0

    debian の場合、/etc/fstab が無い為、新規作成する。

    上記記述の場合、所有権は root になる。
    所有権を設定する場合は、
    <デバイス名> <マウントポイント> drvfs defaults,metadata,uid=<ユーザID>,gid=<グループID>  0 0
    の様に記述する。
    例:
    \\hogehoge\hoge /mnt/nwdrv1 drvfs defaults,metadata,uid=1000,gid=1000   0 0

2020年7月18日土曜日

verilog : SVA の演算子

目的:

SVAの演算子等について記す。

各演算子:

(SVA の記述については、こちらを参照。
 下記 動作は、全てを確認した訳では無い為、使用時は注意して下さい。 )

主なシーケンス演算子:

  ( s, s1, s2 : シーケンス 、b : ブーリアン 、 x, y 等 : 信号名 )
演算子 説明
s1 ##n s2 s1 が成立してから nサイクル後に s2 が成立する時
 
s1 ##[n:m] s2 s1 が成立してから n~mサイクル後に s2 が成立する時
 
s [*n] s がnサイクル連続して繰り返された時
 
   
s [*n:m] s がn~mサイクル連続して繰り返された時
 
 
b[->n] b がnサイクル連続 又は 非連続 で繰り返された時
 
* ブーリアンのみ 
s1 intersect s2 s1, s2 の開始と終了が同じである時
 
b throughout s  s の継続中、 b が 真の時
 
s1 within s2  s2 の継続中 に s1 が 真になった場合の s2 終了時
 

主なプロパティ演算子:

演算子 説明
p1 and p2 p1, 2が共に成立するとき評価
   
p1 or p2 p1, 2どちらかが成立するとき評価
   
not p pが成立しないときに評価
   
if(b) p1 else p2 bが真ならp1を評価、偽ならp2を評価
   
disable iff(b) bが真の時は評価しない(Fail しない)
   
s |-> p s が成立した サイクルで p を評価
   
s |=> p s が成立した 次のサイクルで p を評価 ( s |-> ##1 p と同じ )
   


主な関数:

$rose( 信号 )  信号の最下位ビットが '1' に変化した時に 真
   
$fell( 信号 )  信号の最下位ビットが '0' に変化した時に 真
   
$stable( 信号 )  信号が 1サイクル前から変化しなければ 真
   
$past( 信号 , n )  信号のnサイクル前の値,  nが省略された場合は n=1
   
$onehot( 信号 )  信号の'H' のビット数が 1 の時に 真
   
$onehot0( 信号 )  信号の'H' のビット数が 1 又は 0 の時に 真
   
$isunknown(信号)  信号に 'x' や 'z' が含まれる場合に 真
   
$countones(信号)  信号の'H' のビット数
   



2020年6月22日月曜日

blogger : 画像の枠を消す/変更する

目的:

画像を挿入した時の画像についた枠を消す。

概要:

画像を挿入した時、画像の周りに枠が表示される。
テーマの編集でカステムCSSを追加するか、テーマのHTMLを編集する事で枠を表示しない様になるが、全ての画像で枠が消える。
個々の画像で枠を消したり、枠を変更したりできるようにする。

方法: 

画像を挿入した後、HTML表示に切り替え、挿入した画像のボーダー指定部分をスタイルの指定に変更し、枠とパディングのサイズを"0" に する。
変更前 :  <img border="0" data-original-height=...
変更後: <img style="border: 0px ;  padding: 0px ;" data-original-height=...
 ( 変更後、"作成"に戻り、再度 HTML表示すると style の記述位置は自動で変更されている... )

例:

変更前 (画像を挿入)
変更後 (style="border: 0px ;  padding: 0px ;")
枠の色を変更 (style="border: solid 2px #00FF00 ;  padding: 0px ;")

2020年6月21日日曜日

verilog : SVA の property 記述概要

目的:

SVA の property 記述について 理解した内容を 記す

property 記述 :

検証の動作を定義する。
property 記述のみでは アサーションを生成できず、 assertion 記述が必用。

記述方法:




プロパティ表現:

プロパティ表現で 検証の動作内容を定義する。

インプリケーション演算子を使用する場合。


前提条件が 成立した後、 1 クロック後に 帰結部 を 評価する。
上の例では、 X が 立ち上がった後、1クロック後に Y が 立ち下がる事 を確認する。
シーケンスについては、下記参照。
インプリケーション演算子 には次の 2種類がある。
  • オーバーラップ・インプリケーション演算子  ( |-> )
      前提部終了と帰結部開始が同じクロック
  • ノン・オーバーラップ・インプリケーション演算子 ( |=> )
      前提部終了の 1クロック後に帰結部開始。 |-> ##1  と同じになる。

プロパティ否定演算子を使用する場合。






クロック毎に x と y が 等しくない事を確認する。

演算子を使用しない場合。





クロック毎に x と y が 同じであることを確認する。

その他、プロパティ演算子 として and, or, if else を使用して複数のプロパティ表現を組み合わせる事が可能。

シーケンス:

シーケンスは、信号の一連の動作を表す。
例えは、 信号 A, B, C が 1クロック毎に 立ち上がる場合は、
  a ##1 b ##1 c
の様に記述する。
1つの信号が立ち上がるだけでも シーケンス である。
  a
シーケンスは、シーケンス記述で 名前を付けて プロパティ記述の中で使用することができる。

シーケンス演算子 の 詳細は こちら を参照。

システム関数:

システム関数として、以下の関数があり、シーケンス記述等で使用できる。
$rose( 信号 )
 信号の最下位ビットが '1' に変化した時に 真
$fell( 信号 )
 信号の最下位ビットが '0' に変化した時に 真
$stable( 信号 )
 信号が 1サイクル前から変化しなければ 真
$past( 信号 , n )
 信号のnサイクル前の値,  nが省略された場合は n=1
$onehot( 信号 )
 信号の'H' のビット数が 1 の時に 真
$onehot0( 信号 )
 信号の'H' のビット数が 1 又は 0 の時に 真
$isunknown(信号)
 信号に 'x' や 'z' が含まれる場合に 真
$countones(信号)
 信号の'H' のビット数

2020年6月20日土曜日

verilog : SVA による検証概要

目的:

SVA の概要について 理解した内容を 記す

SVA とは:

System Verilog Assertion の略。
System Verilog の アサーション記述言語。
内部論理の期待動作の検知 や 禁止動作を監視する。
 ( 期待通りに動作しない場合等を検出する )

SVA 適用手順:

SVA を適用するには、以下の記述を行う。
  1. SVA ファイル記述
    SVA 用のモジュールを作成する。
    SVAは、通常のRTL内にも記述できるが、
    1. ifdef などで合成時に無視できるようにする必要がある。
    2. module が system veerilog として扱われるため、RTL に system verilog の記述が混ざっても検証時には気づけない。 
      ( RTL を verilog で記述する場合 )
    等の注意が必要な為、SVA用のモジュールにした方が良いと感じる。
  2. bind ファイル記述
    SVA ファイル を RTL の信号 に紐づける。
    SVA ファイルで 直接 RTLの信号を記述することもできるが、bind ファイルを 使用することで複数の信号に同じ SVA を適用できる。

SVA ファイルの記述:

SVA のファイルは、通常の module と同様に作成する。
但し、output ポートは無い。(必要無い)
SVA の module には、
 通常の rtl 記述
 property 記述
 assertion 記述
 を行う。
例 : sva_test.sv
module sva_test ( clk, rst_n, sig1, sig2) ;
  input clk ;
  input rst_n ;
  input sig1 ;
  input sig2 ;

  // property 記述
  property p_test1(x, y) ;
    @(posedge clk ) disable iff(~rst_n)
        $rose(x) |-> ##1 fell(y) ;
  endproperty

  // assertion 記述
  a_test1 : assert property ( p_test1 ( sig1, sig2) ) ;

endmodule

property 記述 と assertion 記述 を 1つ にして
a_test1 : assert property (
    @(posedge clk ) disable iff(rst)
        $rose(sig1) |-> ##1 fell(sig2)
) ;


の様にも記述できるが、property 記述 と assertion 記述 を 分けると、1つのproperty 記述を 複数の asertion 記述で使用できる。

property 記述 については、SVA の property 記述概要 を参照

bind ファイルの記述:

アサーションの バインディングは 以下の様に行う。
 bind <bind先モジュール名> <アサーションモジュール名>  <インスタンス名> (
   ポート 接続 記述
) ;

bind DUT sva_test  sva_test1 (
  .clk    (clock),
  .rst_n (reset_n) ,
  .sig1   (signal_1) ,
  .sig2   (signal_2) ,
) ;




2020年5月10日日曜日

raspberry pi : 無線LAN設定

目的:
無線LAN(WiFi) を使用できるようにする。

概要:

手順:
  1. 無線LAN I/F の確認
    $ ifconfig
    で、wlan0 等 の 無線LAN I/F が存在することを確認。
     
  2.  SSID, パスフレーズ(PSK:事前共有キー) を設定する。
    $ sudo vi /etc/wpa_supplicant/wpa_supplicant.conf
    で、wpa_supplicant.conf に 以下の様に SSID, パスフレーズを設定する。

    ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
    update_config=1
    country=JP
    
    network={
      ssid="[SSID]"
      psk="[パスフレーズ]"
      key_mgmt=WPA-PSK
      scan_ssid=1
    }
    

    [SSID], [パスフレーズ] は、各自の環境に合わせた値を設定。
    "scan_ssid=1" は、SSID を非公開にしている場合に必要。
     
    psk を 暗号化 (256bit ハッシュ化) したい場合は、
    $ wpa_passphrase [SSID] [パスフレーズ]
    で 暗号化されたpsk値をを取得する。
    $ wpa_passphrase SSID PASSPHRASE
    network={
            ssid="SSID"
            #psk="PASSPHRASE"
            psk=38497220976092fc2707a838e4d4385019256149f99f935be22c90159d3b8373
    }
    

    取得できた psk値 を "[パスフレーズ]" の代わりに設定する。

    $ sudo reboot
    でリブートすると、DHCP により IPアドレスが設定される。
    ( ifconfig で確認する )
       
  3.  固定IP 設定
    $ sudo vi /etc/dhcpcd.conf
    で、固定IP 等を設定する。
    設定は 有線LAN と同様に設定する。
    # 有線LAN  IPアドレス設定
    interface eth0
    static ip_address=192.168.1.90/24
    #static ip6_address=fd51:42f8:caae:d92e::ff/64
    static routers=192.168.1.1
    static domain_name_servers=192.168.1.91 8.8.8.8 fd51:42f8:caae:d92e::1
    
    # 無線LAN  IPアドレス設定
    interface wlan0
    static ip_address=192.168.1.120/24
    static routers=192.168.1.1
    static domain_name_servers=192.168.1.91 8.8.8.8 fd51:42f8:caae:d92e::1
    

2020年5月6日水曜日

ESP32/arduino:NTPサーバーとの時刻同期と時刻表示

目的:

NTPサーバーと時刻同期を行い、現在時刻を取得する。

手順:

  1. 時刻同期
    wifi 設定後、configTime() により、NTPサーバーを設定する。
    書式:
      void configTime(long gmtOffset_sec, int daylightOffset_sec,
             const char* server1, const char* server2, const char* server3);

    パラメータ :
      gmtOffset_sec      GMTとローカル時刻との差(秒)。日本では 3600*9 (9時間)
     daylightOffset_sec 夏時間設定(秒)。日本では 0
     server1 ~ server3 NTPサーバ。最低一つ設定する。

    記述例 :
       configTime(3600 * 9, 0, "ntp.nict.jp", "time.google.com",
                 "ntp.jst.mfeed.ad.jp" );
     
    1. 時刻取得
      時刻の取得は、getLocalTime() で取得する。
      getLocalTime()
      書式 :
         bool getLocalTime(struct tm *info, uint32_t ms);
      パラメータ:
        info 
      取得する時刻情報を格納する領域。
        ms    タイムアウト時間。省略した場合は5000。

      記述例:
        struct tm timeInfo;
        getLocalTime(&timeInfo, 1);
        Serial.printf(" %04d/%02d/%02d %02d:%02d:%02d\n",
                       timeInfo.tm_year + 1900,
                       timeInfo.tm_mon + 1,
                       timeInfo.tm_mday,
                       timeInfo.tm_hour,
                       timeInfo.tm_min,
                       timeInfo.tm_sec) ;

      注:

       getLocalTime は、取得した年が 116(2016年) 以下の場合、タイムアウトになるまで取得し直す。NTPサーバーと同期がとれない場合、タイムアウト値を指定しないと、5秒間、リトライする(処理が待たされる) こととなる。

       NTPサーバーと同期がとれない要因の 1つとして、WIFI 固定IP設定時の DNSアドレス が正しく設定されていないことが考えられる。( やらかしてました。 )
       

例:

 「SPIFFSのテキストファイルをダウンロードする 」の シリアル出力にタイムスタンプを追加した例を以下に示す。
(メインスケッチ以外のファイルは 「SPIFFSのテキストファイルをダウンロードする 」を参照 )
<表示例>
     |
 2020/05/06 17:08:03
 +++++++++++++++++ new client! +++++++++++++++++
GET / HTTP/1.1
     |

#include <WiFi.h>                         // WiFi 使用の為
#include "FS.h"                           // SPIFFS 使用の為
#include "SPIFFS.h"                       // SPIFFS 使用の為
#include "time.h"                         // getLocalTime 使用の為
 
#define DEBUG_HTML                        // Debug用 Serial 表示制御用
#define DEBUG                             // Debug用 Serial 表示制御用
 
// AP モード用 WIFI 設定 ---------------------------------------------
const char ssid[] = "ESP32_AP";           // SSID for softAP
const char pass[] = "password";           // password for softAP
const IPAddress ip(192, 168, 4, 1);       // IPアドレス for softAP
const IPAddress subnet(255, 255, 255, 0); // サブネットマスク for softAP
 
// 動作モードフラグ ---------------------------------------------------
bool stamode = true ;                     // WiFi STA 接続モード : true
bool sta_exec = false ;                   // WiFi STA接続モード実行中フラグ
bool config_mode  = false ;               // 初期設定モード ; true
bool config_exec = false ;                // 初期設定実行中フラグ
 
// 設定ファイル用 -----------------------------------------------------
String s_config ;                         // 設定ファイル用 String
char wifi_ssid[128] = "" ;                // SSID        for WiFi STA
char wifi_pass[128] = "" ;                // password    for WiFi STA
IPAddress wifi_ip(192,168,1,66) ;         // IP Address  for WiFi STA
IPAddress wifi_gw(192,168,1,1)  ;         // gate way    for WiFi STA
IPAddress wifi_sm(255,255,255,0) ;        // subnet mask for WiFi STA
IPAddress wifi_dns(192,168,1,1) ;         // dns address for WiFi STA
char wifi_mode[16] = ""   ;               // wifi mode
String foot_msg ="" ;                     // リブートメッセージ用

#define JST 3600*9                        // 日本の GMT との時間差(秒)
#define CLIENT_TIMEOUT 3000               // クライアントタイムアウト (mS)
WiFiServer server(80);
 
String html_CONF ;                        // 設定用 HTML
String html_MAIN ;                        // メイン HTML
String html_resp ;
 
// LED 調光処理用 ----------------------------------------------------------------
// -- for ledc
byte brightness = 0 ;                     // LED 明るさ設定 (0-255)
 
// -----------------------------------------------------------------------------
//  arduino 初期化処理                                                        --
// -----------------------------------------------------------------------------
void setup() {
  int res = 0 ;                                // 結果格納用 (ワーク)
 
  Serial.begin(115200);                        // シリアル 開始
  SPIFFS.begin(true) ;                         // SPIFFS 開始
 
  // 初期設定用 HTML ファイルの読み込み ---------------------------------------
  res = rd_SPIFFS("/WiFi_Config.html",html_CONF) ;  // html_CONF に Config.html を格納
 
  // 本体 HTML ファイルの読み込み ----------------------------------------------
  res = rd_SPIFFS("/main.html",html_MAIN) ;         // html_MAIN に main.html を格納
   
  // レスポンス用HTML ファイルの読み込み ----------------------------------------
  res = rd_SPIFFS("/resp_cnfld.html",html_resp) ;   // html_resp に resp_confld.html を格納
   
  // 初期設定ファイルを読み込み、グローバル変数に設定し、wifi,画面モードを決定 ---
  res = rd_config() ;                          //
 
  // wifiモード(stamode) に応じて AP か STA かを選択してサーバー起動 -------------
  if (stamode == false) {                    // APモードの場合 softAP WiFi 開始
    start_AP_server() ;                      // APモードでサーバー起動
    sta_exec = false ;                       //
  } else {                                   // STAモードの場合 Wifi 開始
    start_STA_server() ;                     // STAモードでサーバー起動
    sta_exec = true ;                        //
    configTime(JST, 0, "ntp.nict.jp",        // NTPサーバー設定
               "time.google.com",            //
               "ntp.jst.mfeed.ad.jp");       //
  }                                          //

  // 実行中のモード (設定かメインか) を設定 -------------------------------------
  if (config_mode) config_exec = true ;   // config_mode なら config_exec を '1'
  else config_exec = false ;              //
 
  Serial.println("Server start!");
 
  // メイン処理用 初期化 ------------------------------------------------------
  ledcSetup(0, 5000, 8);            // channel 0 周波数 5000 Hz, 8 bit 
  ledcAttachPin(2,0);               // attach pin 2 to channel 0
  ledcWrite(0, 0);                  // initialize channel 0 to off     
 
 
}
// -----------------------------------------------------------------------------
 
 
// -----------------------------------------------------------------------------
//  arduino メインループ処理                                                  --
// -----------------------------------------------------------------------------
void loop() {
  WiFiClient client = server.available();
  String htmlwk = "" ;                            // html 用 ワーク変数
  String line   = "" ;                            // クライアントからの入力用
  int xhr = 0 ;                                   // xhr 要求フラグ
  unsigned long start_time = 0 ;                  // タイムアウト監視開始時間
  unsigned long wait_time  = 0 ;                  // 開始/前回受信からの経過時間
  struct tm timeInfo;                             // 時刻を格納するオブジェクト
  
  // HTML クライアント処理 ------------------------------------------------------
  if (client) {
    start_time = millis() ;                       // タイムアウト監視開始時間を更新
    getLocalTime(&timeInfo, 10);                  // 時刻を取得
    Serial.printf(" %04d/%02d/%02d %02d:%02d:%02d\n", // タイムスタンプ表示
                  timeInfo.tm_year + 1900,        //      年 (西暦)
                  timeInfo.tm_mon + 1,            //      月
                  timeInfo.tm_mday,               //      日
                  timeInfo.tm_hour,               //      時
                  timeInfo.tm_min,                //      分
                  timeInfo.tm_sec  )  ;           //      秒
    Serial.println(" +++++++++++++++++ new client! +++++++++++++++++ ");
    while (client.connected()) {                  // クライアントから接続されたとき
      if (client.available()) {                   //
        start_time = millis() ;                   // タイムアウト監視開始時間を更新
        line = client.readStringUntil('\n');      // 1行分取得
        # ifdef DEBUG_HTML                        //
          Serial.println(line) ;                  //
        # endif                                   //
        if (line.indexOf("GET /?") != -1) {       // GET 処理
          if (config_mode)                        // 初期設定用 フォームデータ処理
            set_form2param(line) ;                //   フォームデータを変数に格納
          else {                                  // メイン用 処理
            xhr = proc_main(line) ;               //   フォームデータのメイン処理
          }                                       //
        }                                         //
        // 最終行(空行)を受信した時 -------------------------------------------
        if (line.length() == 1 && line[0] == '\r'){  // 最終行判定
          if (stamode != sta_exec) {         // sta_mode が変わった場合
            if (strcmp(wifi_mode,"ap") == 0){    //  リブートメッセージを設定
              foot_msg = "アクセスポイントモードに移行します。<br> " ;
              foot_msg += "この画面を閉じてアクセスポイントに接続して下さい。" ;
            }else{
              foot_msg = "ステーションモードに移行します。<br>"; 
              foot_msg += "この画面を閉じて 設定したアドレスに接続して下さい。" ;
            }
            if (config_exec) {                    //   初期設定中なら
              send_CONF_html(client) ;            //     初期設定 HTML 送信
            } else {                              //   メイン画面なら
              send_MAIN_html(client) ;            //     メイン HTML 送信
            }                                     //    ( その後 リブート)
          } else {                                // sta_mode が変わらない場合 
            if (config_mode) {                    //   次に初期設定を表示するなら
              send_CONF_html(client) ;            //     初期設定 HTML 送信
              config_exec = true ;                //
            } else {                              //   次にメイン画面なら
              if (xhr) {                          //     XHR 応答の場合
                send_resp_html(client) ;          //       XHR 応答 送信
              } else {                            //     HTML 送信なら
                send_MAIN_html(client) ;          //     メイン HTML 送信
                config_exec = false ;             //
              }                                   //
            }                                     // 
          }                                       //
          # ifdef DEBUG_HTML                      //
            Serial.println("Send HTML") ;         //
          # endif                                 //
          break ;                                 // ループ終了 
        }                                         //
        //-------------------------------------------------------------------
      } else {                                    // 文字列受信していない時
        wait_time = millis() - start_time ;       //
        if ((wait_time) > CLIENT_TIMEOUT) {       // 文字列受信 タイムアウト
          Serial.print("タイムアウト :") ;        //
          Serial.print(wait_time) ;               //
          Serial.println(" mS") ;                 //
          break ;                                 // ループ終了
        }                                         //
      }
    }
    // 接続が切れた場合 ------------------------------------------------------
    client.stop();
    Serial.println("client disonnected");
    Serial.println("----------------------------------------------------");
  }
 
  if (stamode != sta_exec) {                      // sta_mode が変わった場合
    Serial.println("------------------- リブートします------------------- ");
    delay(500);                                   // 
    ESP.restart() ;                               // リブート
  } else {
  }
  // --------------------------------------------------------------------------
 
}

(行#4)
getLocalTime を使用する為に必要な include

(行#32)
日本時間(GMTとの差) の定義。

(行#72~74)
STAモードにする場合、NTPサーバー設定を行う。

(行#103)
時刻を格納するオブジェクトの定義。

(行#108)
時刻を取得。

(行#109~115)
時刻(タイムスタンプ)の表示。
Serial 表示に printf が使用できる模様。

2020年5月2日土曜日

ESP32/arduino:クライアントのタイムアウト

目的:

WEBブラウザからの文字列受信にタイムアウト処理を追加する。

問題点:

「SPIFFSのテキストファイルをダウンロードする 」等 WEBサーバー動作で、クライアント接続後 に client.connected() のまま何も文字列を受信しなかった場合に while 文のループを抜けなくなってしまう。
 文字列受信開始後に 最終行(空行) を受信せずに client.available() == 0 となった場合も同様。( clientclient.connected() のまま で )

対策:

  一定時間 client.available() == 0 の状態が続いた場合、タイムアウトとして、ループを抜けて client.stop() を行う。タイムアウト時間は状況に応じて設定。

start_time = millis() ;         // 開始前の時刻を取得
while (client.connected()) {   
  if (client.available()) {

    start_time = millis() ;     // 文字列受信時の時刻に更新
      |
  } else {
    if ( (millis() - start_time) > 3000 ) {
      break ;                   // 3000 mS 受信がなければループを抜ける
    }
  }


例:

「SPIFFSのテキストファイルをダウンロードする 」の メインスケッチにタイムアウト処理を追加した例を示す。
(メインスケッチ以外のファイルは 「SPIFFSのテキストファイルをダウンロードする 」を参照 )


#include <WiFi.h>                         // WiFi 使用の為
#include "FS.h"                           // SPIFFS 使用の為
#include "SPIFFS.h"                       // SPIFFS 使用の為
 
#define DEBUG_HTML                        // Debug用 Serial 表示制御用
#define DEBUG                             // Debug用 Serial 表示制御用
 
// AP モード用 WIFI 設定 ---------------------------------------------
const char ssid[] = "ESP32_AP";           // SSID for softAP
const char pass[] = "password";           // password for softAP
const IPAddress ip(192, 168, 4, 1);       // IPアドレス for softAP
const IPAddress subnet(255, 255, 255, 0); // サブネットマスク for softAP
 
// 動作モードフラグ ---------------------------------------------------
bool stamode = true ;                     // WiFi STA 接続モード : true
bool sta_exec = false ;                   // WiFi STA接続モード実行中フラグ
bool config_mode  = false ;               // 初期設定モード ; true
bool config_exec = false ;                // 初期設定実行中フラグ
 
// 設定ファイル用 -----------------------------------------------------
String s_config ;                         // 設定ファイル用 String
char wifi_ssid[128] = "" ;                // SSID        for WiFi STA
char wifi_pass[128] = "" ;                // password    for WiFi STA
IPAddress wifi_ip(192,168,1,66) ;         // IP Address  for WiFi STA
IPAddress wifi_gw(192,168,1,1)  ;         // gate way    for WiFi STA
IPAddress wifi_sm(255,255,255,0) ;        // subnet mask for WiFi STA
IPAddress wifi_dns(192,168,1,1) ;         // dns address for WiFi STA
char wifi_mode[16] = ""   ;               // wifi mode
String foot_msg ="" ;                     // リブートメッセージ用

#define CLIENT_TIMEOUT 3000               // クライアントタイムアウト (mS)
WiFiServer server(80);
 
String html_CONF ;                        // 設定用 HTML
String html_MAIN ;                        // メイン HTML
String html_resp ;
 
// LED 調光処理用 ----------------------------------------------------------------
// -- for ledc
byte brightness = 0 ;                     // LED 明るさ設定 (0-255)
 
// -----------------------------------------------------------------------------
//  arduino 初期化処理                                                        --
// -----------------------------------------------------------------------------
void setup() {
  int res = 0 ;                                // 結果格納用 (ワーク)
 
  Serial.begin(115200);                        // シリアル 開始
  SPIFFS.begin(true) ;                         // SPIFFS 開始
 
  // 初期設定用 HTML ファイルの読み込み ---------------------------------------
  res = rd_SPIFFS("/WiFi_Config.html",html_CONF) ;  // html_CONF に Config.html を格納
 
  // 本体 HTML ファイルの読み込み ----------------------------------------------
  res = rd_SPIFFS("/main.html",html_MAIN) ;         // html_MAIN に main.html を格納
   
  // レスポンス用HTML ファイルの読み込み ----------------------------------------
  res = rd_SPIFFS("/resp_cnfld.html",html_resp) ;   // html_resp に resp_confld.html を格納
   
  // 初期設定ファイルを読み込み、グローバル変数に設定し、wifi,画面モードを決定 ---
  res = rd_config() ;                          //
 
  // wifiモード(stamode) に応じて AP か STA かを選択してサーバー起動 -------------
  if (stamode == false) {                    // APモードの場合 softAP WiFi 開始
    start_AP_server() ;                      // APモードでサーバー起動
    sta_exec = false ;                       //
  } else {                                   // STAモードの場合 Wifi 開始
    start_STA_server() ;                     // APモードでサーバー起動
    sta_exec = true ;                        //
  }                                          //
 
  // 実行中のモード (設定かメインか) を設定 -------------------------------------
  if (config_mode) config_exec = true ;   // config_mode なら config_exec を '1'
  else config_exec = false ;              //
 
  Serial.println("Server start!");
 
  // メイン処理用 初期化 ------------------------------------------------------
  ledcSetup(0, 5000, 8);            // channel 0 周波数 5000 Hz, 8 bit 
  ledcAttachPin(2,0);               // attach pin 2 to channel 0
  ledcWrite(0, 0);                  // initialize channel 0 to off     
 
 
}
// -----------------------------------------------------------------------------
 
 
// -----------------------------------------------------------------------------
//  arduino メインループ処理                                                  --
// -----------------------------------------------------------------------------
void loop() {
  WiFiClient client = server.available();
  String htmlwk = "" ;                            // html 用 ワーク変数
  String line   = "" ;                            // クライアントからの入力用
  int xhr = 0 ;                                   // xhr 要求フラグ
  unsigned long start_time = 0 ;                  // タイムアウト監視開始時間
  unsigned long wait_time = 0 ;                   // 開始/前回受信からの経過時間

  // HTML クライアント処理 ------------------------------------------------------
  if (client) {
    start_time = millis() ;                       // タイムアウト監視開始時間を更新
    Serial.println(" +++++++++++++++++ new client! +++++++++++++++++ ");
    while (client.connected()) {                  // クライアントから接続されたとき
      if (client.available()) {                   // 受信文字列がある事
        start_time = millis() ;                   // タイムアウト監視開始時間を更新
        line = client.readStringUntil('\n');      // 1行分取得
        # ifdef DEBUG_HTML                        //
          Serial.println(line) ;                  //
        # endif                                   //
        if (line.indexOf("GET /?") != -1) {       // GET 処理
          if (config_mode)                        // 初期設定用 フォームデータ処理
            set_form2param(line) ;                //   フォームデータを変数に格納
          else {                                  // メイン用 処理
            xhr = proc_main(line) ;               //   フォームデータのメイン処理
          }                                       //
        }                                         //
        // 最終行(空行)を受信した時 -------------------------------------------
        if (line.length() == 1 && line[0] == '\r'){  // 最終行判定
          if (stamode != sta_exec) {         // sta_mode が変わった場合
            if (strcmp(wifi_mode,"ap") == 0){    //  リブートメッセージを設定
              foot_msg = "アクセスポイントモードに移行します。<br> " ;
              foot_msg += "この画面を閉じてアクセスポイントに接続して下さい。" ;
            }else{
              foot_msg = "ステーションモードに移行します。<br>"; 
              foot_msg += "この画面を閉じて 設定したアドレスに接続して下さい。" ;
            }
            if (config_exec) {                    //   初期設定中なら
              send_CONF_html(client) ;            //     初期設定 HTML 送信
            } else {                              //   メイン画面なら
              send_MAIN_html(client) ;            //     メイン HTML 送信
            }                                     //    ( その後 リブート)
          } else {                                // sta_mode が変わらない場合 
            if (config_mode) {                    //   次に初期設定を表示するなら
              send_CONF_html(client) ;            //     初期設定 HTML 送信
              config_exec = true ;                //
            } else {                              //   次にメイン画面なら
              if (xhr) {                          //     XHR 応答の場合
                send_resp_html(client) ;          //       XHR 応答 送信
              } else {                            //     HTML 送信なら
                send_MAIN_html(client) ;          //     メイン HTML 送信
                config_exec = false ;             //
              }                                   //
            }                                     // 
          }                                       //
          # ifdef DEBUG_HTML                      //
            Serial.println("Send HTML") ;         //
          # endif                                 //
          break ;                                 // ループ終了 
        }                                         //
        //-------------------------------------------------------------------
      } else {                                    // 文字列受信していない時
        wait_time = millis() - start_time ;       //   前回受信からの時間を取得
        if ((wait_time) > CLIENT_TIMEOUT) {       //   文字列受信タイムアウト判定
          Serial.print("タイムアウト :") ;        //   
          Serial.print(wait_time) ;               //
          Serial.println(" mS") ;                 //
          break ;                                 // ループ終了
        }                                         //
      }                                           //
    }
    // 接続が切れた場合 ------------------------------------------------------
    client.stop();
    Serial.println("client disonnected");
    Serial.println("----------------------------------------------------");
  }
 
  if (stamode != sta_exec) {                      // sta_mode が変わった場合
    Serial.println("------------------- リブートします------------------- ");
    delay(500);                                   // 
    ESP.restart() ;                               // リブート
  } else {
  }
  // --------------------------------------------------------------------------
 
}

(行#31)
タイムアウト時間 の define 文

(行#96,97)
タイムアウト処理用ローカル変数

(行#101,105)
開始時、文字列受信時の 監視開始時間を取得

(行#151~158)
client.available() == 0 時の処理。
タイムアウト時間を超えていたら break ;を実行