ラベル Break time の投稿を表示しています。 すべての投稿を表示
ラベル Break time の投稿を表示しています。 すべての投稿を表示

2017年5月31日水曜日

ソフトウェア開発のマネージメントは航空会社の運営:課題管理における「バージョンはフライトで、チケットは乗客だ」という私のバージョンに対する考え方について

あらまし

自分たちのソフトウェア出荷物に特定の名前を付ける場合に、バージョン番号を付けることは一般に広く行われています。「あのバージョンではXXX機能が加わった」とか「このバージョンではYYY機能が加わった」とか「そのバージョンではZZZ機能が壊れている」とか、その類の話です。今日は、私のバージョンに対する考え方について述べたいと思います。

前提とか状況とか

ソフトウェア出荷物を得なければならないプロジェクトで私が用いている前提は概ね以下のとおりです。

  • 人間が計画するものは必ず遅れを呼ぶ。
  • 将来の予期は難しい。
  • 特定のバージョンの出荷を遅らせても、劇的な状況改善に至ることはない。
ソフトウェアを出荷するということは、それまで内部で進めてきた活動が外部に現れ、それが何らかの価値を産むということです。当然ながら出荷しない限り価値は産みません。なので、企業を経営する立場からすると、ソフトウェアを早く出荷したい気持ちになります。

その対岸にいるかもしれないソフトウェア開発者から見ると、特定機能や要求への対応など複数の未解決項目があり、それらの状況は出荷に値しないと見ているかもしれません。ですから、出来るだけ出荷を延期したいかもしれません。

ソフトウェア開発のマネージメントは航空会社の運営

唐突ですが、私はソフトウェア開発のマネージメントは航空会社の運営に似ているなと常々考えていました。


例えば、あなたが上の図に示したVersion 1.0.0に向かって開発を進めているプロジェクトの開発者だったとしましょう。あなたは「DEF機能の追加」が全く間に合わず、ついつい「これはVersion 1.0.0の出荷を見合わせよう」と考え始めます。こうなるとそれに続くVersion 1.5.0もVersion 2.0.0も後ろにずれ込んでいくことになるかもしれません。

こうなると全てが後ろにずれ込んで、計画も何も無いような気持ちになってしまいますし、実際に外から見るとそんな風にダラダラ進んでいるように見えてしまうようです。

でも、出荷はフライトと同じだと少しだけ考え方を変えてみます。例えば、300人が搭乗する航空機の出発の際に、特定の乗客が乗り遅れようとしているからといって、3時間も6時間も出発が遅れることはありません。そうすると、全体の利益が損なわれてしまうからです。

これはちょうどソフトウェア開発における出荷の状況にも言えます。ある150個の仕事をこなした後に出荷すると決めたバージョンに対して、特定のある1つの仕事が間に合わなかったからといって、たとえそれがどんなに重要な仕事だったとしても、他の149個の仕事の成果を巻き添えにして出荷を(フライトに例えると出発を)遅らせるのは、もしかしたら行き過ぎかもしれません。

このバージョン(フライト)に搭載するはずだったチケット(=解決済みの課題)は、次のバージョン以降に延期されたけど、確実にプロジェクトは進んでいるよね?

こんな風に考えると、気持ちも少し楽になってきます。確かに約束した重要な機能は約束したバージョンに搭載されなかったかもしれませんが、それは次のバージョンで確実に搭載されるように配慮します。既に他の149個の仕事は完了しているのですから、搭載の遅れた1個の仕事に注力すれば、次に行うべき他の仕事よりも完了する確率は高いでしょう。

最後に

ソフトウェア開発の場合、とても乱暴に言って遅れることは問題ではありません。それよりもむしろ、進んでいないように見える、あるいは進んでいないことが一番の問題です。ソフトウェア開発のマネージメントに際して、事前に計画した内容の通りに進まないといけないといった厳しい外部制約を与えると、局所的には最適化されるのかもしれませんが、全体の動きは鈍くなる可能性があるので、私は航空会社のように全体の利益に基づいて柔軟に計画を変更することは重要だと考えるようになりました。

2017年4月30日日曜日

今すぐ出来る!HTML5なウェブサイト実現ワークショップ「#HelloHTML5」を開催します。

はじめに

近年、ウェブサイトのHTML5化が盛んに行われています。 様々なデバイスがウェブに接続されるようになり、端末の画面サイズに応じた表現なども必須になってきました。 このセミナーでは、「今すぐ出来る!HTML5なウェブサイト実現セミナー」と題して、HTML5を活用したウェブサイトの実現をワークショップ形式でお届けします。

お勧めする人

  • ウェブサーバーを立ち上げたけど、魅力的なコンテンツの実現に技術的な課題を感じている方。
  • サイトをHTML5化したいけど、どこから手を付けてよいかわからない方。
  • 組み込みシステムのエンジニアリングが専門で、ウェブ技術の調査までなかなか手が回らない方。
  • その他、とにかく現状のウェブが不満でどうにかしたい方。
  • そもそも億劫でなかなかウェブの実現が進まない方。

参加登録

2016年11月30日水曜日

リアルタイムOS教材について思う事(をちょっとだけ書いてみる)

■あらまし

学生の頃からリアルタイム・システムに興味があって様々な書籍を読んでいた自分ですが、エンジニア・ライフを約一周ほど楽しんで、そろそろ思うことあってリアルタイムOS教材についても触れなければならないなと考え始めました。というのも、世の中に溢れる教材の中には、初学者に与えるべきでない間違った例が数あまたあり、それらが実開発の現場で様々な問題を生んでいるからです。今日はその中からひとつだけ取り上げてショート・ブレイクとして書いてみます。

■良くない例:タスク・スリープで排他処理

世の中には不思議な例を取り上げてリアルタイムOSの機能を紹介する例を見かけます。その代表例がタスク・スリープで排他処理やシステムの状態を管理する例です。

この例を取り上げる教材の多くは、リアルタイムOSの機能を紹介したいようでもあるのですが、よくよく読んでみると、結局のところどれも「こういうときはこうするのだ」と実際のアプリケーションについて触れています。しかし、このような設計で実システムを実現されてはひとたまりもありません。

リアルタイムOSの使い方として間違ったアイデア、「タスク・スリープで排他処理やシステムの状態を管理」が何を言っているのか図示してみます。


タスクAとタスクBは、それぞれグローバル変数であるint valueを操作します。もうグローバル変数が出てくる時点で完全に失格なのですが、問題はそこではありません。この典型的な間違ったアイデアは、よく以下のような方法で紹介されています。

①システム起動直後、タスクAは動作し、タスクBは寝ています。
②タスクAはint valueを操作し、タスクBを起こして自分は寝ます。
③起床したタスクBはint valueを操作後、タスクAを起こして自分は寝ます。

例えば、この設計には以下の疑問がつきまといます。

A. タスクAとタスクBが非同期で双方動作している瞬間について考慮されていない。
B. タスクAがタスクBを知っている。タスクBがタスクAを知っている。つまり循環参照関係にある。
C. やっている内容から考えると、そもそも単一タスクで良い。(説明に必然性が全く無い)
D. その他。

上の例、int valueと書いてあるものは物理デバイスであることもあります。となると、なおさら問題は複雑になります。というのも、物理デバイスは動作に時間がかかります。状態遷移中の物理デバイスの状態を適切に扱う場合、上記の例では対処できません。

■例えばどうすれば良いのか?

リアルタイムOSを使うのは、抽象化レベルを上げつつ、キビキビとした動作を実現できるからです。上記の例で言うと、int value(物理デバイスかもしれない)は、操作対象ですが、これはあるタスク内部で操作される操作対象と見ることができます。つまり、タスクAやタスクBから操作される新たなタスクCのようなものが内部で操作する対象とすることができます。


そして、タスクAやタスクBからメッセージ通信でタスクCに操作を依頼する形式を取ります。
「ちょっと待って!さっきの例で出来ていたタスクAとタスクBの同期ができないじゃない!」と言われるかもしれませんが、タスクCは単一スレッド上でメッセージ受信処理を行っているので操作は競合しません。

