AXI Stream
AXI Stream
仕様
OV7670からの画像データをVDMA IPやAXI Stream To Video Out IPに渡すには、AXI4-Streams I/Fを使う必要がある。ここでは、AXI4-Streams I/Fの仕様とAXI4-Streams VIPの使い方についてまとめる。
信号一覧
ビデオ信号系としてAXI4-Streams I/Fを使用する場合、必須信号は下記の4つのみと簡単な通信プロトコルである。
タイミングチャート
- マスター側の設計注意事項
IP
ここにも紹介されているが、AXI Stream 関係のIPはたくさんある。 現時点で使う可能性があるIPのみ簡単に説明する。
AXI4-Stream Subset Converter
AXI4-Stream I/Fはほとんどがオプション信号なので、IPコアによって使用する信号が異なる。AXI4-Stream Subset ConverterはIPコア間の信号の違いを吸収し、IPコア間を正しく接続する役割を担っている。
AXI4-Stream Subset Converterの設定画面を下図に示す。DATA、TUSER、TSTRB、TKEEP、TID、TDEST、TLAST 信号のリマップや信号の追加が可能、ストーリデータの並び替えなどができる。
AXI4-Stream VIP
AXI4-Stream VIP(Verification IP)はAXI Stream I/F を持ったIPコアを検証するときに役立つIPコアである。使い方はここに記載されているので、この記事で不足している場合は参照すること。
使い方
VIPを使うためには、Vivado IPI上で VIPの配置・設定し、テストベンチを作成する。という流れである。VIPはSystemVerilogにて作成されているため、パッケージのインポート、newを使ったインスタンス生成など少し変わった記述である。
Vivado IPI
- IPI上にAXI4-Stream VIPを配置し、テスト対象と接続する
IPI上で Add IP > AXI4-Stream VIPを選択し、画面上にAXI4-Stream VIPを配置する。VIPにテスト対象のIPコアを接続する。今回は作成中のOV7670 IPをテスト対象IPとする。
- VIPの動作モードを設定する
VIPはスレーブとして動作するよう設定する。IPI上での設定は以上で終了である。次にテストベンチを記述する。
テストベンチ
- AXI4-Stream VIPを使用するためにパッケージをインポート
注意:IPの階層に注意すること。
|
- VIPエージェントの宣言
注意:マスターの場合はcomponent_name"_mst_t mst_agent、スレーブの場合は"component_name"_slv_t slv_agent*とすること
// Declare AXI4 Stream VIP agent
BlockDesign_axi4stream_vip_0_0_slv_t slv_agent;
- VIPエージェントをインスタンス生成し、I/F までの階層を引数として渡す
|
- エージェントを開始させる
|
以上で終了である。上手く動作するとTREADY信号がパタパタ切り替わる。
参考サイト
積読
去年から今年に入り、様々な本を購入した。ただ、これらの本は全然読めていない。途中でやめてしまったり、まったく読んでいなかったりなどで完読した本は一冊もない。。
ちょうど季節は秋ということで10月~11月を読書月間とし、積読本をひとつでも減らしたいと考えている。2018年から2019年の間に購入した本は次である。
No. | タイトル | ジャンル |
---|---|---|
1 | Redmineによるタスクマネジメント実践技法 | マネジメント |
2 | コンサル一年目が学ぶこと | マネジメント |
3 | 土壇場プロジェクト 成功の方程式 | マネジメント |
4 | カラー版 マンガでわかる 会社の設立・運営 | マネジメント |
5 | カラー版 マンガでわかる 事業計画書のつくり方 | マネジメント |
6 | デスマーチ 第2版 ソフトウエア開発プロジェクトはなぜ混乱するのか | マネジメント |
7 | チーム開発実践入門──共同作業を円滑に行うツール・メソッド WEB+DB PRESS plus | マネジメント |
8 | ドラクエに学ぶ チームマネジメント | マネジメント |
9 | 図解入門 よくわかる最新 システム開発者のための仕様書の基本と仕組み[第2版] | マネジメント |
10 | マネージャーの問題地図 ~「で、どこから変える?」あれもこれもで、てんやわんやな現場のマネジメント | マネジメント |
11 | マンガでわかるプロジェクトマネジメント | マネジメント |
12 | アプリ開発チームのためのプロジェクトマネジメント チーム駆動開発でいこう! | マネジメント |
13 | マンガでわかる! 幼稚園児でもできた!! タスク管理超入門 impress QuickBooks | マネジメント |
14 | 図解 これ以上やさしく書けない プロジェクトマネジメントのトリセツ (Panda Publishing) | マネジメント |
15 | システム開発のためのWBSの作り方(日経BP Next ICT選書) | マネジメント |
16 | 失敗学実践講義 文庫増補版 (講談社文庫) | マネジメント |
17 | ゼロからのプレゼンテーション | プレゼン技術 |
18 | 社内プレゼンの資料作成術 | プレゼン技術 |
19 | 1分で話せ 世界のトップが絶賛した大事なことだけシンプルに伝える技術 | プレゼン技術 |
20 | ロジカル・プレゼンテーション ― 自分の考えを効果的に伝える戦略コンサルタントの「提案の技術」 | プレゼン技術 |
21 | マンガでわかる 必ず伝わる!ロジカル会話術 (アクションコミックス) | プレゼン技術 |
22 | 「それ、根拠あるの?」と言わせないデータ・統計分析ができる本 | 統計 |
23 | 統計でウソをつく法 (ブルーバックス) | 統計 |
24 | マンガでわかる統計学 素朴な疑問からゆる~く解説 | 統計 |
25 | 完全独習 統計学入門 | 統計 |
26 | 【この1冊でよくわかる】ソフトウェアテストの教科書―品質を決定づけるテスト工程の基本と実践 | ソフトウェアテスト |
27 | 知識ゼロから学ぶソフトウェアテスト 【改訂版】 | ソフトウェアテスト |
28 | はじめて学ぶソフトウェアのテスト技法 | ソフトウェアテスト |
29 | フィードバック制御入門 | 制御工学 |
30 | 現場で役立つ制御工学の基本 | 制御工学 |
31 | Pythonによる制御工学入門 | 制御工学 |
32 | はじめての制御工学 改訂第2版 | 制御工学 |
33 | エンジニアのためのマネジメントキャリアパス | キャリアパス |
34 | 目指すは生涯現役のITエンジニア!どこでも通用するために今からやっておくべきチェックシート。 | キャリアパス |
35 | CAREER SKILLS ソフトウェア開発者の完全キャリアガイド | キャリアパス |
36 | SOFT SKILLS ソフトウェア開発者の人生マニュアル | キャリアパス |
37 | 武器がないエンジニアは肯定を続けることで武器を手にする。20分で読めるシリーズ | キャリアパス |
38 | Inspired: 顧客の心を捉える製品の創り方 | キャリアパス |
39 | ITエンジニアとして生き残るための指南書。 自分を守りアップデートするための18のテクニック | キャリアパス |
40 | なれる!SE 2週間でわかる?SE入門 (電撃文庫) | キャリアパス |
41 | 小さな会社の稼ぐ技術 | キャリアパス |
42 | リーン・スタートアップ ムダのない起業プロセスでイノベーションを生みだす | キャリアパス |
43 | A4一枚勉強法 効率よく夢をかなえる | キャリアパス |
44 | ジョブ理論 | キャリアパス |
45 | UMLモデリングレッスン | オブジェクト指向 |
46 | UMLモデリング入門 | オブジェクト指向 |
47 | ダイアグラム別UML徹底活用 第2版 | オブジェクト指向 |
48 | オブジェクト指向でなぜつくるのか 第2版 | オブジェクト指向 |
49 | ゼロからはじめるLinuxサーバー構築・運用ガイド 動かしながら学ぶWebサーバーの作り方 | Linux |
50 | これ一冊で完全理解 Linuxカーネル超入門(日経BP Next ICT選書) | Linux |
51 | 日経Linux(リナックス) 2019年9月号 [雑誌] | Linux |
52 | Linuxサーバーがゼロから作れる本 | Linux |
53 | 超例解Linuxカーネルプログラミング | Linux |
54 | まんがでわかるLinux シス管系女子(日経BP Next ICT選書) | Linux |
55 | FPGAパソコンZYBOで作るLinux I/Oミニコンピュータ | FPGA Linux |
56 | ARM Cortex-A9×2! ZynqでワンチップLinux on FPGA | FPGA Linux |
57 | ソフトウェア技術者のためのFPGA入門 機械学習編 (技術の泉シリーズ(NextPublishing)) | FPGA AI |
58 | 【改訂2版】FPGAボードで学ぶ 組込みシステム開発入門[Intel FPGA編] | FPGA |
59 | C言語によるオブジェクト指向プログラミング入門 | C言語 オブジェクト指向 |
60 | モダンC言語プログラミング | C言語 オブジェクト指向 |
61 | 新・明解c言語 入門編 | C言語 |
62 | テスト駆動開発による組み込みプログラミング | C言語 |
63 | 新・明解C言語で学ぶアルゴリズムとデータ構造 | C言語 |
64 | Visual Studio C#でRaspberry Pi ADCグラフ編 | C# |
65 | 独習C# 新版 | C# |
66 | 作って覚えるVisual C# 2017 デスクトップアプリ入門 | C# |
67 | 基礎からしっかり学ぶC#の教科書 マイクロソフト関連書 | C# |
68 | トランジスタ技術 2018年11月 | AI |
リストにすると改めて感じるが、こんなに購入しているとは思わなかった・・・しかも積読なので知識にもなっておらずお金の無駄になっている。すごくもったいない。今日から少し読み始め、完読した本については感想や役立ったことをブログに書いていこうと思う。ブログも続けるように頑張りたい。。。
UIOを用いてLinuxからIPコアへアクセスする
LinuxからIPコアへアクセスする方法は3つある。そのうち上位2つの方法は簡単に使用できるので重宝されているらしい。
ただ、dev/mem
を用いる方法は、物理アドレスに直接アクセスできることから、予期せぬメモリ領域にデータを書き込んでしまい、カーネルやプロセスのメモリを破壊する恐れがある。実際に使ったことないのでわからないが。
そのため、上記の中ではUIOを使用する方法が良いらしいので、UIOの使い方の調査と実機を使った動作確認を行う。調査内容と実機確認内容を記載するとブログが長くなるので、理解編と実践編と2回に分ける。この記事は理解編である。
目次
UIOとは
UIO(User space I/O)は物理アドレスへアクセスするための仕組みで、Linux2.6.22から追加された機能らしい。UIOは割り込みハンドリングが可能であり、かつデバイスドライバ開発が不要であるという利点がある。その一方で、DMA転送には対応していないのが欠点らしい。
使用手順
UIOを用いてのIPコアアクセスは、以下の手順が必要となる。
UIOドライバを有効化する
コンフィグのデフォルト設定ではUIOドライバが有効であるので、ここは読み飛ばしてもよい。
- 以下のコマンドを実行し、カーネルコンフィグレーション画面を表示
make xilinx_zynq_deconfig #MPSocの場合は、下記コマンド #make xilinx_zynqmp_deconfig make menuconfig
- UserspcaeI/O platform driverwith IRQ handoling を有効化にする Device Drives > Userspace I/O drivers まで移動する。UserspaceI/O platform driver with generic IRQ handling にチェック、またはM<モジュール>が付いていればよい。
IPコアをUIOデバイスに設定する
今回は既存の.dtbファイルを.dtsファイルに逆変換し、UIOデバイスの設定を行う。そして、設定した.dtsファイルを.dtbファイルに変換し、デバイスツリーを再生成する 。
- .dtbファイルを.dtsファイルへ逆変換 以下のコマンドを実行し、.dtsファイルをdtsファイルに変換する。
$ dtc -I dtb -O dts -o system.dts system.dtb
- UIOデバイスの設定
下記のように、IPコアの
compatible
をgeneric-uio
に変更する。
Gpio@a0020000{ - compatible = "xlnx,GpioIP-1.1"; + generic-uio = "generic-uio"; reg = <0x 0xa0020000 0x0 0x1000>; };
$ dtc -I dts -O dtb -o system.dtb system.dts
Linuxから実行できるプログラム作成する
mmapはUNIX OSシステムコールの一つで、ファイルに連続した仮想アドレス空間にマッピングする関数である。LinuzからFPGAのアドレス空間にアクセスするには、mmap関数とUIOを使用して、FPGAのアドレス空間を仮想アドレス空間にマッピングするプログラムを作成する。
手順
1. open関数を用いてデバイスをオープンする
open関数の戻り値と引数は以下である。
int open (const char *pathname, int flags) /* * @param pathname :オープンするファイルを指定する文字列 * @param flags : O_RDONLY(0x),O_WRONLY(0x), O_RDWR(0x) のいづれかが入る。 * @return * - 正常:正の整数を返す * - 異常:-1を返す /*/
void *mmap (void *addr, size_t len,int prot, int flags, int fileds, off_t off); /* * @param *addr : マッピングするアドレスを指定する。NULLを指定した場合、 * カーネルがマッピングするアドレスを決める。 * @param size_t len :マッピングするデータサイズを指定する。 * @param int prot : マップ領域のアクセス権限を設定する。 * @param int flags : マップ領域の扱いを設定する。MAP_PRIVATE とMAP_SHAREDの2つのフラグがある。 * MAP_SHAREDがよく使用される。 * @param int fileds : opne関数でオープンしたファイルを指定する。 * @param off_t off : ファイルのどこからマッピングしたいのか、オフセットを指定 * @return * - 正常:マップ領域のポインタを返す * - 異常:MAP_FAILEDを返す /*/
- close関数により、オープンしたファイルディスクリプタを閉じる。
close(fd); /* * @return * - 正常:0を返す * - 異常:-1を返す */
- munmap関数により、mmapでマップしたメモリを解放する。 munmap関数の引数と戻り値は以下である。
munmap(void *addr, size_t len); /* * @return * - 正常:0を返す * - 異常:-1を返す */
以上が理解編である。次回は、実践編でUIOを用いてLinuxからIPコアへアクセスできるか動作確認する。
ポリモーフィズム
目次
ポリモフィズム
ポリモフィズムとは、宣言した変数の型ではなく、実体であるインスタンスに応じて呼び出されるメソッドが決まること。これにより、1つのメソッドで、クラスに応じた適切な処理が行える。
理解編
オーバーライド
オーバーライドとは、隠蔽と同じように基本クラスのメソッドを派生クラスで書き換えできる機能である。オーバーライドするには、基本クラスのメソッドにvertial
、派生クラスのメソッドにoverride
をそれぞれ付けてメソッドを宣言すればよい。基本クラスでvertial
が宣言されていないメソッドは、オーバーライドできない。
継承関係のあるクラスでは、派生クラスから基本クラスへの変換、または基本クラスから派生クラスへの変換が可能である。派生クラスから基本クラスへの変換をアップキャスト、基本クラスから派生クラスへの変換をダウンキャストと呼ぶ。
隠蔽との違い
隠蔽との違いを次にまとめる。
- 隠蔽は new
を宣言するだけで利用できる。一方、オーバーライドはvirtual
と override
をセットで宣言する必要がある。
- 隠蔽では、基本メソッドと派生メソッドとで アクセス修飾子、戻り値 を一致させる必要はない。
- 隠蔽は想定外のメソッドを書き換えてしまう可能性がある、
- 隠蔽の場合、アップキャストすると派生クラスで再定義した機能が使用できない。
上記の理由により、ポリモフィズムを実現したい場合、隠蔽を使用せずにオーバーライドでメソッドを再定義すべきらしい。
実践編
オーバーライド
オバーライドを使用したコードを次に示す。
// 基本クラス class Music { public virtual void BaseInfo() { Console.WriteLine("Music"); } } // 派生クラス、オーバーライドあり class Pop : Music { public override void BaseInfo() { Console.WriteLine("Pop"); } } // 派生クラス、隠ぺいあり class Jazz : Music { new public void BaseInfo() { Console.WriteLine("Jazz"); } } class MainClass { static void Main(string[] args) { // 基本クラスMusicのインスタンス生成 Music music = new Music(); music.BaseInfo(); // 派生クラスPopのインスタンス生成 Pop pop = new Pop(); pop.BaseInfo(); // 派生クラスJazzのインスタンス生成 Jazz jazz = new Jazz(); jazz.BaseInfo(); } }
結果を次に示す。
Music Pop Jazz
隠蔽との違い
// 基本クラス class Music { public virtual void BaseInfo() { Console.WriteLine("Music"); } } // 派生クラス class Pop : Music { // オーバーライド public override void BaseInfo() { Console.WriteLine("Pop"); } public void PopInfo() { Console.WriteLine("PopInfo"); } } // 派生クラス class Jazz : Music { // 隠蔽 new public void BaseInfo() { Console.WriteLine("Jazz"); } } class Program { static void Main(string[] args) { // アップキャスト // PopクラスのインスタンスをMusicクラスの変数に代入 Music music = new Pop(); music.BaseInfo(); // ダウンキャスト:成功例 // Musicクラスの変数をPopクラスの変数に代入 Pop pop = (Pop)music; pop.BaseInfo(); pop.PopInfo(); // ダウンキャスト:失敗例 // Musicクラスの変数をJazzクラスの変数に代入 // 元クラスがPopクラス(継承関係が異なるクラス)であるためエラーとなる。 // Jazz jazz = (Jazz)music; } }
結果を次に示す。
Pop Pop PopInfo
インタフェース
理解編
インタフェース
インタフェースとは、メソッドの呼び出しだけを定義したものである。メソッドの処理内容は継承したクラスで定義することになる。継承クラスにメソッドの処理内容を記載することを 実装する という。インタフェースは次のように定義すればよい。インタフェースとクラスを区別するために、先頭にI
を付けるとよいらしい。
interface インタフェース名 { //メンバーの宣言 }
また、インタフェースはプロパティをもつこともできる。
インタフェースの多重継承と多重実装
インタフェースは継承が可能である。インタフェースを継承する場合は、クラスの継承と同じようにすればよい。また、インタフェースはクラスの継承とは異なり、多重継承が可能である。多重継承する場合は、基本インタフェースを ,
で区切ればよい。
interface 継承インタフェース : 基本インタフェース
{
メンバー宣言
}
また、インタフェースは1つのクラスに複数のインタフェースを実装できる。
実践編
インタフェース
インタフェースを使用したコードを次に示す。
interface IGetInfo { void Getinfo(); } class Sample : IGetInfo { public void Getinfo() { Console.WriteLine("test"); } } class Program { static void Main(string[] args) { Sample inst = new Sample(); inst.Getinfo(); Console.ReadKey(); } }
結果を次に示す。
test
プロパティを用いたインタフェースのコードを次に示す。
interface Ipoint { int Px { get; set; } int Py { get; set; } } class ReversePoint : Ipoint { int x; int y; // コンストラクタ public ReversePoint(int xdata, int ydata) { this.x = xdata; this.y = ydata; } // Property public int Px { get { return -x; } set { x = value; } } public int Py { get { return -y; } set { y = value; } } } class MainClass { public static void DisplayPoint(Ipoint point) { Console.WriteLine("x={0}, y={1}", point.Px, point.Py); } static void Main(string[] args) { int VAL_PX = -10; int VAL_PY = 100; ReversePoint p1 = new ReversePoint(VAL_PX, VAL_PY); Console.WriteLine($"PX is {VAL_PX}, PY is {VAL_PY}"); Console.WriteLine($"invert PX is {p1.Px}, invert PY is {p1.Py}"); VAL_PX = 100; VAL_PY = 1; p1.Px = VAL_PX; p1.Py = VAL_PY; Console.WriteLine($"PX is {VAL_PX}, PY is {VAL_PY}"); Console.WriteLine($"invert PX is {p1.Px}, invert PY is {p1.Py}"); Console.ReadKey(); } } }
結果を次に示す。
PX is -10, PY is 100 invert PX is 10, invert PY is -100 PX is 100, PY is 1 invert PX is -100, invert PY is -1
インタフェースの多重継承と多重実装
インタフェースの多重継承と多重実装を使用したコードを次に示す。
interface IBase01Interface { void Method01(int a); } interface IBase02Interface { void Method02(int a); } // インタフェースの多重継承 interface IBase03Interface : IBase01Interface, IBase02Interface { void Method03(int a); } // インタフェースの多重実装 class Sample : IBase01Interface, IBase02Interface { public void Method01(int a) { Console.WriteLine($"Sample a is {a}"); } public void Method02(int a) { Console.WriteLine($"Sample a is {a * a}"); } } class Test : IBase03Interface { public void Method01(int a) { Console.WriteLine($"a is {a}"); } public void Method02(int a) { Console.WriteLine($"a is {a * a}"); } public void Method03(int a) { Console.WriteLine($"a is {a * a * a}"); } } class Program { static void Main(string[] args) { // インスタンス生成 Test test = new Test(); Sample sample = new Sample(); test.Method01(2); test.Method02(2); test.Method03(2); sample.Method01(2); sample.Method02(2); } }
結果を次に示す。
a is 2 a is 4 a is 8 Sample a is 2 Sample a is 4
継承とカプセル化
目次
継承とカプセル化
継承
理解編
継承
継承 とは、あるクラスを土台として新しいクラスを作ることである。新しいクラスは継承元のメンバーを引き継ぐことができる。また、新しいメンバーを追加することもできる。 継承元のクラスを基本クラス 、新しいクラスを 派生クラス と呼ぶ。1つの基本クラスから派生クラスを作ることとを単一継承 と呼ぶ。一方、複数の基本クラスから派生クラスを作ることを 多重継承と呼ぶが、C# では使用できない。 Javaとかなら使用できるのだろうか?
継承は派生クラスの後ろに:
を記述し、その後ろに継承元の基本クラスを指定する。
// 基本クラスの定義 class 基本クラス { 処理内容 } // 派生クラスの定義 class 派生クラス : 基本クラス { 処理内容 }
継承クラスのコンストラクタ
基本クラスと派生クラスにコンストラクタが存在する場合、基本クラスのコンストラクタ、派生クラスのコンストラクタの順番で呼び出される。
引数なしのコンストラクタはインスタンス生成時に自動的に呼び出される。一方、引数ありのコンストラクタでは、コンストラクタ初期化子と呼ばれるbase
キーワードを使用し、基本クラスに渡す引数を指定する必要がある。
隠蔽
隠蔽とは、派生クラスのメンバーを基本クラスのメンバーと同じ名前に定義することで、基本クラスのメンバーを強制的に上書きすることである。隠蔽する場合は、メンバー宣言の前にnew
をつければよい。
基本クラスを参照していることを示すbase
を使用することで、派生クラスでメンバーを隠蔽しつつ基本クラスのメンバーにアクセスできる。
そうなると隠蔽するメリットは何なのだろうか?
隠蔽に似た機能で オーバーライド というものがある。オーバーライドは基本クラスのメソッドを派生クラスで書き換えるものであるが、隠蔽とは違うらしい。
実践編
継承
// 基本クラスの生成 class Music { public int Type = 3; public string Name = "Music"; public void BaseInfo() { Console.WriteLine("BaseInfo is OK"); } } // 派生クラスの生成 class Song : Music { public string Key = "Song"; // 新しいメソッドの追加 public void DrvInfo() { Console.WriteLine("DrvInfo is OK"); } } class MainClass { static void Main(string[] args) { // 派生クラスのインタスタン生成 Song test = new Song(); // 基本クラスのインタスタン生成 Music baseInst = new Music(); Console.WriteLine("----Access to derived class----"); // 派生クラスのメンバーアクセス Console.WriteLine("Key is " + test.Key); Console.WriteLine("Type is " + test.Type); test.DrvInfo(); test.BaseInfo(); Console.WriteLine("----Access to base class----"); // 基本クラスのメンバーアクセス Console.WriteLine("Type is " + baseInst.Type); baseInst.BaseInfo(); Console.ReadKey(); } }
結果を次に示す。 派生クラスでは、基本クラスのメンバーに加えて派生クラスにて追加したメンバーも使用できることが分かる。
----Access to derived class---- Key is Song Type is 3 DrvInfo is OK BaseInfo is OK ----Access to base class---- Type is 3 BaseInfo is OK
継承クラスのコンストラクタ
引数なしのコンストラクタ
class Music { public Music() { Console.WriteLine("Base class Constracter"); } } class Pop : Music { public Pop() { Console.WriteLine("Derived class Constracter"); } } class Program { static void Main(string[] args) { Pop instPop = new Pop(); } }
結果を次に示す。
Base class Constracter Derived class Constracter
引数ありのコンストラクタ
class Music { public Music(string str) { Console.WriteLine(str); } } class Pop : Music { public Pop(string str1, string str2) : base(str1) { Console.WriteLine(str2); } } class Program { static void Main(string[] args) { Pop instPop = new Pop("Music", "Pop"); } }
結果を次に示す。
Music Pop
隠蔽
隠蔽のコードを次に示す。
// 基本クラスの定義 class Music { public int value = 5; } // 派生クラスの定義 class Pop : Music { // valueを隠蔽 new public int value = 10; public void ViewValue() { // 基本クラスにアクセス Console.WriteLine(base.value); // 自分のメンバーにアクセス Console.WriteLine(value); } } class Program { static void Main(string[] args) { Pop instPop = new Pop(); instPop.ViewValue(); } }
結果を次に示す。
5 10
カプセル化
理解編
カプセル化
カプセル化とは、クラスへの外部アクセスを制限し、安全に使いやすくするためのものである。
- アクセス制限はアクセス修飾子を使用する。
- アクセス修飾子によりアクセス制限のレベルが設定できる。
アクセス修飾子 | レベル |
---|---|
private | クラスの内部のみアクセス可能 |
public | アクセス制限なし |
protected | クラスの内部のみアクセス可能、または派生クラスに制限される |
フィールドのアクセス制限
クラス内のフィールド値を安全に変更するには、外部から直接アクセスしない方がよい。その方法として、アクセサーメソッド を使用する方法と プロパティ を使用する方法がある。
とは書いたが、なぜフィールド値を直接書き換えしてはいけないのか理解できていない。リード/ライドの方向指定ができること、値のエラーチェックがことがメリットなのだろうか。
アクセサーメソッド
アクセサーメソッドとはフィールド値の変更や取得を行うためだけのメソッドのことである。メソッド名がSetxx
やGetxx
となっているものが、アクセサーメソッドに該当するらしい。Setxx
メソッドで入力値のエラーチェックが行えるので、有効な値を他のメソッドに渡すことができる。
ただし、フィールド変数の数だけアクセサーメソッドの数が増えることから、可読性が悪くなるというデメリットがあるらしい。そこで C# では、デメリットを解消する方法として、プロパティ と呼ばれる機能がある。
プロパティ
プロパティは『クラスの内部ではメソッドのように振る舞うが、クラス外からはフィールドのようにアクセスできる』ものである。プロパティは、set
と set
を使用し、次のように定義すること使用できる。
アクセス修飾子 データ型 プロパティ { set { // 値代入時の処理を記述 フィールド名 = value; // 代入値の妥当性チェックの記述も可能 } get { // 値参照時の処理を記述 // 返却値の妥当性チェックの記述も可能 return フィールド名; } }
set
メソッドは値を設定するものなので戻り値を指定できない。また、これらメソッドは値の妥当性チェックや演算も行うことができる。 確かに使ってみると変数と同じ扱いで済むので便利である!
自動プロパティ
値の設定・取得をするだけならプロパティの中身を省略して記述できる。このプロパティを 自動プロパティ と呼ぶ。自動プロパティに初期値を与えることもできる。初期値が不要な場合は、次のうち= 初期値
を削除すればよい。
データ型 プロパティ名 {get; set;} = 初期値;
実践編
カプセル化
class Music { private int type = 0; // protected :クラス内部と派生クラスの内部からのみアクセス可能 protected string name = "Music"; public void SetType(int setType) { // "this"により、Musicクラスのメンバーを指定する。 // "this"は、他メンバーにパラメータを渡す場合に用いる。 // 同じクラス内なので、Typeにアクセス可能 this.type = setType; } // フィールドの値を出力するメソッド public void Printname() { Console.WriteLine(name); Console.WriteLine(type); } } class Pop : Music { new public void Printname() { // nameのアクセス修飾子はprivate、派生クラスなのでアクセスできない // base.type = 100; // nameのアクセス修飾子はprotected、派生クラスなのでアクセス可能 this.name = "Pop"; // "base"により、基本クラスのメソッドを呼び出している。 // Printnameのアクセス修飾しはpublic なのでアクセス可能 base.Printname(); } } class MainClass { static void Main(string[] args) { Pop pop = new Pop(); pop.SetType(10); // protected にアクセスしようとするとエラーになる // アクセスできない保護レベルとなっている // pop.name = "test"; pop.Printname(); Console.ReadKey(); } }
次に結果を示す。
Pop 10
フィールドのアクセス制限
アクセサーメソッド
アクセサーメソッドを用いたコードを次に示す。
class Bmi { // --フィールド値 // アクセス修飾子をprivateにして、クラス外からのアクセスを禁止する private double height = 0; private double weight = 0; // -- heightを取得するためのアクセサーメソッド public double GetHeight() { return this.height; } // -- weightを取得するためのアクセサーメソッド public double GetWeight() { return this.weight; } // -- 取得したheightをセットするためのアクセサーメソッド public void SetHeight(double setHeight) { if (setHeight <= 0) { Console.WriteLine("Error : invalid value"); } else { this.height = setHeight; Console.WriteLine($"set height is {height} cm"); } } // -- 取得したweightをセットするためのアクセサーメソッド public void SetWeight(double setWeight) { if (setWeight <= 0) { Console.WriteLine("Error : invalid value"); } else { this.weight = setWeight; Console.WriteLine($"set weight is {weight} kg"); } } // -- BMIを計算するメソッド public double CalcBmi() { return GetWeight() / ((GetHeight() / 100) * (GetHeight() / 100)); } } class Program { static void Main(string[] args) { Bmi bmiInst = new Bmi(); Console.Write("enter height (cm) :"); double height = double.Parse(Console.ReadLine()); Console.Write("enter weight (kg) :"); double weight = double.Parse(Console.ReadLine()); // 下記のようにメンバーを直接アクセスすることができない。 // bmiInst.weght = 64; // アクセサーメソッドを用いてメンバー値の変更を行う。 bmiInst.SetHeight(height); bmiInst.SetWeight(weight); Console.WriteLine($"BMI is {bmiInst.CalcBmi()}"); } }
heiht:174、weight:65 を入力したときの結果を次に示す。
enter height (cm) :174 enter weight (kg) :65 set height is 174 cm set weight is 65 kg BMI is 21.4691504822301
プロパティ
class Bmi { // --フィールド値 // アクセス修飾子をprivateにして、クラス外からのアクセスを禁止する private double height = 0; private double weight = 0; // -- Weightプロパティ public double Weight { set { if (value <= 0) { Console.WriteLine("Error : invalid value"); } else { this.height = value; Console.WriteLine($"set height is {height} cm"); } } get { return this.height; } } public double Height { set { if (value <= 0) { Console.WriteLine("Error : invalid value"); } else { this.weight = value; Console.WriteLine($"set weight is {weight} kg"); } } get { return this.weight; } } // -- BMIを計算するメソッド public double CalcBmi() { return Weight / ((Height / 100) * (Height / 100)); } } class Program { static void Main(string[] args) { Bmi bmiInst = new Bmi(); Console.Write("enter height (cm) :"); double height = double.Parse(Console.ReadLine()); Console.Write("enter weight (kg) :"); double weight = double.Parse(Console.ReadLine()); // 下記のようにメンバーを直接アクセスすることができない。 // bmiInst.weght = 64; // アクセサーメソッドを用いてメンバー値の変更を行う。 bmiInst.Height = height; bmiInst.Weight = weight; Console.WriteLine($"BMI is {bmiInst.CalcBmi()}"); } }
heiht:174、weight:65 を入力したときの結果を次に示す。 結果を次に示す。
enter height (cm) :174 enter weight (kg) :65 set weight is 174 kg set height is 65 cm BMI is 21.4691504822301
自動プロパティ
自動プロパティのコードを次に示す。
class CallProperty { // 自動プロパティ public int Type { set; get; } = 10; } class Program { static void Main(string[] args) { CallProperty call = new CallProperty(); Console.WriteLine(call.Type); call.Type = 100; Console.WriteLine(call.Type); } }
結果を次に示す。
10 100
参考
クラスとメソッド
はじめに
最近、C#を用いてオブジェクト指向プログラミングの学習をしている。 クラス、カプセル化、継承、ポリモーフィズムなど、オブジェクト指向を知らない私でもよく耳にする。そろそろオブジェクト指向がどのようなものか理解しようと思い、このブログに学習した内容をまとめた。C#を選んだ理由は、今後Windowsアプリを作ってみたいなと思ったからと、色んな参考書を見た中でC#が分かりやすそうだったからだ。
目次
クラスとメソッド
クラス
クラスの意味や使い方をまとめた。
理解編
クラスとインスタンス
クラス はデータとデータ処理を1つのブロックとして定義したものである。データ部分を データメンバー 、データ処理を 関数メンバー と呼び、データやデータ処理をまとめて メンバー と呼ぶ。それぞれのクラスには、関連性の強いデータとデータ処理がまとめられている。
クラスで直接宣言される変数があり、その変数をフィールド と呼ぶ。フィールドは、メンバー変数 とも呼ばれている。
クラスを定義する場合は、class
を使用してブロック内でメンバーを定義する。クラスを使用するには クラスの宣言 と クラスのインスタンス化(メモリ領域の確保) が必要である。
クラスの宣言はクラス名に続けて変数名を指定すればよい。
クラス名 変数名
クラスのインスタンス化はnew演算子
を使用し、new演算子の後にクラス名とパラメータを指定すればよい。
変数名 new クラス名();
クラスは参照型なのでクラスを宣言のみだと、参照先のアドレスを確保するだけらしい(変数にnullが入る)。new演算子は、クラスのデータを置くためのメモリ領域を確保する役割を担う。 インスタンス化によって生成されたものを、クラスの実体 または オブジェクト と呼ぶ。
クラスの宣言とクラスのインスタンス化は一緒に行うこともできる。
クラス名 変数名 = new クラス名();
クラス内のデータメンバーにアクセスする場合は、ドット演算子(.)を使用すればいよい。
インスタンス名.メンバー名
実践編
クラスとインスタンス
クラスの概念については理解したので、次は実際にコードを書いてみる。
// クラスの定義 class Car { // field string name; int seats = 4; } class MainClass { static void Main() { //----クラスのインスタンス化----//- // クラスの宣言 Car mycar; // new演算子を使用して、クラス「Car」を呼び出す。 mycar = new Car(); // クラスの宣言と呼び出しをまとめて書く場合 //Car mycar = new Car(); // ドット(.)演算子を使用して、mycarクラスのメンバーにアクセスする。 mycar.name = "マイカー"; Console.WriteLine(mycar.name); Console.WriteLine(mycar.seats); } }
この出力結果は、次となる。
マイカー 4
メソッド
メソッドはC言語でいうところの関数みたいなものらしい。そのメソッドについて調べたことをまとめた。
理解編
メソッド は関数メンバーの一部であり、データ処理を行うものである。メソッドは次のように定義することで使用できる。
アクセス修飾子 戻り値の型 メソッド名( 引数 ) { 処理内容 }
メソッドの呼び出しは、インスタンス名を指定して呼び出す方法 (インスタンスメソッド) と クラス名を指定して呼び出す方法(クラスメソッド) がある。
インスタンスメソッド
インスタンス名を指定して呼び出す方法において、クラスのインスタンスを生成しないと呼び出せないメソッドのことを インスタンスメソッド と呼ぶ。
クラスメソッド(静的メソッド)
一方、クラス名を指定して呼び出すメソッドのことを、クラスメソッド 、または 静的メソッド と呼ばれる。クラスメソッドは、アクセス修飾子の前にstatic
を付ければよい。
また、クラスメソッドの呼び出しは、インスタンスを生成せずに呼び出し可能で、クラス名.メソッド名(引数)
とすればよい。ただし、クラスメソッドはインスタンスを指定して呼び出す(インスタンス名.クラスメソッド)ことができない。
インスタンスメソッドとクラスメソッドの違いは分かったが、設計時の使い分けはどうすればよいのだろうか? オブジェクト指向の概念やメリットを理解していないから使い分けができていないのだろう。
静的クラス
クラスメソッドつながりで、静的クラスについても記載する。 静的クラスは、インスタンス生成せずにクラス内のメンバーやメソッドにアクセスできるものである。静的クラスを使用する場合は、classの前にstatic
を付ければよい。静的クラスは、インスタンスを生成せずにアクセスできるデータや処理を作成するときに使用するらしい。
コンストラクタ
インスタンス生成時に呼び出される特殊なメソッドがある。この特殊メソッドを コンストラクタ と呼ぶ。コンストラクタは、インスタンス生成時に呼びされることから、フィールドの初期化 や クラス内部での初期化処理 などで利用される。
コンストラクタを使用する場合は、戻り値を持たすことができない 、 アクセス修飾子が制限されている (通常はPublicを使用するらしい)いう制約がある。また、初期化が不要な場合コンストラクタの省略が可能である。 コンストラクタが複数ある場合、 引数によって自動的に呼び出すコンストラクタが選択 される。引数のないコンストラクタのことを 既定のコンストラクタ という。
デストラクターと呼ばれるコンストラクタと対になる機能がある。 デストラクター はインスタンスが消滅する時に呼び出される特殊なメソッドである。ただし、あまり使用しないらしいので、現時点では言葉の意味を知る程度でとどめておく。
実践編
インスタンスメソッド
インスタンスメソッドを使用したコードを次に記載する。
class MainClass { // メソッドの生成 public void Printf() { Console.WriteLine("Hello world !!"); } static void Main() { //MainClassのインスタンス生成 MainClass mC = new MainClass(); //クラスメソッドの呼び出し mC.Printf(); } }
この出力結果は、次となる。
Hello world !!
クラスメソッド
クラスメソッドを使用したコードを次に記載する。
class MainClass { // メソッドの生成 static public void Printf() { Console.WriteLine("Hello world !!"); } static void Main() { MainClass.Printf(); } }
この出力結果は、次となる。
Hello world !!
静的クラス
静的クラスを使用したコードを次に記載する。
static class ClassStatic { // 静的メンバー public static int a = 5; public static void Display() { Console.WriteLine(a); } } class Program { static void Main(string[] args) { // インスタンス生成せずにクラスのメソッドを使用する ClassStatic.Display(); } }
この出力結果は、次となる。
5
コンストラクタ
コンストラクタを使用したコードを次に記載する。
class Car { // フィールド public int seats; public string name; // 1つ目のコンストラクタ public Car() { seats = 4; } // 2つ目のコンストラクタ public Car(string str) { name = str; } } class Program { static void Main(string[] args) { //インスタンス生成するときの引数で、呼び出すコンストラクタが自動的に選択される。 // 引数なしでインスタンス生成した場合、1つ目のコンストラクタが呼び出される。 Car instCar = new Car(); Console.WriteLine("seats: " + instCar.seats); Console.WriteLine("name : " + instCar.name); // 引数ありでインスタンス生成した場合、2つ目のコンストラクタが呼び出される。 Car instCar2 = new Car("MYCAR"); Console.WriteLine("seats: " + instCar2.seats); Console.WriteLine("name : " + instCar2.name); } }
この出力結果は、次となる。
seats: 4 name : seats: 0 name : MYCAR
参考
Zybo割り込みについて
ブログを書くのは久しぶりである。今回も2月に掲げた目標とは別の内容を取り上げる。 その内容とはZyboの割り込み発生方法についてである。今回はPLから割り込み信号を発生させ、簡単なPSで割り込み処理を行う方法について記載する。
目次
使用環境
Zyboの割り込みについて
ZyboのFPGAには、汎用割り込みコントローラ(GIC)と呼ばれるものが組み込まれている。GICはPS/PL - CPU 間の割り込みを管理するものである。PS/PL - CPU 間の割り込みは、いくつかの種類が存在する。
今回やりたいことは、PLから割り込み信号を発生させて簡単なPSで割り込み処理を行うことである。その場合は共有ペリフェラル割り込み(SPI)を使用する。SPIはI/OペリフェラルやPLからの割り込み信号をGICへ送信するモジュールである。
下の図にブロック図を示す。ブロック図の赤枠がSPIの部分を示しており、PLからSPIへ送信する信号数は計16本である。
計16本あるPLからSPIへ送信する割り込み信号には、割り込み要求番号(IRQ ID#)が割り当てられている。割り込み信号とIRQ ID の関係を下の表にまとめた。今回は下記の表のうち、割り込み要求番号 #61を使用して割り込みの動作確認を行う。 ちなみに、PS部のI/OペリフェラルからSPIへ送信する信号数は計44本ある。
信号名 | IRQ ID# | トリガタイプ | vivado上の信号名 |
---|---|---|---|
PL[7:0] | 68:61 | 立ち上がりエッジ/Highレベル | IRQF2P[ 7:0] |
PL[15:8] | 91:84 | 立ち上がりエッジ/Highレベル | IRQF2P[15:8] |
動作確認用の構成
割り込み信号の発生と割り込み処理の動作確認するための構成について説明する。構成図を下に示す。割り込み信号はPL側に配置したプッシュボタンを押すことで発生させる。そして、割り込み処理は割り込み処理関数を作成し、コンソール画面上に文字を出力することで動作確認する。
これら手続きはxilinxが用意したAPIを使用することで、簡単に済ますことができる。今回の動作確認ではこのAPIを使用する。
動作確認
PL設定
FPGAの構成図を以下に示す。上記にて記載した通り、プッシュボタンを押すことで割り込み信号を発生させ、CPUに送信したい。割り込み信号をCPUに送信するには、Vivado IPインテグレータ上で下記の2つを実施する必要がある。
- zynq IPコアの割り込み有効化
- 割り込み信号の接続
zynq IPコアの割り込み有効化
zynq IPコアの割り込み有効化は、ZynqIPの設定画面から行う。ZynqIPの初期設定では、PL割り込みは無効となっている。ZynqIPをダブルクリックすると、Re-Customize IPというウインドウが表示させる。その画面の左側の項目から、Interruptsを選択する。Interruptsを選択すると、Interrupt Portの下にいくつかの設定項目が表示される。そのうち、IRQ_F2P[15:0]のチェックボックスを選択する。選択したときの画面を下に示す。
画面右下のOKボタンを押すと、Zynq IPにIRQ_F2Pという入力ポートが表示される。これでzynq IPコアの割り込み有効化は完了である。
割り込み信号の接続
次にZynqIPとプッシュボタンの信号を接続する。Zyboの基板上にはプッシュボタンが4つある。プッシュボタンの信号名をBTN [3:0]
とし、これらをConCat IPを経由してZynqIPのIRQ_F2Pポートに接続する。ConCat IPは信号名の異なるバスを一つのバスにまとめる役割を持つ。今回の例だとConCat IPを使用するメリットはないが、とりあえず使用した。
今回の場合、割り込み要求番号とプッシュボタンの関係は下記表のようになる。
信号名 | IRQ ID# |
---|---|
BTN[0] | 61 |
BTN[1] | 62 |
BTN[2] | 63 |
BTN[3] | 64 |
上記2つが終わったら、bitファイルを生成する。bitファイルが生成後、Xilinx SDKを起動させる。PS設定に進む。
PS設定
CPUが割り込み処理を行うためには、GICの初期化やPL割り込みの有効化、割り込みハンドラの登録、使用する割り込みIDの有効化といくつかの手続きが必要となる。これら手続きを以下のフロー図に示す。書くほどの内容はないが・・・
この手続きはXilinxが提供しているAPIを使用することで簡単に済ますことができる。なお、Xilinx APIのドキュメントやサンプルコードは以下から入手できる。
サンプルコードは、下記のように構造体に変数をまとめていたり、インスタンス生成するなど、オブジェクト指向設計っぽい記述となっている。オブジェクト指向設計、というよりソフトについては全般的に詳しくないので違っているかもしれないが。。。
作成したコード全文を以下に示す。これを実行すると、プッシュボタンを押すたびにコンソール画面上にgenerate interrupt!!
と表示される。
/******************************************************************************/ #include "xil_cache.h" #include "xil_exception.h" #include "xil_io.h" #include "xil_printf.h" #include "xil_types.h" #include "xparameters.h" #include "xscugic.h" #include <stdint.h> #include <stdio.h> #include <stdlib.h> /************************** Constant Definitions *****************************/ #define INTC_DEVICE_ID XPAR_SCUGIC_0_DEVICE_ID #define PRIORITY 0xF // zynq7000 SPI fpga's interrupt IDs static const uint32_t fpgaId[] = {61, 62, 63, 64, 65, 66, 67, 68, 84, 85, 86, 87, 88, 89, 90, 91}; typedef enum { LEVEL_HIGH =0x1, EDGE_RISING =0x3 } TriggerType; /************************** Variable Definitions *****************************/ // 構造体の宣言 XScuGic InterruptController; /* Instance of the Interrupt Controller */ // 構造体ポインタの宣言 static XScuGic_Config* GicConfig; /* The configuration parameters of the controller */ /******************************************************************************/ /** * @param CallbackRef is passed back to the device driver's interrupt * @return None. * @note None. * ****************************************************************************/ void IRQHandler(void* CallbackRef) { /* * Indicate the interrupt has been processed using a shared variable */ printf("generate interrupt!! \n"); } /******************************************************************************/ /** * * This function connects the interrupt handler of the interrupt controller to * the processor. This function is seperate to allow it to be customized for * each application. Each processor or RTOS may require unique processing to * connect the interrupt handler. * * @param XScuGicInstancePtr is the instance of the interrupt controller * that needs to be worked on. * * @return None. * * @note None. * ****************************************************************************/ int SetUpInterruptSystem(XScuGic* XScuGicInstancePtr) { /* * Connect the interrupt controller interrupt handler to the hardware * interrupt handling logic in the ARM processor. */ Xil_ExceptionRegisterHandler( XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, XScuGicInstancePtr); /* * Enable interrupts in the ARM */ Xil_ExceptionEnable(); return XST_SUCCESS; } /******************************************************************************/ /** * @param * @return None. * @note None. * ****************************************************************************/ int SetupGIC(uint32_t usingID, uint32_t* fpgaId) { int32_t status; uint32_t maxId = sizeof(fpgaId) / sizeof(fpgaId[0]); ///! GICの初期化のためにメモリ領域を確保する GicConfig = XScuGic_LookupConfig(INTC_DEVICE_ID); if (NULL == GicConfig) { xil_printf("ERROR : device does not exist \n"); return XST_FAILURE; } ///! GICを初期化する status = XScuGic_CfgInitialize(&InterruptController, GicConfig, GicConfig->CpuBaseAddress); if (status != XST_SUCCESS) { xil_printf("ERROR : inizialization failed \n"); return XST_FAILURE; } ///! 割り込みの種類と優先度を設定する for (uint32_t i = 0; i < maxId; i++) { XScuGic_SetPriorityTriggerType(&InterruptController, fpgaId[i], PRIORITY , EDGE_RISING); } ///! GICとハードウェアを接続する for (uint32_t i = 0; i < usingID; i++) { status = XScuGic_Connect(&InterruptController, fpgaId[i], (Xil_ExceptionHandler)IRQHandler, (void*)&InterruptController); if (status != XST_SUCCESS) { xil_printf("ERROR \n"); return XST_FAILURE; } } ///! GICを有効化する for (uint32_t i = 0; i < usingID; i++) { XScuGic_Enable(&InterruptController, fpgaId[i]); printf("setup interrupt \n"); } ///! GICとARMコアを接続する SetUpInterruptSystem(&InterruptController); } /*****************************************************************************/ /** * This is the main function for the Interrupt Controller example. * @param None. * @return XST_SUCCESS to indicate success, otherwise XST_FAILURE. * @note None. * ****************************************************************************/ int main() { init_platform(); // get fpga interrupt ID uint32_t idNumber = sizeof(fpgaId) / sizeof(fpgaId[0]); printf("ID Number %d \n", idNumber); SetupGIC(1, &fpgaId); printf("waiting interrupt \n"); printf("\n---------------------------------\n"); while (1) { } cleanup_platform(); return 0; }
まとめ
割り込み処理の動作確認を行うことができた。次は、割り込みを使った別のプログラムを作成する。また、今回の記事は後半に駆け足になり、PS部の説明が薄い内容になってしまった。もう少し記事を各モチベーションを維持できるようにしたい。