HTML5 Canvasで簡単な論理回路図を描いてみた
今回は、HTML5 CanvasのAPIを利用し、伝統的な論理ゲート記号によって簡単な論理回路図を描く手順について書き留める。
同じ論理ゲート記号を複数回使うことになるので、それらの論理ゲート記号を描く機能の一部をまずは
ここでは、オブジェクトを初期化するための特別な函数であるコンストラクター函数と呼ばれるものを使ってみた。コンストラクター函数はまるでクラスのように働き、これによって同じ種類の異なるオブジェクトを量産することができるようになる。
NANDゲート記号をコンストラクター函数化
実用論理ゲートだけによって基本論理ゲートと同じ機能を実現できることを示す論理回路図を描くため、NANDゲート記号のソースコードをコンストラクター函数にまとめてみた。
コンストラクター函数の引数としては、第1と第2に座標を、第3には輪郭線の太さを、第4には指定することができるようにしてみた。
こうすることで異なる属性を持つ複数のオブジェクトを一つの雛形から描画することができるようになる。
コンストラクター函数NoAndGateのソースコードは次のとおり。使っているスクリプト言語はJavaScript。
<script> /* コンストラクター函数でNoAndGateオブジェクトを定義 引数にはx座標, y座標, 輪郭線の太さ, 輪郭線の色 */ function NoAndGate(argX, argY, lineW, argColor) { //座標のプロパティ this.x = argX; this.y = argY; //輪郭線の太さのプロパティ this.width = lineW; //本体部分の輪郭線の色のプロパティ this.color = argColor; /* 2つの頭部直線先端と1つの尾部直線先端の座標を表わすプロパティ hornPoint1: 頭部(左端)の上の直線の先端の座標 hornPoint2: 頭部(左端)の下の直線の先端の座標 tailPoint: 尾部(右端)の先端の座標 */ this.hornPoint1X = x+0; this.hornPoint1Y = y+11; this.hornPoint2X = this.x+0; this.hornPoint2Y = this.y+35; this.tailPointX = this.x+82; this.tailPointY = this.y+22; //NANDゲート記号を描画するメソッド this.draw = function() { //輪郭線と色のプロパティ ctx.lineWidth = this.width; ctx.strokeStyle = color; //本体部分 ctx.beginPath(); ctx.moveTo(this.x+17, this.y+3); ctx.lineTo(this.x+38, this.y+3); ctx.bezierCurveTo( this.x+62, this.y+3, this.x+62, this.y+43, this.x+38, this.y+43); ctx.lineTo(this.x+17, this.y+43); ctx.lineTo(this.x+17, this.y+3); ctx.closePath(); ctx.stroke(); //尾部の円 ctx.beginPath(); ctx.arc(this.x+62, this.y+22, 4, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); ctx.stroke(); //本体に付属する3つの直線 ctx.strokeStyle = 'black'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(this.x+67, this.y+22); ctx.lineTo(this.x+82, this.y+22); ctx.moveTo(this.x+0, this.y+11); ctx.lineTo(this.x+17, this.y+11); ctx.moveTo(this.x+0, this.y+35); ctx.lineTo(this.x+17, this.y+35); ctx.stroke(); } } </script>
JavaScriptには通常のオブジェクト指向言語に実装されているクラスという概念はないものの、それに類似した擬似的な機能が用意されている。その1つにコンストラクター函数というものがあるらしい。
ここではNANDゲート記号を描く機能をそのコンストラクター函数へとまとめてみた。
函数を定義する予約語functionを用いてNoAndGateという函数名を与え、x座標を受け取るargXとy座標を受け取るargY、そしてNANDゲート本体の輪郭線の太さを受け取るlineW、その輪郭線の色を受け取るargColorを仮引数としてそれぞれ定義した。
{}で囲んだコンストラクター函数NoAndGateのブロック内では、引数として受け取った値を各種プロパティとして設定できるようにした。this.プロパティ名 = 仮引数名
がそのコードに当たる。
受け取った引数を格納するプロパティだけではなく、その他のプロパティも用意した。
this.hornPoint1X = x+0;
では、hornPoint1XというプロパティにNANDゲート記号に付属する3つの直線の内の1つの先端座標を設定している。同様にして他の付属線の先端の座標も設定した。この座標は後にそれらを直線でつなぎ合わせるときに利用できる情報となる。
this.draw = function() {...}
では、函数のコードそのものを直接代入して「オブジェクト名.draw()」というメソッドを作っている。驚くべきことにJavaScriptではこのようなことが可能であるらしい。これは函数式と呼ばれているらしい。代入した函数式のブロック内には、NANDゲート記号を描画するためのコードが収められている。
引数を受け取るプロパティと各種座標情報を記憶しておくプロパティとNANDゲート記号を描画するメソッドを持ったコンストラクター函数NoAndGateがこれで出来上がった。
NANDゲートだけでORゲートを作る論理回路図
このNoAndGate函数を使ってNANDゲート記号オブジェクトを複数作り出し、NANDゲートだけでORゲートと同じ機能を実現することができるようにした論理回路図を描いてみた。その完成図が次のとおり。
いろいろと微調整の余地があるが、それらしき回路図をなんとか描くことができた。
このソースコードは次のとおり。言語はJavaScript。
<div style="text-align: center"> <canvas id="notAndGate-only_diagram1" width="200" height="100"> あなたのブラウザはCanvas要素に対応していません。 </canvas> </div> <script> var canvas = document.getElementById("notAndGate-only_diagram1"); var ctx = canvas.getContext("2d"); /*オブジェクト化と描画メソッドdraw()を使用*/ var noAndGate1 = new NoAndGate(10, 0, 2, 'blue'); noAndGate1.draw(); var noAndGate2 = new NoAndGate(10, 50, 2, 'red'); noAndGate2.draw(); var noAndGate3 = new NoAndGate(92, 25, 2, 'green'); noAndGate3.draw(); /*残りの接続線を描く*/ ctx.lineWidth = 1; ctx.strokeStyle = 'black'; //青のgate1の尾部と緑のgate2の頭部1をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate1.tailPointX, noAndGate1.tailPointY); ctx.lineTo(noAndGate3.hornPoint1X, noAndGate3.hornPoint1Y); ctx.stroke(); //赤のgate2の尾部と緑のgate3の頭部2をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate2.tailPointX, noAndGate2.tailPointY); ctx.lineTo(noAndGate3.hornPoint2X, noAndGate3.hornPoint2Y); ctx.stroke(); //青のgate1の頭部2と青のgate1の頭部1をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate1.hornPoint2X, noAndGate1.hornPoint2Y); ctx.lineTo(noAndGate1.hornPoint1X, noAndGate1.hornPoint1Y); ctx.stroke(); //赤のgate2の頭部1と赤のGate2の頭部2をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate2.hornPoint1X, noAndGate2.hornPoint1Y); ctx.lineTo(noAndGate2.hornPoint2X, noAndGate2.hornPoint2Y); ctx.stroke(); //青のgate1の頭部1と頭部1の中点座標を計算して変数に代入 var gate1CenterY = (noAndGate1.hornPoint1Y + noAndGate1.hornPoint2Y)/2 //青のgate1の頭部の真ん中の結節点から左側へ伸びる直線 ctx.beginPath(); ctx.moveTo(noAndGate1.hornPoint1X, gate1CenterY); ctx.lineTo(0, gate1CenterY); ctx.stroke(); //赤のgate2の頭部1と頭部2の中点座標を計算して変数に代入 var gate2CenterY = (noAndGate2.hornPoint1Y + noAndGate2.hornPoint2Y)/2 //赤のgate2の頭部の真ん中の結節点から左側へ伸びる直線 ctx.beginPath(); ctx.moveTo(noAndGate2.hornPoint1X, gate2CenterY); ctx.lineTo(0, gate2CenterY); ctx.stroke(); /*接点の円を描く*/ //青のgate1の頭部の結節点 ctx.beginPath(); ctx.arc(noAndGate1.hornPoint1X, gate1CenterY, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); //fill()メソッドで円を塗りつぶす ctx.fill(); //赤のgate2の頭部の結節点 ctx.beginPath(); ctx.arc(noAndGate2.hornPoint1X, gate2CenterY, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); ctx.fill(); </script>
コンストラクター函数は予約語newによってオブジェクト化することができる。オブジェクト化とは通常のオブジェクト指向言語で云うところのインスタンス化のこと。その際に各種引数を与えることでそれぞれ異なる属性(プロパティ)の値を持つオブジェクトを量産することができる。
ここでは異なる属性値(プロパティ値)を持つNANDゲート記号のオブジェクトを3つ作り、draw()メソッドを用いてそれらを描画した。それぞれに色分けしてある。
次に、それら3つのNANDゲート記号をつなぎ合わせて論理回路図を描いてみた。
ここで役立つのがNoAndGate函数内部に収められている座標のプロパティ。NANDゲートには3つの直線が、その左側の頭部2つ、その右側の尾部1つに付属している。この先端同士を直線でつなぎ合わせることになるが、このときにそれらの直線の先端の座標を収めたNoAndGate函数のプロパティを利用する。
直線を引くにはHTML5 CanvasのAPIを用いる。HTMLソースのcanvas要素を取得し、かつ、2D描画コンテキストを取得するコードは、NoAndGate函数のオブジェクト化(インスタンス化)よりも前にコーディングしておく必要があることに要注意。
beginPath()メソッドでパスを初期化し、moveTo(x,y)メソッドで直線の描き始めの座標を決め、lineTo(x,y)メソッドで直線を引く終点になる座標を決めた。そうしておいてstroke()メソッドで描画した。
この要領でNANDゲート記号同士をすべてつなぎ合わせる。
直線と直線を結びつけた結節点は、そのことを明確に示す必要があるとき、黒い円で塗りつぶすことになっている。それらの円を描くには円弧を描くためのarc()メソッドを用いた。そして最後に円の中身を塗りつぶすためにfill()メソッドを利用した。
NANDゲートだけでXNORゲートを作る論理回路図
次の論理回路図は、NANDゲートだけを使ってXNORゲートと同じ機能を実現できることを示すもの。
このソースコードは次のとおり。言語はJavaScript。
<div style="text-align: center"> <canvas id="notAndGate-only_diagram2" width="280" height="150"> あなたのブラウザはCanvas要素に対応していません。 </canvas> </div> <script> var canvas = document.getElementById("notAndGate-only_diagram2"); var ctx = canvas.getContext("2d"); /*オブジェクト化と描画メソッドdraw()を使用*/ var noAndGate4 = new NoAndGate(20, 0, 2, 'purple'); noAndGate4.draw(); var noAndGate5 = new NoAndGate(30, 50, 2, 'blue'); noAndGate5.draw(); var noAndGate6 = new NoAndGate(30, 100, 2, 'red'); noAndGate6.draw(); var noAndGate7 = new NoAndGate(112, 75, 2, 'green'); noAndGate7.draw(); var noAndGate8 = new NoAndGate(194, 10, 2, 'brown'); noAndGate8.draw(); /*残りの接続線を描く*/ ctx.lineWidth = 1; ctx.strokeStyle = 'black'; //青のgate5の尾部と緑のgate7の頭部1をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate5.tailPointX, noAndGate5.tailPointY); ctx.lineTo(noAndGate7.hornPoint1X, noAndGate7.hornPoint1Y); ctx.stroke(); //赤のgate6の尾部と緑のgate7の頭部2をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate6.tailPointX, noAndGate6.tailPointY); ctx.lineTo(noAndGate7.hornPoint2X, noAndGate7.hornPoint2Y); ctx.stroke(); //青のgate5の頭部2と青のgate5の頭部1をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate5.hornPoint2X, noAndGate5.hornPoint2Y); ctx.lineTo(noAndGate5.hornPoint1X, noAndGate5.hornPoint1Y); ctx.stroke(); //赤のgate6の頭部1と赤のgate6の頭部2をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate6.hornPoint1X, noAndGate6.hornPoint1Y); ctx.lineTo(noAndGate6.hornPoint2X, noAndGate6.hornPoint2Y); ctx.stroke(); //青のgate5の頭部1と頭部2の中点座標を計算して変数に代入 var gate1CenterY = (noAndGate5.hornPoint1Y + noAndGate5.hornPoint2Y)/2 //青のgate5の頭部の真ん中の結節点から伸びる直線 ctx.beginPath(); ctx.moveTo(noAndGate5.hornPoint1X, gate1CenterY); ctx.lineTo(15, gate1CenterY); ctx.stroke(); //赤のgate6の頭部1と頭部2の中点座標を計算して変数に代入 var gate2CenterY = (noAndGate6.hornPoint1Y + noAndGate6.hornPoint2Y)/2 //赤のgate6の頭部の真ん中の結節点から伸びる直線 ctx.beginPath(); ctx.moveTo(noAndGate6.hornPoint1X, gate2CenterY); ctx.lineTo(10, gate2CenterY); ctx.stroke(); //gate4の頭部1の直線の延長 ctx.beginPath(); ctx.moveTo(noAndGate4.hornPoint1X, noAndGate4.hornPoint1Y); ctx.lineTo(0, noAndGate4.hornPoint1Y); ctx.stroke(); //gate4の頭部2の直線の延長 ctx.beginPath(); ctx.moveTo(noAndGate4.hornPoint2X, noAndGate4.hornPoint2Y); ctx.lineTo(0, noAndGate4.hornPoint2Y); ctx.stroke(); //gate5の頭部とgate4の頭部1をつなぐ ctx.beginPath(); ctx.moveTo(15, gate1CenterY); ctx.lineTo(15, noAndGate4.hornPoint1Y); ctx.stroke(); //gate6の頭部とgate4の頭部2をつなぐ ctx.beginPath(); ctx.moveTo(10, gate2CenterY); ctx.lineTo(10, noAndGate4.hornPoint2Y); ctx.stroke(); //gate8の頭部1とgate4の尾部をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate8.hornPoint1X, noAndGate8.hornPoint1Y); ctx.lineTo(noAndGate4.tailPointX, noAndGate4.tailPointY); ctx.stroke(); //gate8の頭部2とgate7の尾部をつなぐ ctx.beginPath(); ctx.moveTo(noAndGate8.hornPoint2X, noAndGate8.hornPoint2Y); ctx.lineTo(noAndGate7.tailPointX, noAndGate7.tailPointY); ctx.stroke(); /*結節点の円を描く*/ //gate5の頭部の結節点 ctx.beginPath(); ctx.arc(noAndGate5.hornPoint1X, gate1CenterY, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); //fill()メソッドで円を塗りつぶす ctx.fill(); //gate6の頭部の結節点 ctx.beginPath(); ctx.arc(noAndGate6.hornPoint1X, gate2CenterY, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); ctx.fill(); //gate4の頭部1の結節点 ctx.beginPath(); ctx.arc(noAndGate4.hornPoint1X-5, noAndGate4.hornPoint1Y, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); ctx.fill(); //gate4の頭部2の結節点 ctx.beginPath(); ctx.arc(noAndGate4.hornPoint1X-10, noAndGate4.hornPoint2Y, 2, (Math.PI/180)*0, (Math.PI/180)*360); ctx.closePath(); ctx.fill(); </script>
やや複雑だけれども、やっていることは先のコードとほぼ同じ。
フリップ=フロップも描いてみようと思ったが、またの機会にしようと思う。
MIL(軍事)規格の論理ゲート記号を用いた回路図は、このように極めて単純な回路図を描くとき以外には現在では推奨されていない。
IEC Symbolsと呼ばれる新しい形の論理ゲート記号を用いて描くか、あるいはまた実際の設計にはVerilogやVHDLといったハードウェア記述言語を用いるのがより現実的。
また現在では、VerilogやVHDLとは別に、より高次のハードウェア設計環境(Transaction-level Modeling)としてSystemCのようなクラス・ライブラリーがC++言語向けに提供されているようだ。ただし、SystemCは論理ゲートの記述には対応していない。これらについてもまたの機会に。
コメント
コメントを投稿