加えて、タスクCのAPIを工夫しておけば、操作自体も抽象化された表現で扱うことが可能になります。
  • アームを上に上げろ!
  • アームを下に下げろ!
  • 緊急停止!
上記のような操作を抽象的に表現したAPIにするだけで、グンとシステムで操作する内容がわかりやすくなってきます。そして、実装の詳細はタスクCに隠ぺいされるというメリットも生まれます。タスクAとタスクBが循環参照状態になる事もありません。

■ということで・・・

リアルタイムOSの教材でタスクのスリープを使って状態をコントロールするような例を見かけたら、「この教材は怪しいな」と疑って内容をレビューしてみて下さい。

2016年9月30日金曜日

Artistic Style (astyle) でコードを整形する

■インデントって重要

最近、色々な人のコードを眺めるにつけ、インデントまできちんと目を配って実装している人とそうでない人との間に、とてつもなく大きな壁がある事に気付きました。前者、インデントまできちんと目を配って実装している人の多くは、不要な実装、余計な実装は一切無く、終始一貫した表現で実装されています。対して、後者のインデントに気を使わない人の多くは、未使用変数や未使用関数、全く意味のないコメントやデバッグ文の散乱、ハードタブとソフトタブの同居・・・その他たくさんの危険な香りのする実装が残されています。ここ数年で見ていく中でインデントと成果物の内容に相関性があるのでは?という疑問に至り、インデントをきちんと行う姿勢に変えるだけでもその人のアウトプットが改善されるようにすら思えてきました。(今は単にそう思っているだけ)

■最近はArtistic Styleなの?

私の場合、ソースコードは常にvimで編集しています。autoindentは常にonですし、整形する際も組み込まれた整形機能をパパッと充てて済ませていました。ふとしたことから「最近では皆さんどうしているのかな?」と疑問に至り調べてみたところ、Artistic Style (astyle)というツールが割と最近では使われているようです。メジャーなLinuxディストリビューションではパッケージ管理システムから簡単にインストールできますし、Windows版はsourceforge.netのプロジェクトページから入手可能なようです。


■"One True Brace Style"が使える!

Artistic Styleでは、様々なコーディングスタイルを実現できるように豊富なオプションが用意されています。自分たちのコーディングスタイルに合わせてオプションを加えて実行できるのは嬉しい点です。"One True Brace Style"は、条件分岐などで一行で完結する命令の場合でも波括弧を付けて実装するスタイルのひとつで、私が好んで使用しているスタイルのひとつです。Artistic Styleでは、「--style=otbs」という引数を与えるだけで、One True Brace Styleになっていないコードでも自動的に波括弧を加えて整形してくれます。他にも様々な代表的なコーディングスタイルに対応しているので、きっと自分の気に入ったスタイリングオプションを見つける事ができるでしょう。



2016年7月31日日曜日

技術開発と製品開発とビジネス開発は全く別物だけど混同されていることが多いという話

あらまし

お仕事で様々な立場の方とお話しする機会があるわけですが、以前からどの階層にいる人にも共通した「技術開発と製品開発とビジネス開発が同時に出来るのではないかという思い込み」を持っている方が非常に多い事に気付きました。



よくある混同で起きるマズい事

例えば、おぼろげながら自分達で考えている製品像が浮かんだとしましょう。この製品像は、それこそ具体的になっているものではありませんが、必要な機能や性能やインターフェースが何となく整理できた状態になっていたとします。そこで必要になる機能を実現するための技術的な要素は、少し頑張れば実現できそうな事ばかりで、さっそく製品開発をスタートする事にしてしまいます。

このシチュエーションで起きるのは以下です。
  1. 製品の機能を仕様にまとめる
  2. 仕様が好ましいものと判断され/判断し、計画をスタートさせる
  3. 仕様を満たす機能を実現できるように設計を開始する
  4. 設計が完了したので実装を始める(ハードウェア、ファームウェア、ソフトウェアは問わない)
  5. 実装が完了したので製造する(ハードウェア、ファームウェア、ソフトウェアは問わない)
  6. 製造が完了したので評価する
  7. (以降、付随したプロセスに続く・・・)
何もマズい事が起きていないようにも見えます。が、実際に技術開発と製品開発とビジネス開発を混同している人達に起きる悲劇の一例を挙げます。
  • 出来ると思っていた技術的な要素に意外な課題があった
  • 技術的な要素に絡む問題のために実現すべき仕様の一部が実現出来なくなった
  • 仕様の一部が無くなったが、市場に出したい時期を明らかにしているので出す事にした
  • 実現できなかったのはエンジニアリング能力の低さだと誰もが落胆し真因にたどり着かない
混同によって、どれもこれもが全て中途半端になってしまうのが最大の欠点です。こういった結末に至るプロジェクトは無数にあり、共通に存在するのが技術開発、製品開発、ビジネス開発を混同してしまっている事にあります。

技術開発

当たり前の話ですが、製品を実現するために必要な技術要素について、事前に課題や実現性を吟味しておく必要があります。そうでなければ製品の実現性すら不明のままで製品開発をスタートさせる事になってしまいます。このような組織にいたっては、製品開発を行う時点でビジネスまで混同して考えてしまっているので、全部が不透明感で一杯になる事になります。

技術開発は、製品開発とは性格の異なるもので、将来何に役立つのかわからないが、魅力的な物事について事前に研究開発しておくものです。大抵の場合、これらは現場の技術者の個人的な興味に由来していたり、過去に失敗した製品の残骸からゾンビのように生き返って組織に残っているようなものだったりします。

そして、非常に重要な点ですが、これらは一般に組織的に必要性を認められることは少なく、たいていの場合アンダーグラウンドで活動する事すら認められないようなものだったりします。(そんな事をやっている暇があるのなら、別の事をやれというやつです。)

もちろん、ニーズを的確に察知して必要になりそうな技術要素について研究開発する事も多いのですが、実際にそれらはビジネスへの発展を事前に考慮しているので、思わぬ発展に至るケースは少ないように思います。

いずれにせよ、重要な点はその時点で何の役に立つのかわからない、けれども興味のある物事を実現できるようにしておくというのが技術開発です。

製品開発

製品開発は、商品やサービスを実際に市場に出すために行う活動で、通常はビジネス開発との関係も持ちながら行われます。売れないものを作っても何もならないので、売れる前提でモノを作る事になります。つまり、実現性が非常に重要な点で、この時に初めて技術開発の成果物が価値を持ってきます。

製品開発の段階では、明らかになった技術要素を用いる事ができるようになっていると失敗のリスクを低減できます。

最近は様々な物事が安易に進む傾向があるようで、お金を積めば実現できると思っている方も多いのですが、実際に製品開発をするならば事前に積み重ねられた技術開発が伴っていなければなりません。そうでなければ博打にもなりかねません。動くと思うけどどうかなー。なんて嫌ですよね。

ビジネス開発

ビジネス開発では製品やサービスだけでなく、市場の動向や人々の趣味嗜好、その他さまざまな要素を踏まえて見えるものや見えないものまで作っていきます。

重要な点ですが、技術開発はそれ自身が当初品質が低く、実現形態も最終品ではないので曖昧で、結果的にビジネス開発側では具体的な製品のイメージを持ちにくい性質のものです。ビジネス開発側に自分が位置している場合には、目の前で実現できているモノが、技術開発の成果物なのか、製品開発の成果物なのか、十分に理解した上でモノを見なければなりません。その点を見誤ると、将来の大事な要素を自ら見捨ててしまう結果になってしまいます。

まとめじゃないまとめ

沢山の失敗するプロジェクトと少しの成功するプロジェクトを眺めていると、ある一定の共通項が見えてきました。その結論の一つが上記の、技術開発と製品開発とビジネス開発を混同しない事。です。

技術開発と製品開発を混同している組織は、製品開発で必ず失敗しています。そして、その失敗の原因を現場の担当に押し付けてしまいます。しかし、実際に組織で起きているのは、単に十把一絡げに物事を混同してしまう思想の雑さ加減にあるのです。もちろん、製品開発とビジネス開発も同様で、開発担当現場に一緒にビジネスまで開発させようとする組織すら存在しますが、エンジニアはスーパーマンではないので、小さなビジネスでまとめるなら別ですが、通常はうまくいかないでしょう。

モノを作るのが大好きな日本人ですが、世界中で見ると創造性や生産性は高くないのが現状です。そして、うまくいかなくても分析すらままなりません。結局は「社員の能力」などと言い放つ方すらいるのですが、それでは問題が解決しないようなぁと考える今日この頃なのです。

2015年10月31日土曜日

システムにおけるレイテンシーとスループットの話題

レイテンシーとは、あるシステムに何らかの入力を与え、それに対応する何らかの出力が出てくるまでの時間(遅延量)を指します。例えば、ある処理の実行を指令してから実際の結果が得られるまでに250ミリ秒かかったとすると、それは250ミリ秒のレイテンシーを持つシステムという事になります。それではここで、この時のスループットについて考えてみましょう。

以下の図は、横軸が時間で、250ミリ秒かけてある処理を4回実行した時の様子を示したものです。


上記の図に示した「ある処理」で取り扱われるデータが32MBだったと仮定します。このシステムの場合、「ある処理」を直列に実行しており、スループット[MB/s]は(32[MB] x 4) / 1[s] = 128[MB/s]となります。

さて、ハードウェアであってもソフトウェアであっても、最初に設計実装した処理のスループットが想定を下回り、結果的に何らかの対策が必要になる場合があります。必要なスループットに達していない場合、所望の処理を並列に実行する事でスループットを改善しますが、これは一体どのようなメカニズムによるものなのでしょうか?・・・というのが今日のお話。

さっそく先ほど1系統で行っていた処理を複数系統に拡張する事を考えてみます。下の図は、先ほどの処理を5並列で実行するようにしたものです。

上記の場合、スループット[MB/s] = ((32[MB] x 4 x 1) + (32[MB] x 3 x 4)) / 1[s] = 512[MB/s]となり、先ほどの128[MB/s]に対して大幅にスループットが向上している事がわかります。

直列的に処理する事になれた人が時間軸だけに注目してこの例を考えた場合、「250ミリ秒かかる処理なのに、どうして50ミリ秒毎に結果が出せるの?」と不思議に思うかもしれませんが、図示してみれば特別驚くべき事もありません。

このように、同じ処理を実行可能な系統を複数用意して並列動作させるだけで、システム全体のスループットを大幅に向上可能である事がわかります。上記の方法で重要な点のひとつは、系統毎の処理方法と処理時間は、系統を並列にする前の直列動作時と何ら変わりが無いという点です。

ここで、その他の方法として、単一の「ある処理」を複数に分割して、複数の系統で同時に並列処理する事も考えてみましょう。下記は、緑の部分は5つに分割して処理した結果、時間軸上では1/5の時間で済むことを示しています。


上記の場合、250ミリ秒かかる「ある処理」を5分割にして50ミリ秒ほどで完了する細かな処理に分割し、それら分割した処理を複数の系統で同時に処理する事で、レイテンシー50ミリ秒、計算系統5系統で「ある処理」を50ミリ秒で片づける事に設計する事になります。但し、並列化する前と後では、処理の実装が異なる他、並列化に要する新たなオーバーヘッドが発生する事があります。



システムにおけるレイテンシーとスループットを考える場合、何を並列化するのか、どのように並列化するのか、その並列化においてのスケーラビリティはどのようなものか、など様々な視点で設計について考察しなければなりません。

この話の続きには、同期と非同期や多段接続に関する話題、バッファリングに関する考察が必要ですが、それはまたの機会にと思います。

2014年1月26日日曜日

ビデオやオーディオのDMAバッファ管理手法(の続編の続編)

2012年の投稿「ビデオやオーディオのDMAバッファの管理手法 (動かして遊べるソースコード付き!)」や、2013年の投稿「DMAバッファ管理手法の続編」で触れたDMAバッファの管理手法は、以下の図に示すような簡単な概念を使って、実入出力処理を抽象化する手法について説明したものでした。


DMAを使用した転送を行なう場合、当たり前ですがDMAエンジンに対して転送指令を行なう必要があります。先の実装は抽象化した処理モデルに対する実装のみを提示したもので、実際にどこでDMAの設定を施して良いのかについては特に明示していませんでした。また、処理対象バッファの実装のヒントも示しておらず、実際の実装との距離を感じた方もいるかもしれません。

そこで、今回はframe.hにバッファの追加場所を、main.cにはDMAの設定箇所を@todoで記載したものを今回御紹介します。

無保証です。使用した結果の責任は一切負いません。

DMAの設定後、完了するまでそのバッファの内容に触れてはいけませんが、これは抽象化した処理モデルによって保証されています。実デバイスの処理に依存する箇所は、主にバッファとDMAに対する設定のみですので、実際には抽象化されたコードから実装までの距離は殆どありません。実際に私もこのモデルを使用してDSP上にオーディオ処理アプリケーションを書いていますが、まさに今回の実装をそのまま使用して遊んでいます。

今回の設計は特定のチャネルのみを想定していますが、この一連のモデルを拡張するだけで複数のチャネルを対象に処理する事ができるようになります。以下のように入出力チャネル数に応じて入力遅延器、出力キューを増やせばよい事になります。


冒頭の概念図に示したように、システム側で準備すべきバッファは、チャネル数に依存しない一連のバッファが一つあれば済むのもポイント。上位層のコードは、抽象化によって処理内容が一目でわかるようになるほか、将来の変更に対する柔軟性が高まりますので、遅延器やキューに対する処理によるオーバーヘッドを許容できるシステムにおいては、十分に検討のある方法です。

こんな感じで、2014年のCuBeatSystemsのブログは、ビデオやオーディオのDMAバッファ管理手法は続編の続編の続編から始まる事になりました。

今年もよろしくお願い申し上げます。

2013年3月31日日曜日

DMAバッファ管理手法の続編

シンプルな制御モデル

昨年10月の投稿「ビデオやオーディオのDMAバッファの管理手法 (動かして遊べるソースコード付き!)」では、ビデオやオーディオなどを扱うファームウェアで、DMAバッファをどのように扱うと良いのかについて述べました。

先の投稿で上げた制御モデルは以下のようなシンプルなものでした。



この制御モデルは、巷でよく見かける配列のインデックスを使って制御する方法よりも抽象化が可能で、チャネル数の変更や遅延量の制御などを簡単に実現する事が可能です。

このシンプルな制御モデルの応用可能範囲は、ビデオやオーディオに限りません。

例えば、データロガー等の場合、出力側にSDカードのような書き込み処理に時間がかかる物を配置する事があります。
この場合、viproc側でデータ収集設定(DMA設定)、voproc側でSDカードへの書き込みという事になります。

何がポイントですか?

先の制御モデルのポイントの一つは「queueによって入出力の関係が切れている」事です。
実は先日のシンプルな制御モデルは非常にシンプルにした一例で、実際に使用する場合にはもう少し思考を進める必要があります。

話を先に進める前に簡単にポイントを振り返っておきましょう。
  1. 遅延器(DELAY)は、DMAリクエスト発行からDMA完了までの時間を保証する。
  2. キュー(QUEUE)は、処理時間が一定でない後段に前段が影響を受けないようにする。
  3. 遅延器(DELAY)とキュー(QUEUE)によって、入力と出力の時間依存関係を緩いものにする。
入力側と出力側の両方でDMAを使用する場合も考慮して、先のシンプルな制御モデルに対して少し思考を進めてみましょう。


今回は対象領域も明確にしておきました。

入力処理では、入力キューからバッファを取得し、DMAリクエストを発行した後、入力遅延器にバッファを格納します。加えて、入力遅延器から出力キューに過去のバッファを移動させます。
出力処理では、出力キューからバッファを取得し、DMAリクエストを発行した後、出力遅延器にバッファを格納します。加えて、出力遅延器から入力キューに過去のバッファを移動させます。

上記文面を見ておわかり頂けると思いますが、入出力で完全に対称系になっています。

上記モデルを使用する事で、先に挙げたようなチャネル数の変更や遅延量の制御だけでなく、入出力の位相が曖昧な場合でも制御が破綻するような事がありません。

上記の制御モデルを使用する場合、以下のような事も考慮すると良いでしょう。
  • 入力と出力は別々のハードウェアが担当しているのが普通。
    • 入力割り込み、出力割り込みは非同期系として設計する。
    • RTOSを使用する場合、入力と出力の処理スレッドは別々に設計する。
  • キューから取り出せない場合、どのように振舞うべきか?
    • 制御系が何を期待するのかによって挙動を決めます。
  • 全体をリセットする場合、どのようにリセットすべきか?
    • これは意外に難しい問題です。
    • ハードウェアがどのように振舞うのかによっても大きく変わります。
  • その他。(色々あります)

動かして遊べるソースコード

今回も動かして遊べるソースコードを用意しました。
先の投稿のソースコードと見比べてみるのも楽しいと思います。

2012年10月31日水曜日

ビデオやオーディオのDMAバッファの管理手法 (動かして遊べるソースコード付き!)

DMAバッファを管理する

ビデオやオーディオのように絶え間なくやってくるデータの入出力を処理する場合、一般にDMAを使います。
システムの中において、バッファの状態は主に「DMA中」、「DMA完了」、「未使用」の3種類に分類する事ができます。

DMAは、リクエストを出してからバッファにデータが完全にやってくるまでに時間がかかります。
DMAのリクエストを出してから即座にそのバッファに対して処理を開始する事は通常しません。

ビデオやオーディオのDMAバッファの管理は、DMAを出してからDMA完了イベントを待ち、到着したデータに対して処理を始める事になります。

配列による手法

DMAバッファを管理する機構を実現する際によく見かけるのは以下のような配列を用いた手法です。


バッファや管理構造体を配列として保持し、何番目が「DMA中」、何番目が「DMA完了」といった具合にインデックスで管理する方法です。非常にシンプルですし、何の問題もないのですが、実装してみると意外とごちゃごちゃした感じになる傾向があります。これは、「システムの全体挙動を見る」という視点を持って見た場合、「配列にアクセスする」という実装詳細が目に入ってきてしまうためなのかもしれません。

モデルベースによる手法

少し考え直して、以下のような簡単なモデルを作ってみました。
このモデルには登場人物が4人います。


queueは、任意の長さを構成可能なFIFOで、「未使用」状態のバッファを格納しておく場所です。
delayは、任意の遅延数を構成可能なFIFOで、設定した遅延数でデータが出てくるディレイ・ラインです。

viprocは、ビデオやオーディオを入力するモジュールで、データを入力すべきタイミングでのみ実行されるものです。viprocは、自身が呼ばれたらqueueからバッファを取りだし、バッファにデータを入力するためにDMAをリクエストします。加えて、バッファをdelayに押し込んで終了します。

voprocは、ビデオやオーディオを出力するモジュールで、データを出力すべきタイミングでのみ実行されるものです。voprocは、自身が呼ばれたらdelayからバッファを取りだし、バッファからデータを出力するためにDMAをリクエストします。加えてqueueにバッファを押しこんで終了します。

上記設計の場合、queueの深さは出力DMAが完了するために必要な深さが必要です。
その点さえ注意すれば、上記のモデルは以下のようなメリットがあります。
  • 実装の抽象度が高く、メインテナンスが容易。
  • データ入力が確定するまでの遅延数をdelay(ディレイ・ライン)の長さで任意に調整可能。
  • 入出力が完全同期系でなくても、delayとqueueの範囲で吸収可能。
  • 使用可能なバッファ数はqueueの長さで決められ、先読みなどの処理も容易。
  • その他。
先の配列による手法では、バッファ管理のためのインデックス変数などが上位に出てきて、ごちゃごちゃした感が否めませんでした。
モデルベースによる手法を用いる事で実装の抽象度が上がってシンプルな実装を実現できます。

上記に示したメリットは配列による手法でも当然のように実現可能です。
が、実際に実現した実装を比較してみるとモデルベース設計の効果がわかると思います。

動かして遊べるソースコード

実際に上記のモデルを動かして遊べるソースコードを用意しました。
viomodel.tar.gzからダウンロードして下さい。

2013年03月31日追記

DMAバッファ管理手法の続編」に続編を書きました。

2012年9月15日土曜日

テキストLCDをグラフィックLCDのように扱うギミック ~テキストLCDでもここまでできる!~

今回はちょっとお遊び休憩ネタです。

Analog Devices社Blackfin BF592搭載オーディオ・プラットフォームBlueTank BF592を使ったお遊び。
まずは動作をご覧下さい。

[フレーム]

上記で使用しているテキストLCDは2行8文字の表示が可能なLCDです。

殆どのテキストLCDにはHD44780互換のLCDコントローラが搭載されています。
ユーザ登録可能な文字は8つあります。

しかし、普通に考えるとユーザ登録文字を使って全画面を一度に表示する事はできません。
なぜなら2行x8文字で、16文字のユーザ登録文字が必要になるからです。

では「どうしているのか?」というと簡単で、時分割で細かく切り替えて表示を行なっています。
以下にステップ毎の動作の様子を示します。

1文字目は普通に考えてもいけます。


もちろん2文字目も。


3文字。


4文字。


5文字。


6文字。


7文字。


8文字。

ここで、ユーザ登録文字の8文字を使い切りました。
まだ画面の半分しか描画できていません。


で、どうするのかというと・・・。

新たに描画の必要がある領域のために一番最初に書いた文字を消します。
これなら使っているユーザ登録文字は依然として8文字です。


同様にして。


どんどん上側を消して下側を書きます。


どんどん上側を消して下側を書きます。


どんどん上側を消して下側を書きます。


どんどん上側を消して下側を書きます。


どんどん上側を消して下側を書きます。


これで下側の画面描画が完了しました。


上記を非常に細かい時間で繰り返すと以下のように見えるというのが今回のギミック。
まぁ、飛び越し走査とは違いますが、あえて言うならば「なんちゃってインターレース」でしょうか?


実はこのデモ、48KHzステレオオーディオの処理をしながらの動作です。
先日のFFTスペアナと合わせればもっと楽しい見た目を提供できそう。

動作を見ると実装は複雑なんじゃないか?と思うかもしれません。
でも、きちんと設計すればそんな事はありません。

以下のような定義でざざっと絵を作れるようにしました。


で、実際の描画は下層のglcdモジュールにお任せという具合です。

glcd_clear(&(w->glcd));
for (px = 0; px < GLCD_PX; px++) {
 for (py = 0; py < GLCD_PY; py++) {
  glcd_set_pixel(&(w->glcd), px, py, (img1[py][px + (VOL_MAX - w->volume)] == 'o') ? 1 : 0);
 }
}

gcld_blitを呼ぶと、内部でうまい具合に1シーケンス分の処理をしてくれるという設計です。

glcd_blit(&w->glcd);

上記の画像は「リアルなLCD画像ファイルが得られるLCD Toolの改良」で取り上げたlcdtoolを使って生成しました。
LCDプロファイルとLCDフォントに少し手を入れるだけで好みの出力が得られます。
BMP画像ファイルの生成はスクリプトで一発自動生成。
#!/bin/sh
LCDTOOL=lcdtool
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq01.bmp '\x00' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq02.bmp '\x00\x02' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq03.bmp '\x00\x02\x04' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq04.bmp '\x00\x02\x04\x06' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq05.bmp '\x00\x02\x04\x06\x08' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq06.bmp '\x00\x02\x04\x06\x08\x0A' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq07.bmp '\x00\x02\x04\x06\x08\x0A\x0C' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq08.bmp '\x00\x02\x04\x06\x08\x0A\x0C\x0E' ' '
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq09.bmp ' \x02\x04\x06\x08\x0A\x0C\x0E' '\x01'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq10.bmp ' \x04\x06\x08\x0A\x0C\x0E' '\x01\x03'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq11.bmp ' \x06\x08\x0A\x0C\x0E' '\x01\x03\x05'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq12.bmp ' \x08\x0A\x0C\x0E' '\x01\x03\x05\x07'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq13.bmp ' \x0A\x0C\x0E' '\x01\x03\x05\x07\x09'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq14.bmp ' \x0C\x0E' '\x01\x03\x05\x07\x09\x0B'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq15.bmp ' \x0E' '\x01\x03\x05\x07\x09\x0B\x0D'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont seq16.bmp ' ' '\x01\x03\x05\x07\x09\x0B\x0D\x0F'
$LCDTOOL bluetank.lcdprof glcd_demo.lcdfont view.bmp '\x00\x02\x04\x06\x08\x0A\x0C\x0E' '\x01\x03\x05\x07\x09\x0B\x0D\x0F'
自動化してあるので、画面数が多くても操作が苦になる事はありません。
また、出力が間違っていても、スクリプトさえ残しておけば後で何度も修正する事ができます。

2012年8月29日水曜日

リアルな操作画面のキャプチャが欲しい! ~ドキュメント制作時に便利なLCD画像生成ツールを作ってみた~

リアルな操作画面のキャプチャが欲しい!

ドキュメント制作時、意外に面倒なのが操作画面のキャプチャです。
たかがドキュメント、されどドキュメント。

意外に適当な文書作成ツールで作ったドキュメントが多いのが実状ではないでしょうか?
テキストLCDの画面が情けない通常のフォントを使ったものになっている事も少なくありません。

今回はドキュメント制作時に便利なLCD画像生成ツールを作ってみました。
まずは、その出力をご覧ください。



上記は8文字x2行のLCDを模擬した出力です。
バックライトは青色ですね。

少し実物と見比べてみましょう。


上記の画面を仮に説明するドキュメントを作る場合、以下のような出力を得る事になります。


適当なフォントでドキュメントを制作するよりも当然ながらリアリティがあります。
少し「ちゃんとドキュメントも書こうかな?」なんていう気持ちにもなります。

実行方法

出力BMPファイル名、1行目の表示、2行目の表示を与えて実行させるようにしました。
シェルから実行させればまとめて出力を得る事ができます。

外字も組み込んでおけば外字込みでの出力を得る事も可能。
bashを使えばコマンドラインから16進数を与える事ができます。

#!/bin/bash
LCDTOOL=./lcdtool
$LCDTOOL page001.bmp $'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' "TEST"
$LCDTOOL page002.bmp "BlueTank" " BF592 "
$LCDTOOL page003.bmp "Effect:0" "Volume:0"
$LCDTOOL page004.bmp "MT: OK " "InitDone"

ソースコードのダウンロード

こちらからダウンロードして適当にビルドして使用して下さい。
ライセンスはMITです。


2012年09月01日追記
改良したバージョンがhttp://shinta-main-jp.blogspot.jp/2012/09/lcdlcd-tool.htmlにあります。

2012年8月17日金曜日

有機ELディスプレイ搭載スイッチIS-C15ANP4をPicoBlazeで制御しよう

先日からピコピコとPicoBlazeを触っていたわけですが、某所で日本開閉器工業社製の有機ELディスプレイ搭載スイッチIS-C15ANP4をPicoBlazeから制御したいと依頼を受けました。


メーカのウェブからダウンロードできる資料には、初期化時に使用するパラメータが列挙されています。このスイッチのディスプレイ・コントローラには、SOLOMON SYSTECH社のSSD1331が搭載されているようです。初期化コードをスイッチに送ってやるだけでディスプレイ・コントローラの初期化は完了です。後は、データを送ればそのまま絵として表示されます。


さて、今回のPicoBlazeのデザインは、先のLEDをピコピコさせたものをそのまま流用しました。
という事はBit Bangで制御するのね、ヒドイ!


ということで、トップのデザインです。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity main is
 Port (
 oled_ss : out std_logic;
 oled_res : out std_logic;
 oled_dc : out std_logic;
 oled_sck : out std_logic;
 oled_sdi : out std_logic;
 clk : in std_logic;
 sw1 : in std_logic);
end main;
architecture RTL of main is
 component kcpsm6
 generic( hwbuild : std_logic_vector(7 downto 0) := X"00";
 interrupt_vector : std_logic_vector(11 downto 0) := X"3FF";
 scratch_pad_memory_size : integer := 64);
 port ( address : out std_logic_vector(11 downto 0);
 instruction : in std_logic_vector(17 downto 0);
 bram_enable : out std_logic;
 in_port : in std_logic_vector(7 downto 0);
 out_port : out std_logic_vector(7 downto 0);
 port_id : out std_logic_vector(7 downto 0);
 write_strobe : out std_logic;
 k_write_strobe : out std_logic;
 read_strobe : out std_logic;
 interrupt : in std_logic;
 interrupt_ack : out std_logic;
 sleep : in std_logic;
 reset : in std_logic;
 clk : in std_logic);
 end component;
 component c15anp4
 generic( C_FAMILY : string := "S6";
 C_RAM_SIZE_KWORDS : integer := 1;
 C_JTAG_LOADER_ENABLE : integer := 0);
 Port ( address : in std_logic_vector(11 downto 0);
 instruction : out std_logic_vector(17 downto 0);
 enable : in std_logic;
 rdl : out std_logic;
 clk : in std_logic);
 end component;
signal address : std_logic_vector(11 downto 0);
signal instruction : std_logic_vector(17 downto 0);
signal port_id : std_logic_vector(7 downto 0);
signal in_port : std_logic_vector(7 downto 0);
signal out_port : std_logic_vector(7 downto 0);
signal read_strobe : std_logic;
signal write_strobe : std_logic;
signal port000 : std_logic_vector(7 downto 0);
begin
 processor: kcpsm6
 generic map ( hwbuild => X"00",
 interrupt_vector => X"3FF",
 scratch_pad_memory_size => 64)
 port map(
 address => address,
 instruction => instruction,
 port_id => port_id,
 interrupt => '0',
 write_strobe => write_strobe,
 out_port => out_port,
 read_strobe => read_strobe,
 in_port => in_port,
 sleep => '0',
 reset => '0',
 clk => clk);
 program: c15anp4
 generic map( C_FAMILY => "S6",
 C_RAM_SIZE_KWORDS => 1,
 C_JTAG_LOADER_ENABLE => 0)
 port map( address => address,
 instruction => instruction,
 enable => '1',
 clk => clk);
 out_ratch : process(clk) is
 begin
 if clk'event and clk='1' then
 if write_strobe='1' then
 if port_id ="00000000" then
 port000 <= out_port;
 end if;
 end if;
 end if;
 end process out_ratch;
 in_ratch : process(clk) is
 begin
 if clk'event and clk='1' then
 if read_strobe='1' then
 if port_id ="00000000" then
 in_port(0) <= not sw1;
 end if;
 end if;
 end if;
 end process in_ratch;
 oled_ss <= port000(0);
 oled_res <= port000(1);
 oled_dc <= port000(2);
 oled_sck <= port000(3);
 oled_sdi <= port000(4);
end RTL;

回路はLX-9 MicroBlazeのPMODのピンに超適当に配線です。

NET "oled_ss" LOC = D18;
NET "oled_res" LOC = D17;
NET "oled_dc" LOC = G14;
NET "oled_sck" LOC = F14;
NET "oled_sdi" LOC = F15;


さて、後はI/Oをバンバン打つだけです。

 ; =========================================================
 ; Port000[0] : SS- : Slave Select (Low Active)
 ; Port000[1] : RES : Reset (Low Active)
 ; Port000[2] : D/C : Data/Command (Data=High, Command=Low)
 ; Port000[3] : SCK : Serial Clock (Up Edge Trigger)
 ; Port000[4] : SDI : Serial Data
 ; =========================================================
 ; ---------------------------------------------------------
 ; Register Name
 ; ---------------------------------------------------------
 ; SPI Data Register
 NAMEREG s0, SPIDATA
 ; Port Data Register
 NAMEREG s1, PORTDAT
 ; Delay Counter No.1
 NAMEREG s2, DLYCNT1
 ; Delay Counter No.2
 NAMEREG s3, DLYCNT2
 ; Delay Counter No.3
 NAMEREG s4, DLYCNT3
 ; Send Counter No.1
 NAMEREG s5, SNDCNT1
 ; Send Counter No.2
 NAMEREG s6, SNDCNT2
 ; Temporary Register No.1
 NAMEREG s7, TEMP_I
 ; Temporary Register No.2
 NAMEREG s8, TEMP_J
 ; ---------------------------------------------------------
 ; Constant Variables
 ; ---------------------------------------------------------
 CONSTANT PORT000, 00
 CONSTANT OLED_SS, 01
 CONSTANT OLED_RES, 02
 CONSTANT OLED_DC, 04
 CONSTANT OLED_SCK, 08
 CONSTANT OLED_SDI, 10
MAIN:
 CALL INIT
TESTLOOP:
 CALL INIT_ADDR
 CALL FILLR
 CALL LONGDELAY
 CALL INIT_ADDR
 CALL FILLG
 CALL LONGDELAY
 CALL INIT_ADDR
 CALL FILLB
 CALL LONGDELAY
 CALL INIT_ADDR
 CALL FILLW
 CALL LONGDELAY
 JUMP TESTLOOP
INIT_ADDR:
 ; ---------------------------------------------------------
 ; Setup Column Address
 ; ---------------------------------------------------------
 LOAD SPIDATA, 15
 CALL SEND_CMD
 LOAD SPIDATA, 10
 CALL SEND_CMD
 LOAD SPIDATA, 4F
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Setup Row Address
 ; ---------------------------------------------------------
 LOAD SPIDATA, 75
 CALL SEND_CMD
 LOAD SPIDATA, 00
 CALL SEND_CMD
 LOAD SPIDATA, 2F
 CALL SEND_CMD
 RETURN
FILLR:
 LOAD TEMP_I, 30
FILLR1:
 LOAD TEMP_J, 40
FILLR2:
 ; ---------------------------------------------------------
 ; Byte0 = B[4:0] + G[5:3]
 ; Byte1 = G[2:0] + R[4:0]
 ; ---------------------------------------------------------
 LOAD SPIDATA, 00
 CALL SEND_DAT
 LOAD SPIDATA, 1F
 CALL SEND_DAT
 SUB TEMP_J, 01
 JUMP NZ, FILLR2
 SUB TEMP_I, 01
 JUMP NZ, FILLR1
 RETURN
FILLG:
 LOAD TEMP_I, 30
FILLG1:
 LOAD TEMP_J, 40
FILLG2:
 ; ---------------------------------------------------------
 ; Byte0 = B[4:0] + G[5:3]
 ; Byte1 = G[2:0] + R[4:0]
 ; ---------------------------------------------------------
 LOAD SPIDATA, 07
 CALL SEND_DAT
 LOAD SPIDATA, C0
 CALL SEND_DAT
 SUB TEMP_J, 01
 JUMP NZ, FILLG2
 SUB TEMP_I, 01
 JUMP NZ, FILLG1
 RETURN
FILLB:
 LOAD TEMP_I, 30
FILLB1:
 LOAD TEMP_J, 40
FILLB2:
 ; ---------------------------------------------------------
 ; Byte0 = B[4:0] + G[5:3]
 ; Byte1 = G[2:0] + R[4:0]
 ; ---------------------------------------------------------
 LOAD SPIDATA, F8
 CALL SEND_DAT
 LOAD SPIDATA, 00
 CALL SEND_DAT
 SUB TEMP_J, 01
 JUMP NZ, FILLB2
 SUB TEMP_I, 01
 JUMP NZ, FILLB1
 RETURN
FILLW:
 LOAD TEMP_I, 30
FILLW1:
 LOAD TEMP_J, 40
FILLW2:
 ; ---------------------------------------------------------
 ; Byte0 = B[4:0] + G[5:3]
 ; Byte1 = G[2:0] + R[4:0]
 ; ---------------------------------------------------------
 LOAD SPIDATA, FF
 CALL SEND_DAT
 LOAD SPIDATA, FF
 CALL SEND_DAT
 SUB TEMP_J, 01
 JUMP NZ, FILLW2
 SUB TEMP_I, 01
 JUMP NZ, FILLW1
 RETURN
INIT:
 ; =========================================================
 ; SS- : Slave Select (Low Active)
 ; RES : Reset (Low Active)
 ; D/C : Data/Command (Data=High, Command=Low)
 ; SCK : Serial Clock (Up Edge Trigger)
 ; SDI : Serial Data
 ; =========================================================
 ; ---------------------------------------------------------
 ; Reset Active
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; Reset Inactive
 ; ---------------------------------------------------------
 LOAD PORTDAT, OLED_RES
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; Setup Column Address
 ; ---------------------------------------------------------
 LOAD SPIDATA, 15
 CALL SEND_CMD
 LOAD SPIDATA, 10
 CALL SEND_CMD
 LOAD SPIDATA, 4F
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Setup Row Address
 ; ---------------------------------------------------------
 LOAD SPIDATA, 75
 CALL SEND_CMD
 LOAD SPIDATA, 00
 CALL SEND_CMD
 LOAD SPIDATA, 2F
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Setup Contrast Color A
 ; ---------------------------------------------------------
 LOAD SPIDATA, 81
 CALL SEND_CMD
 LOAD SPIDATA, 19
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Setup Contrast Color B
 ; ---------------------------------------------------------
 LOAD SPIDATA, 82
 CALL SEND_CMD
 LOAD SPIDATA, 14
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Setup Contrast Color C
 ; ---------------------------------------------------------
 LOAD SPIDATA, 83
 CALL SEND_CMD
 LOAD SPIDATA, 24
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Master Current Control
 ; ---------------------------------------------------------
 LOAD SPIDATA, 87
 CALL SEND_CMD
 LOAD SPIDATA, 0F
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Display Start Line
 ; ---------------------------------------------------------
 LOAD SPIDATA, A1
 CALL SEND_CMD
 LOAD SPIDATA, 00
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Display Offset
 ; ---------------------------------------------------------
 LOAD SPIDATA, A2
 CALL SEND_CMD
 LOAD SPIDATA, 10
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Display Mode
 ; ---------------------------------------------------------
 LOAD SPIDATA, A4
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Multiplex Ratio
 ; ---------------------------------------------------------
 LOAD SPIDATA, A8
 CALL SEND_CMD
 LOAD SPIDATA, 2F
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Dim Mode Setting
 ; ---------------------------------------------------------
 LOAD SPIDATA, AB
 CALL SEND_CMD
 LOAD SPIDATA, 12
 CALL SEND_CMD
 LOAD SPIDATA, 0C
 CALL SEND_CMD
 LOAD SPIDATA, 14
 CALL SEND_CMD
 LOAD SPIDATA, 12
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Master Configuration
 ; ---------------------------------------------------------
 LOAD SPIDATA, AD
 CALL SEND_CMD
 LOAD SPIDATA, 8E
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Phase Period Adjustment
 ; ---------------------------------------------------------
 LOAD SPIDATA, B1
 CALL SEND_CMD
 LOAD SPIDATA, 44
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Display Clock Divider
 ; ---------------------------------------------------------
 LOAD SPIDATA, B3
 CALL SEND_CMD
 LOAD SPIDATA, A0
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Enable Linear Gray Scale
 ; ---------------------------------------------------------
 LOAD SPIDATA, B9
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Precharge Level
 ; ---------------------------------------------------------
 LOAD SPIDATA, BB
 CALL SEND_CMD
 LOAD SPIDATA, 12
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set VCOMH
 ; ---------------------------------------------------------
 LOAD SPIDATA, BE
 CALL SEND_CMD
 LOAD SPIDATA, 28
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Color Depth = 64K
 ; ---------------------------------------------------------
 LOAD SPIDATA, A0
 CALL SEND_CMD
 LOAD SPIDATA, 70
 CALL SEND_CMD
 ; ---------------------------------------------------------
 ; Set Display Normal
 ; ---------------------------------------------------------
 LOAD SPIDATA, AF
 CALL SEND_CMD
 RETURN
SEND_DAT:
 ; =========================================================
 ; SS- : Slave Select (Low Active)
 ; RES : Reset (Low Active)
 ; D/C : Data/Command (Data=High, Command=Low)
 ; SCK : Serial Clock (Up Edge Trigger)
 ; SDI : Serial Data
 ; =========================================================
SEND_DAT_SLAVE_ACTIVE:
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
SEND_DAT_SETUP:
 LOAD SNDCNT1, 08
 LOAD SNDCNT2, SPIDATA
SEND_DAT_LOOP:
 SL0 SNDCNT2
 JUMP C, SEND_DAT_H
 JUMP SEND_DAT_L
SEND_DAT_H:
 ; ---------------------------------------------------------
 ; SDI=HIGH, SCK=LOW
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
 OR PORTDAT, OLED_SDI
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; SDI=HIGH, SCK=HIGH
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
 OR PORTDAT, OLED_SCK
 OR PORTDAT, OLED_SDI
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; NEXT
 ; ---------------------------------------------------------
 JUMP SEND_DAT_NEXT
SEND_DAT_L:
 ; ---------------------------------------------------------
 ; SDI=LOW, SCK=LOW
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; SDI=LOW, SCK=HIGH
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
 OR PORTDAT, OLED_SCK
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; NEXT
 ; ---------------------------------------------------------
 JUMP SEND_DAT_NEXT
SEND_DAT_NEXT:
 SUB SNDCNT1, 01
 JUMP NZ, SEND_DAT_LOOP
SEND_DAT_END:
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_DC
 OR PORTDAT, OLED_SS
 OUTPUT PORTDAT, PORT000
 RETURN
SEND_CMD:
 ; =========================================================
 ; SS- : Slave Select (Low Active)
 ; RES : Reset (Low Active)
 ; D/C : Data/Command (Data=High, Command=Low)
 ; SCK : Serial Clock (Up Edge Trigger)
 ; SDI : Serial Data
 ; =========================================================
SEND_CMD_SLAVE_ACTIVE:
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
SEND_CMD_SETUP:
 LOAD SNDCNT1, 08
 LOAD SNDCNT2, SPIDATA
SEND_CMD_LOOP:
 SL0 SNDCNT2
 JUMP C, SEND_CMD_H
 JUMP SEND_CMD_L
SEND_CMD_H:
 ; ---------------------------------------------------------
 ; SDI=HIGH, SCK=LOW
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_SDI
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; SDI=HIGH, SCK=HIGH
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_SCK
 OR PORTDAT, OLED_SDI
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; NEXT
 ; ---------------------------------------------------------
 JUMP SEND_CMD_NEXT
SEND_CMD_L:
 ; ---------------------------------------------------------
 ; SDI=LOW, SCK=LOW
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; SDI=LOW, SCK=HIGH
 ; ---------------------------------------------------------
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_SCK
 OUTPUT PORTDAT, PORT000
 CALL DELAY
 ; ---------------------------------------------------------
 ; NEXT
 ; ---------------------------------------------------------
 JUMP SEND_CMD_NEXT
SEND_CMD_NEXT:
 SUB SNDCNT1, 01
 JUMP NZ, SEND_CMD_LOOP
SEND_CMD_END:
 LOAD PORTDAT, 00
 OR PORTDAT, OLED_RES
 OR PORTDAT, OLED_SS
 OUTPUT PORTDAT, PORT000
 RETURN
DELAY:
 LOAD DLYCNT1, 01
DELAY1:
 LOAD DLYCNT2, 01
DELAY2:
 SUB DLYCNT2, 01
 JUMP NZ, DELAY2
 SUB DLYCNT1, 01
 JUMP NZ, DELAY1
 RETURN
LONGDELAY:
 LOAD DLYCNT1, 80
LONGDELAY1:
 LOAD DLYCNT2, FF
LONGDELAY2:
 LOAD DLYCNT3, FF
LONGDELAY3:
 SUB DLYCNT3, 01
 JUMP NZ, LONGDELAY3
 SUB DLYCNT2, 01
 JUMP NZ, LONGDELAY2
 SUB DLYCNT1, 01
 JUMP NZ, LONGDELAY1
 RETURN

もう完全にPicoBlazeの使い方としてはおかしいですが、第一段階としては気にしません。
上記のコードで「赤」、「緑」、「青」、「白」と順に表示を繰り返す事ができます。


[フレーム]

リソースの使用率ですが、以下のようになりました。


Bit Bangはあんまりなので、SPIモジュールを作ってやってデータを渡すのが良いでしょう。
データを渡して、転送開始入力信号と転送完了出力信号が出てくるようなモジュールです。

フォントなどを内蔵すればもっと手軽に表示が楽しめそう。
上記を雛型にすればこういった拡張も簡単です。

ちなみに、今回はBit Bangの基本動作を外部のロジック・アナライザで確認した後、セットアップとホールド、クロックサイクルを見ながらアセンブラ・コードをチューニングし、波形が期待通りに出力されている事を確認した後で、最後にIS-C15ANP4を接続するという形で作業しました。(ChipScope使えよという感じです。)


PicoBlazeのアセンブラも、最初のバージョンより気が利くようになっていて、一度立ち上げてプログラム・ファイル名を入力しておけば「R」をタイプするだけで再コンパイルできるようになっています。


なので、アセンブルの後で生成されたVHDLををプロジェクトにコピーするバッチファイルを作っておけば、「アセンブル→再インプリメンテーション」の流れをスムーズにこなす事ができます。


こんな感じで(超適当だけど)半田付けを含めて小1時間ほどでPicoBlazeからIS-C15ANP4に絵が出せるようになりました。


念のために書いておくと、IS-C15ANP4には決められた電源シーケンスがあります。
製品に使用する場合、きちんとシーケンスを守るように設計しましょう。


最後にグリーンな表示。

ちなみに、先ほどの初期化コードの場合、2バイトで1ピクセルを表現するようになっています。
BとRが5ビット、Gが6ビットで、1バイト目がB[4:0] + G[5:3]、2バイト目がG[2:0] + R[4:0]です。


2012年8月15日水曜日

PicoBlazeでピコピコしよう (Xilinx社のデバイスが搭載されたFPGAボードをジャンク箱で眠らせている人にもお勧め)

PicoBlazeってなぁに?

PicoBlazeとは、Xilinxが提供しているソフト・マクロで構成されたマイクロ・コントローラです。
最大4K命令の実行が可能で、命令長は18ビット固定、1命令あたり2クロック・サイクルで処理されます。

PicoBlazeは、非常にシンプルな8ビット・プロセッサです。
I/Oは自分で構成する事ができ、入出力それぞれ独立した8ビットのポート番号を持ちます。
割り込みなんて1系統しかありません。

最大クロック周波数は、デバイスとデザイン(配置配線、タイム・スペック)に依存するものの、Spartan-6(-2)で105MHz、Spartan-6(-3)で138MHz、Vertex-6(-3)で240MHzが得られます。

とはいえ、パフォーマンスはさほど重要なポイントではありません。
というか、パフォーマンス目当てでこんな物を使ってはいけません。


FPGAとプロセッサを並行して使う事で、論理記述言語のみで処理する場合に比べて、格段に設計が楽になったりします。(当然ですが、全てのケースでそうだなんて言いません。そういう場合もあるというだけです。)

プロセッサの場合、シーケンシャルな処理を記述するのは簡単で、単に処理を上から順に書くだけで済みます。(実行はFPGAに比べて格段に遅いけどね。)
FPGAでシーケンシャルな処理を構成する場合、ステートマシンを設計したり、ジェネリックで色々なパラメータを上位から渡せるようにした後でそれらを上位でパイプラインを構成して組み合わせたりと、何だかんだを手作業でやる事になるのが通例です。

「FPGAで構成したクロック単位でキビキビ動作する回路と、PicoBlaze上で動作するゆるーいソフトウェアを組み合わせる事で、従来にないメリットが得る」という使い方が、PicoBlazeの美味しいところです。

PicoBlazeはEDKなど有償ライセンスなしで開発できるのも嬉しいところ。
自宅のプチ・プロジェクトでも安心して使えるのがPicoBlazeなのです。

PicoBlazeのリソースをダウンロードする

PicoBlazeのリソースは、Xilinx社のウェブからダウンロードする事ができます。
ダウンロードにはサイン・インが必要です。
http://www.xilinx.com/ipcenter/processor_central/picoblaze/member/index.htm

初めにダウンロードする場合、登録が必要になっています。


ターゲットデバイスを選択して、住所などを入力します。


ライセンス情報が表示されます。


登録が完了するとラウンジに入室です。


ここからお目当てのデバイスのリソースをダウンロードします。
既に7シリーズ用のリソースもあります。


今回はLX-9 MicroBoardで使用するので、KCPSM6をダウンロードしました。
Release4の中身は以下のようになっていました。


どんな風に開発するの?

PicoBlazeの開発フローを簡単に説明します。
  1. PicoBlaze向けのソフトウェアをアセンブラで記述する。
  2. 記述したソフトウェアをアセンブルして、プログラムROM用のモジュールを得る。
  3. プログラムROM用のモジュールとPicoBlazeソフト・コアをプロジェクトに追加する。
  4. シンセサイズとインプリメンテーションを実行する。
まぁ、ざっくり言ってしまうと、FPGAの開発フローの前段に余計な物がくっついたという感じです。
PicoBlazeプロセッサ向けに追加されたフローは、アセンブラの記述とアセンブルの実行です。

このプロセスで得られるのは、PicoBlazeソフト・コア本体と、自分で記述したソフトウェアのインストラクションが格納されたモジュールです。後は、ROMとソフト・コア本体、そして自分が欲しい周辺回路を論理記述言語で接続して使用するだけです。

命令格納モジュール(下図で言うyour_program)とPicoBlaze(下図で言うkcpsm6)の関係は以下です。プロセッサ・コアからアドレスを与えられた命令格納モジュールが、命令をプロセッサ・コアに引き渡す構成です。


使用イメージを簡単に掴む為に、実際にインプリメンテーションを通した後の様子を回路図で見てみましょう。


上記を見ておわかりのように、ユーザ回路の一部にインストラクション格納モジュールとPicoBlaze本体が挟まっただけの構成です。

本当に適当な例(さっきの回路)

ここで、本当に適当な例(さっきの回路)を挙げてみましょう。
トップはsw1の状態を入力ポートのビット0に渡し、出力ポートのビット3から0をport_idが0の時にラッチしてLEDを点灯させているだけの回路です。

入力ポートをどのように使用するのか、出力ポートをどのように使用するのか、についてはプロセッサ上で動作するプログラムに任されています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity main is
 Port (
 led1 : out std_logic;
 led2 : out std_logic;
 led3 : out std_logic;
 led4 : out std_logic;
 clk : in std_logic;
 sw1 : in std_logic);
end main;
architecture RTL of main is
 component kcpsm6
 generic( hwbuild : std_logic_vector(7 downto 0) := X"00";
 interrupt_vector : std_logic_vector(11 downto 0) := X"3FF";
 scratch_pad_memory_size : integer := 64);
 port ( address : out std_logic_vector(11 downto 0);
 instruction : in std_logic_vector(17 downto 0);
 bram_enable : out std_logic;
 in_port : in std_logic_vector(7 downto 0);
 out_port : out std_logic_vector(7 downto 0);
 port_id : out std_logic_vector(7 downto 0);
 write_strobe : out std_logic;
 k_write_strobe : out std_logic;
 read_strobe : out std_logic;
 interrupt : in std_logic;
 interrupt_ack : out std_logic;
 sleep : in std_logic;
 reset : in std_logic;
 clk : in std_logic);
 end component;
 component led_toggle
 generic( C_FAMILY : string := "S6";
 C_RAM_SIZE_KWORDS : integer := 1;
 C_JTAG_LOADER_ENABLE : integer := 0);
 Port ( address : in std_logic_vector(11 downto 0);
 instruction : out std_logic_vector(17 downto 0);
 enable : in std_logic;
 rdl : out std_logic;
 clk : in std_logic);
 end component;
signal address : std_logic_vector(11 downto 0);
signal instruction : std_logic_vector(17 downto 0);
signal port_id : std_logic_vector(7 downto 0);
signal in_port : std_logic_vector(7 downto 0);
signal out_port : std_logic_vector(7 downto 0);
signal read_strobe : std_logic;
signal write_strobe : std_logic;
signal port000 : std_logic_vector(7 downto 0);
begin
 processor: kcpsm6
 generic map ( hwbuild => X"00",
 interrupt_vector => X"3FF",
 scratch_pad_memory_size => 64)
 port map(
 address => address,
 instruction => instruction,
 port_id => port_id,
 interrupt => '0',
 write_strobe => write_strobe,
 out_port => out_port,
 read_strobe => read_strobe,
 in_port => in_port,
 sleep => '0',
 reset => '0',
 clk => clk);
 program_rom: led_toggle
 generic map( C_FAMILY => "S6",
 C_RAM_SIZE_KWORDS => 1,
 C_JTAG_LOADER_ENABLE => 0)
 port map( address => address,
 instruction => instruction,
 enable => '1',
 clk => clk);
 out_ratch : process(clk) is
 begin
 if clk'event and clk='1' then
 if write_strobe='1' then
 if port_id ="00000000" then
 port000 <= out_port;
 end if;
 end if;
 end if;
 end process out_ratch;
 in_ratch : process(clk) is
 begin
 if clk'event and clk='1' then
 if read_strobe='1' then
 if port_id ="00000000" then
 in_port(0) <= not sw1;
 end if;
 end if;
 end if;
 end process in_ratch;
 led1 <= port000(0);
 led2 <= port000(1);
 led3 <= port000(2);
 led4 <= port000(3);
end RTL;

次にプロセッサ上のプログラムです。

レジスタは16レジスタが2バンクある構成になっています。



デフォルト・バンクは'A'です。
これらのレジスタは、ユーザが自由に使用する事ができます。
単にs0とかs1とかだと分かりにくいのですが、NAMEREGを使う事で名前でレジスタにアクセスする事ができます。


 NAMEREG s0, TEMP
 NAMEREG s1, DLYCNT1
 NAMEREG s2, DLYCNT2
 NAMEREG s3, PVAL000
 CONSTANT PORT000, 00
INIT:
 LOAD PVAL000, 00
LOOP:
 CALL DELAY
 INPUT TEMP, PORT000
 COMPARE TEMP, 01
 JUMP Z, LOOP
LOOP1:
 CALL DELAY
 INPUT TEMP, PORT000
 COMPARE TEMP, 00
 JUMP Z, LOOP1
LOOP2:
 ADD PVAL000, 01
 OUTPUT PVAL000, PORT000
 JUMP LOOP
DELAY:
 LOAD DLYCNT1, FF
DELAY1:
 LOAD DLYCNT2, FF
DELAY2:
 SUB DLYCNT2, 01
 JUMP NZ, DELAY2
 SUB DLYCNT1, 01
 JUMP NZ, DELAY1
 RETURN

上記の例では、スイッチを押すたびにプロセッサ上のレジスタの値をインクリメントし、インクリメントした値を8ビットのポートに出力するようにしました。

[フレーム]

今回は非常に単純な例を示したので、「そんなのVHDLだけで良い」と言われかねませんが、プロセッサによる並列処理を介在させる事でハードウェア側の設計を単純にする事ができる例があります。I2CやSPIなど、同じプロトコルでも複数の種類があるような場合や、そもそもステートがあまりに多い場合にも、全体制御をプロセッサにさせる事で、ハードウェア側の設計が楽になる事は間違いありません。(ここでは性能について一切触れませんが。)

また、ソフト・コア・プロセッサが動作するというだけで、少しワクワクした気分になるのは自分だけではないでしょう。無償で得られる開発環境で、チクチクピコピコPicoBlazeいじりなんていうのも、たまには面白いのではないでしょうか。

AltStyle によって変換されたページ (->オリジナル) /