Pythonで作るなんちゃってNANDゲートシミュレータ



はじめに

Pythonで作るなんちゃってCインタプリタ の更新も途絶えて久しく放置したままに、 何となく別のことをぼちぼち試してみようかと。

皆さんNANDえすかー! NANDがあれば何でもできる。 この道を行けばどうなるものか。 行けば解るさ。ダー!

と言うわけで「Pythonで作るなんちゃってNANDゲートシミュレータ」です。

例によって趣味のプログラミングなので、実用性、効率は問いません。 全貌をすぐに把握できる程度に簡潔で、そこそこ動作すればそれで良しとします。


駆動方式の概略

ぼんやりと動作してる様を想像してみまするに、まずはNANDゲートのクラスがあって、2つの入力と1つの出力を持ってると。 そのインスタンスが複数あって、誰かの出力が誰かの入力へとつながっていると。

誰かの出力が変化したとして、その変化はつながってる先の他の誰かの入力へと伝えます。

伝えられた誰かは、2つの入力の値を調べて自分の出力を計算し、必要なら出力の値を変化させます。

その変化が、またさらにつながってる他の誰かに伝わり、、、と繰り返されます。

ぱっと思いつくのが、出力の値を保持する変数を用意して、つながってる先の入力はその変数が変化するのを待つ。

となると、スレッドの条件変数のパターンでは。

変数にロックを付けて、cond waitで寝て、出力側のスレッドが変数の値を変化させたら、cond signalで寝てる人を起こす。

実はこれ、ちょっと前に少し試してみました。 スレッドの数がめっさ膨大になります。 そらそうです。出力から入力への接続の数だけ対応するスレッドが要ります。

複数のスレッドが起床したいとき、どの接続に対応したスレッドが優先して動き出すべきか?

シミュレートするゲートの遅延まで含めて考慮しだすと、 だんだん手詰りになってきて、挫折しました。orz

この挫折の経験を踏まえて、次の方針でよりシンプルなものを目指してやりなおしてみます。

シングルスレッドなので、出力の値を保持する変数のロックも不要で、 メソッドを登録するキューのロックもまた不要。シンプルで何よりです。


最初の骨組み

だーいたい動きだした段階から、遡って骨組だけ抜き出して、まとめたソースコードです。

nand.py

#!/usr/bin/env python

import sys

class Obj:
	def __init__(self, parent, name):
		self.parent = parent
		self.name = name
		if parent:
			setattr(parent, name, self)

	def get_name(self):
		me = self.name if self.name else ''
		pa = self.parent.get_name() + '.' if self.parent else ''
		return pa + me

class Foo(Obj):
	def __init__(self, parent, latency=None):
		Obj.__init__(self, parent, 'foo')
		self.latency = latency
		self.v = 0
		sched.enque( latency, self.add, (0, ) )

	def add(self, a):
		self.v += a
		msg = '{} v={}'.format( self.get_name(), self.v )
		dbgout(msg)
		if self.v < 55:
			sched.enque( self.latency, self.add, (a+1,) )

class Sched:
	def __init__(self):
		self.que = []
		self.now = 0
	
	def enque(self, latency, f, args=() ):
		tm = self.now
		if latency:
			tm = tm + latency
		t = (tm, f, args)
		n = len(self.que)
		while n > 0:
			(tm_n, _, _) = self.que[ n - 1 ]
			if tm >= tm_n:
				break
			n -= 1
		self.que.insert(n, t)

	def main_loop(self):
		while len(self.que) > 0:
			(tm, f, args) = self.que.pop(0)
			self.now = tm
			f(*args)

def dbgout(s, always=False):
	if always or '-v' in sys.argv:
		print '{} {}'.format( sched.now, s )
		sys.stdout.flush()

if __name__ == "__main__":
	sched = Sched()

	o = Obj(None, 'o')
	Foo(o)

	slow = Obj(o, 'slow')
	Foo(slow, 10)

	slow_12 = Obj(o, 'slow_12')
	Foo(slow_12, 12)

	sched.main_loop()

# EOF


ちょー簡単なスケジューラから

class Sched:
	def __init__(self):
		self.que = []
		self.now = 0
	
# 空のキューを用意して、時刻を0に

# ここでは時間の単位はありません
# 時間が経過すると値が増えるという取り決めだけです


	def enque(self, latency, f, args=() ):
		tm = self.now
		if latency:
			tm = tm + latency
		t = (tm, f, args)
		n = len(self.que)
		while n > 0:
			(tm_n, _, _) = self.que[ n - 1 ]
			if tm >= tm_n:
				break
			n -= 1
		self.que.insert(n, t)

# キューに登録するメソッドです

# 実行させたい時刻を現在時刻からの経過時間として latency で指定します
# すぐに実行させたければ latency に None や 0 を指定します

# f は関数で、args は引数のタプルです

# hoge.foo(1, 2, 3) を実行させるなら
# f に hoge.foo を args に (1, 2, 3) を指定します

# hoge.foo(1) なら
# f に hoge.foo を args に (1,) を指定します

# hoge.foo() なら
# f に hoge.foo を args に () を指定します

# while の箇所で、latency から求めた実行時刻を調べて
# キューのどこに登録するか決めてます
# 時刻が同じなら、先に登録した方が先に実行されます


	def main_loop(self):
		while len(self.que) > 0:
			(tm, f, args) = self.que.pop(0)
			self.now = tm
			f(*args)

# キューからメソッドを取り出して実行するループです
# メソッド実行で、新たな登録が追加されることもなく、
# キューが空になれば終了です


お試し用のメソッドを持つクラス

class Obj:
	def __init__(self, parent, name):
		self.parent = parent
		self.name = name
		if parent:
			setattr(parent, name, self)

	def get_name(self):
		me = self.name if self.name else ''
		pa = self.parent.get_name() + '.' if self.parent else ''
		return pa + me

# 名前の階層を管理させたかったので
# Obj という底のクラスを用意して、そこで名前を扱ってます

# 親と自身の名前を指定してインスタンスを生成します
# 親が不要なら None を指定します
# 親が居れば、親側に自身名前のアトリビュートを設け、自身のインスタンスを保持させてます
# (これは、ちょっと分かりにくい仕組みになったかも...)

# get_name() では、親をたぐって先祖からの名前を '.' でつないで返します


class Foo(Obj):
	def __init__(self, parent, latency=None):
		Obj.__init__(self, parent, 'foo')
		self.latency = latency
		self.v = 0
		sched.enque( latency, self.add, (0, ) )

	def add(self, a):
		self.v += a
		msg = '{} v={}'.format( self.get_name(), self.v )
		dbgout(msg)
		if self.v < 55:
			sched.enque( self.latency, self.add, (a+1,) )

# お試し用のメソッドのクラスで、先の Obj から継承してます
# キューに登録するメソッドは add() です

# インスタンスを生成するときに、親と遅延時間を指定します
# 遅延時間は、メソッドを登録したときから、メソッドが実行されるまでの遅延時間です

# このクラスは初期値 0 で v という値を保持してます
# インスタンス生成時に、メソッド add() を引数 0 を指定して呼び出すように
# スケジューラに登録してます

# add() が呼び出されると、引数の値を v に加算して表示します
# v が 55 未満なら、さらに次の add() 呼び出しをスケジューラに登録します
# このとき引数の値を +1 してます

# スケジューラはグローバル変数 sched になってしまいました...
# この世界に一つのものだから、まぁいいか


def dbgout(s, always=False):
	if always or '-v' in sys.argv:
		print '{} {}'.format( sched.now, s )
		sys.stdout.flush()

# 先の add() で v の値を表示するのに使ってる関数です


そしてプログラムのメイン関数にあたる部分

if __name__ == "__main__":
	sched = Sched()

# スケジューラのインスタンスを生成して
# グローバル変数 sched に保持


	o = Obj(None, 'o')
	Foo(o)

# o という何もしない Obj を生成して
# o を親として Foo を生成

# Foo はインスタンスを生成すると
# メソッド add(0) 呼び出しをスケジューラに登録します

	slow = Obj(o, 'slow')
	Foo(slow, 10)


# 同様に、何もしない 'slow' という名前の Obj を o を親として生成して
# その slow を親に指定しつつ Foo を生成
# ただしこの場合、遅延時間 10 を指定してます


	slow_12 = Obj(o, 'slow_12')
	Foo(slow_12, 12)

# さらに同様に、遅延時間 12 を指定した場合を追加


	sched.main_loop()

# そして最後にスケジューラのメインループを呼び出しておしまい


実行してみます

$ /nand.py
$ 

# 何も表示されず無事? 終了。

# dbgout() 関数で '-v' オプションを指定したときだけ
# 表示するようにしてました ;-p)

$ ./nand.py -v
0 o.foo v=0
0 o.foo v=1
0 o.foo v=3
0 o.foo v=6
0 o.foo v=10
0 o.foo v=15
0 o.foo v=21
0 o.foo v=28
0 o.foo v=36
0 o.foo v=45
0 o.foo v=55
10 o.slow.foo v=0
12 o.slow_12.foo v=0
20 o.slow.foo v=1
24 o.slow_12.foo v=1
30 o.slow.foo v=3
36 o.slow_12.foo v=3
40 o.slow.foo v=6
48 o.slow_12.foo v=6
50 o.slow.foo v=10
60 o.slow_12.foo v=10
60 o.slow.foo v=15
70 o.slow.foo v=21
72 o.slow_12.foo v=15
80 o.slow.foo v=28
84 o.slow_12.foo v=21
90 o.slow.foo v=36
96 o.slow_12.foo v=28
100 o.slow.foo v=45
108 o.slow_12.foo v=36
110 o.slow.foo v=55
120 o.slow_12.foo v=45
132 o.slow_12.foo v=55
$ 

# 左端から、時刻、Fooインスタンスの名前、vの値です
# 最初に latency None のメソッド実行が、
# 時刻 0 の段階で全部終わって

# その後、latency 10 と 12 の 2つの Fooインスタンスが
# 指定の時刻にメソッドが呼び出されてるはずです

# 遅延時間 10 の方が、先に処理を終えて
# その後、遅延時間 12 の方も終了
# キューが空になり、スケジューラのメインループも終了


端子を付けてつないでみる

入力や出力の端子として、Pinクラスを追加します。

既出のFooクラスに入力端子、出力端子を一つずつ追加して、3つのFooクラスをつないでみました。

nand.py-1-diff.txt

--- nand.py-
+++ nand.py
@@ -9,25 +9,57 @@
 		if parent:
 			setattr(parent, name, self)
 
+	def dbgout(self, s, always=False):
+		dbgout( '{} {}'.format( self.get_name(), s ), always )
+
 	def get_name(self):
 		me = self.name if self.name else ''
 		pa = self.parent.get_name() + '.' if self.parent else ''
 		return pa + me
 	
# dbgout() 時に、いちいち get_name() の呼び出しを書くのが面倒なので、
# Objクラスにdbgout()メソッド追加しました。


+class Pin(Obj):
+	def __init__(self, parent, name, direc='IO', v=None):
+		Obj.__init__(self, parent, name)
+		self.direc = direc
+		self.v = v
+		self.conn = None
+

# 端子用のPinクラスを追加しました

# direcは端子の入力、出力の種別で、入力用なら'I'、出力なら'O'を指定します
# デフォルトは'IO'で入出力ということにしておきます

# v は初期状態の値で、True、Falseを指定します
# デフォルトは None で、いわゆる未接続(ハイ・インピーダンス)ということにしておきます

# メンバ conn は接続する別の端子(Pin)を保持します
# 生成時は None で初期化して、何もつながってない状態です


+	def connect(self, other):
+		self.conn = other
+		if other and other.conn != self:
+			other.conn = self
+

# 自身に、別の端子(Pin)をつなぐメソッドです
# 引数 other には相手の Pin インスタンスを指定します
# 相手側の conn には、問答無用で自身のインスタンスを設定します ;-p)

# 通常、出力端子には入力端子を、入力端子には出力端子をつなぐ事を想定してます


+	def set(self, v):
+		if v == self.v:
+			return
+		self.v = v
+		self.dbgout( str_v(v) )
+		if 'O' in self.direc and self.conn:
+			self.conn.set(v)
+		if 'I' in self.direc and self.parent and hasattr(self.parent, 'update'):
+			sched.enque(None, self.parent.update)
+

# 端子(Pin)に値をセットするメソッドです
# 既に指定の値がセット済みならば、何もしません

# 出力端子に値をセットすると、つながってる他の端子(通常は、入力端子)があれば、
# set()メソッドも呼び出して、同じ値をセットしようとします

# 入力端子に値をセットすると、
# その入力端子の親に居る場合は、親に端子の状態が変化した事を知らせるために、
# 親の update() メソッドの呼び出しを、スケジューラに登録します

# こちらは即座に呼び出さずに、スケジューラに latecy None で登録するようなってます
# これは、親が複数の入力端子を持っていて、それらの値をセットする処理が、
# スケジューラの実行を待ってる場合に備えてます


 class Foo(Obj):
 	def __init__(self, parent, latency=None):
 		Obj.__init__(self, parent, 'foo')
 		self.latency = latency
 		self.v = 0
+
+		Pin(self, 'inp', 'I')
+		Pin(self, 'out', 'O')
+
 		sched.enque( latency, self.add, (0, ) )
 
# 既出のお試しFooクラスに Pinを2つ追加しました
# Pinの名前 'inp', 'out' は、Pin の親である Foo に
# メンバ inp, out として追加されます
# (Objのコンストラクタでやってます... 分かりにくいかも)


 	def add(self, a):
 		self.v += a
-		msg = '{} v={}'.format( self.get_name(), self.v )
-		dbgout(msg)
+		self.dbgout( 'v={}'.format(self.v) )
 		if self.v < 55:
 			sched.enque( self.latency, self.add, (a+1,) )
 
# Foo.add() の表示を出すところを、Objのdbgout()メソッドを使うようにしました
# ここは、機能は変わりません


+	def update(self):
+		v = not self.inp.v
+		sched.enque( self.latency, self.out.set, (v,) )
+

# Fooクラスに update() メソッドを追加してます
# 追加した入力端子の値が変化したら呼び出されます
# 入力端子の値を見て、latency 分の時間経過後に、反転した値を出力端子にセットします

# 出力端子セットすべき値を決定した後、スケジューラに値を引数として登録してます
# latency経過後の入力端子の値ではなく、update()呼び出しの時点で決定した値が、
# スケジューラに登録されます


@@ -52,6 +84,9 @@
 			self.now = tm
 			f(*args)
 
+def str_v(v):
+	return { True: 'H', False: 'L', None: 'Hi-Z' }.get(v, '?')
+
 def dbgout(s, always=False):
 	if always or '-v' in sys.argv:
 		print '{} {}'.format( sched.now, s )

# 端子の値を表示するときの文字列を返す関数です


@@ -69,6 +104,10 @@
 	slow_12 = Obj(o, 'slow_12')
 	Foo(slow_12, 12)
 
+	o.foo.out.connect( o.slow.foo.inp )
+	o.slow.foo.out.connect( o.slow_12.foo.inp )
+	sched.enque( 200, o.foo.out.set, (True,) )
+
 	sched.main_loop()
 
# メイン関数部分への追加です

# 最初のFooインスタンス (o.foo 遅延 None) の出力端子に
# 2つめのFooインスタンス (o.slow.foo 遅延 10) の入力端子をつなぎ

# 2つめのFooインスタンス (o.slow.foo 遅延 10) の出力端子に
# 3つめのFooインスタンス (o.slow.foo_12 遅延 12) の入力端子をつなぎ

# 最初のFooインスタンス (o.foo 遅延 None) の出力端子を、
# None から True にするためのメソッド呼び出しを
# 時間 200 の遅延を指定して、スケジューラに登録してます

それでは実行してみます。

$ cat nand.py-1-diff.txt | patch

# パッチをあてて


$ ./nand.py -v
0 o.foo v=0
0 o.foo v=1
0 o.foo v=3
0 o.foo v=6
0 o.foo v=10
0 o.foo v=15
0 o.foo v=21
0 o.foo v=28
0 o.foo v=36
0 o.foo v=45
0 o.foo v=55
10 o.slow.foo v=0
12 o.slow_12.foo v=0
20 o.slow.foo v=1
24 o.slow_12.foo v=1
30 o.slow.foo v=3
36 o.slow_12.foo v=3
40 o.slow.foo v=6
48 o.slow_12.foo v=6
50 o.slow.foo v=10
60 o.slow_12.foo v=10
60 o.slow.foo v=15
70 o.slow.foo v=21
72 o.slow_12.foo v=15
80 o.slow.foo v=28
84 o.slow_12.foo v=21
90 o.slow.foo v=36
96 o.slow_12.foo v=28
100 o.slow.foo v=45
108 o.slow_12.foo v=36
110 o.slow.foo v=55
120 o.slow_12.foo v=45
132 o.slow_12.foo v=55
200 o.foo.out H
200 o.slow.foo.inp H
210 o.slow.foo.out L
210 o.slow_12.foo.inp L
222 o.slow_12.foo.out H
$ 


# 末尾の時刻 200 以降が、追加した端子の変化になります

200 o.foo.out H
200 o.slow.foo.inp H
210 o.slow.foo.out L
210 o.slow_12.foo.inp L
222 o.slow_12.foo.out H

# 時刻200 で 1つめFooの出力端子が H に変わり
# つながってる2つめのFooの入力端子が即座に H になります

# 時間10が経過して
# 時刻210 で2つめのFooの出力端子が L に変わり
# つながってる3つめのFooの入力端子が即座に L になります

# 時間12が経過して
# 時刻222 で3つめのFooの出力端子が H に変わり、終了

とりあえずNANDゲートではなく、NOTゲート風な動作です。


満を持してNANDゲートクラス

ようやく本題のNANDゲートの実装の追加です。

nand.py-2-diff.txt

--- nand.py-
+++ nand.py
@@ -3,9 +3,10 @@
 import sys
 
 class Obj:
-	def __init__(self, parent, name):
+	def __init__(self, parent, name, latency=None):
 		self.parent = parent
 		self.name = name
+		self.latency = latency
 		if parent:
 			setattr(parent, name, self)
 
@@ -17,6 +18,9 @@
 		pa = self.parent.get_name() + '.' if self.parent else ''
 		return pa + me
 
+	def enque(self, f, args=() ):
+		sched.enque(self.latency, f, args)
+
 class Pin(Obj):
 	def __init__(self, parent, name, direc='IO', v=None):
 		Obj.__init__(self, parent, name)

# 底のクラスObjに latency を追加しました
# ここで latency を保持するようにして

# スケジューラへの登録メソッドも追加
# スケジューラに登録するときに、保持してるlatency を指定します


@@ -37,28 +41,39 @@
 		if 'O' in self.direc and self.conn:
 			self.conn.set(v)
 		if 'I' in self.direc and self.parent and hasattr(self.parent, 'update'):
-			sched.enque(None, self.parent.update)
+			self.enque(self.parent.update)
 
 class Foo(Obj):
 	def __init__(self, parent, latency=None):
-		Obj.__init__(self, parent, 'foo')
-		self.latency = latency
+		Obj.__init__(self, parent, 'foo', latency)
 		self.v = 0
 
 		Pin(self, 'inp', 'I')
 		Pin(self, 'out', 'O')
 
-		sched.enque( latency, self.add, (0, ) )
+		self.enque( self.add, (0, ) )
 
 	def add(self, a):
 		self.v += a
 		self.dbgout( 'v={}'.format(self.v) )
 		if self.v < 55:
-			sched.enque( self.latency, self.add, (a+1,) )
+			self.enque( self.add, (a+1,) )
 
 	def update(self):
 		v = not self.inp.v
-		sched.enque( self.latency, self.out.set, (v,) )
+		self.enque( self.out.set, (v,) )
+

# Pinクラス、Fooクラスでしてた latency への考慮は、
# 全て Objクラスにまかせるようにしました
# ここは、機能は変わりません


+class NAND(Obj):
+	def __init__(self, parent, name='nand', latency=10):
+		Obj.__init__(self, parent, name, latency)
+		Pin(self, 'out', 'O')
+		Pin(self, 'inp_a', 'I')
+		Pin(self, 'inp_b', 'I')
+
+	def update(self):
+		self.dbgout('update')
+		v = not ( self.inp_a.v and self.inp_b.v )
+		self.enque( self.out.set, (v,) )
 
 class Sched:
 	def __init__(self):

# ようやく本題の NANDクラスの追加です
# 2つの入力端子と1つの出力端子

# latency のデフォルトは、とりあえず値 10 としてます
# 入力端子が変化したときに呼び出される update() メソッドでは
# 2つの入力端子の値から、NAND の出力を算出し
# latency の遅延で出力端子に値をセットします

# まぁ、この説明そのものが NANDの動作ですよね


@@ -70,6 +85,10 @@
 		if latency:
 			tm = tm + latency
 		t = (tm, f, args)
+
+		if self.exist_same(t):
+			return
+
 		n = len(self.que)
 		while n > 0:
 			(tm_n, _, _) = self.que[ n - 1 ]
@@ -78,6 +97,9 @@
 			n -= 1
 		self.que.insert(n, t)
 
+	def exist_same(self, t):
+		return next( ( True for a in self.que if a == t ), False )
+
 	def main_loop(self):
 		while len(self.que) > 0:
 			(tm, f, args) = self.que.pop(0)

# スケジューラの登録時に呼び出すメソッドの重複のチェックを入れました
# 同じ時刻に同じ引数で同じメソッドを呼び出す登録について、はじくようにしました


@@ -108,6 +130,13 @@
 	o.slow.foo.out.connect( o.slow_12.foo.inp )
 	sched.enque( 200, o.foo.out.set, (True,) )
 
+	NAND(o)
+	sched.enque( 300, o.nand.inp_a.set, (False,) )
+	sched.enque( 300, o.nand.inp_b.set, (False,) )
+	sched.enque( 320, o.nand.inp_a.set, (True,) )
+	sched.enque( 340, o.nand.inp_b.set, (True,) )
+	sched.enque( 360, o.nand.inp_a.set, (False,) )
+
 	sched.main_loop()
 
 # EOF

# メイン関数部分末尾で追加したNANDクラスを試してます
# インスタンスを1つ生成して
# 時刻 300 以降に、inp_a, inp_bの2つの入力端子を、あれこれ変化させてます

それでは実行してみます。

$ cat nand.py-2-diff.txt | patch

$ ./nand.py -v
0 o.foo v=0
0 o.foo v=1
0 o.foo v=3
0 o.foo v=6
0 o.foo v=10
0 o.foo v=15
0 o.foo v=21
0 o.foo v=28
0 o.foo v=36
0 o.foo v=45
0 o.foo v=55
10 o.slow.foo v=0
12 o.slow_12.foo v=0
20 o.slow.foo v=1
24 o.slow_12.foo v=1
30 o.slow.foo v=3
36 o.slow_12.foo v=3
40 o.slow.foo v=6
48 o.slow_12.foo v=6
50 o.slow.foo v=10
60 o.slow_12.foo v=10
60 o.slow.foo v=15
70 o.slow.foo v=21
72 o.slow_12.foo v=15
80 o.slow.foo v=28
84 o.slow_12.foo v=21
90 o.slow.foo v=36
96 o.slow_12.foo v=28
100 o.slow.foo v=45
108 o.slow_12.foo v=36
110 o.slow.foo v=55
120 o.slow_12.foo v=45
132 o.slow_12.foo v=55
200 o.foo.out H
200 o.slow.foo.inp H
210 o.slow.foo.out L
210 o.slow_12.foo.inp L
222 o.slow_12.foo.out H
300 o.nand.inp_a L
300 o.nand.inp_b L
300 o.nand update
310 o.nand.out H
320 o.nand.inp_a H
320 o.nand update
340 o.nand.inp_b H
340 o.nand update
350 o.nand.out L
360 o.nand.inp_a L
360 o.nand update
370 o.nand.out H
$ 


# 時刻 300未満は、おかわりなく
# 末尾の時刻 300 以降が、追加したNANDゲートの変化になります

300 o.nand.inp_a L
300 o.nand.inp_b L
300 o.nand update
310 o.nand.out H
320 o.nand.inp_a H
320 o.nand update
340 o.nand.inp_b H
340 o.nand update
350 o.nand.out L
360 o.nand.inp_a L
360 o.nand update
370 o.nand.out H

# 入力が2つとも H のときだけ出力が L に落ちてます
# OKです


ジョイントで分岐してつないでみる

1つの出力端子の結果を、複数の入力端子につないでみたいので、 jointというクラスを追加してみます。

ジョイントには最初端子はありません。

端子の方向を指定して端子を生成するメソッドを呼び出したり、 他の端子とつなぐメソッドを呼び出すと、 ジョイントが保持する端子が内部に生成されるようにします。

複数の出力端子から、ジョイント内の入力端子へとつながっている場合、 それらの出力の値の OR をとった値を、ジョイント全体の値とします。

そうして決定した値を、ジョイント内の出力端子群に設定してまわり、 接続先外部に出力します。

nand.py-3-diff.txt

--- nand.py-
+++ nand.py
@@ -34,15 +34,19 @@
 			other.conn = self
 
 	def set(self, v):
+		v = bool_v(v)
 		if v == self.v:
 			return
 		self.v = v
 		self.dbgout( str_v(v) )
-		if 'O' in self.direc and self.conn:
+		if self.has('O') and self.conn:
 			self.conn.set(v)
-		if 'I' in self.direc and self.parent and hasattr(self.parent, 'update'):
+		if self.has('I') and self.parent and hasattr(self.parent, 'update'):
 			self.enque(self.parent.update)
 
+	def has(self, direc): # direc 'I' or 'O'
+		return direc in self.direc
+
 class Foo(Obj):
 	def __init__(self, parent, latency=None):
 		Obj.__init__(self, parent, 'foo', latency)

# Pinクラスの set() メソッドで bool 以外に 'H', 'L', 'Hi-Z' の指定を許すようにしました
# 端子の入出力方向を判定する has() メソッドを追加しました


@@ -75,6 +79,40 @@
 		v = not ( self.inp_a.v and self.inp_b.v )
 		self.enque( self.out.set, (v,) )
 
+class Joint(Obj):
+	def __init__(self, parent, name='jt', latency=None):
+		Obj.__init__(self, parent, name, latency)
+		self.pins = []
+
+	def connect(self, other_pin):
+		direc = { 'I': 'O', 'O': 'I' }.get(other_pin.direc, 'IO')
+		pin = self.new_pin(direc)
+		pin.connect(other_pin)
+
+	def new_pin(self, direc='IO'):
+		i = len(self.pins)
+		name = 'pin{}_{}'.format(i, direc)
+		pin = Pin(self, name, direc)
+		self.pins.append(pin)
+		return pin
+
+	def update(self):
+		ivs = [ pin.v for pin in self.pins if pin.has('I') ]
+		f = lambda r, v: r if v == None else ( v if r == None else r or v ) # OR
+		v = reduce(f, ivs, None)
+		for pin in self.pins:
+			if pin.has('O'):
+				pin.set(v)
+

# 配線を分岐するための Jointクラスを追加しました
# new_pin() メソッドで、指定の向きの端子を自身側に生成して追加し返します

# connect() メソッドでは、つなぎたい端子を指定します
# 指定の端子とつなぐための入出力方向が逆の端子を自身側に生成し、
# 指定の端子と接続します

# 自身側の入力端子のどれかが変化したときに呼び出される update()メソッドでは
# 値が入力端子群の中から、None 以外の値のもので OR をとった値を、
# Joint全体の値として決定します

# 入力方向の端子が無かったり、あっても値が None ばかりなら、全体の値も None です

# 決定したJoint全体の値を、各出力端子に latency の遅延後にセットするよう
# スケジューラに登録します


+class Lamp(Obj):
+	def __init__(self, parent, name='lamp', latency=None):
+		Obj.__init__(self, parent, name, latency)
+		Pin(self, 'inp', 'I')
+
+	def update(self):
+		msg = str_v( self.inp.v )
+		self.enque( self.dbgout, (msg, True) )
+
 class Sched:
 	def __init__(self):
 		self.que = []

# 入力端子が一つあるだけの「ランプ」クラスです
# 入力端子の状態が変化すると、
# latencyの遅延後に、'L' または 'H' を表示します

# dbgout メソッドで always=True を指定してるので、
# -v オプションをつけてなくても、必ず表示します


@@ -109,6 +147,9 @@
 def str_v(v):
 	return { True: 'H', False: 'L', None: 'Hi-Z' }.get(v, '?')
 
+def bool_v(v):
+	return {'H': True, 'L': False, 'Hi-Z': None }.get(v, v)
+
 def dbgout(s, always=False):
 	if always or '-v' in sys.argv:
 		print '{} {}'.format( sched.now, s )

# Pinクラスの set()メソッドで、 'H', 'L', 'Hi-Z' を許すために追加した関数です
# 既出の str_v() と逆向きの変換です


@@ -128,14 +169,24 @@
 
 	o.foo.out.connect( o.slow.foo.inp )
 	o.slow.foo.out.connect( o.slow_12.foo.inp )
-	sched.enque( 200, o.foo.out.set, (True,) )
+	sched.enque( 200, o.foo.out.set, ('H',) )
 
 	NAND(o)
-	sched.enque( 300, o.nand.inp_a.set, (False,) )
-	sched.enque( 300, o.nand.inp_b.set, (False,) )
-	sched.enque( 320, o.nand.inp_a.set, (True,) )
-	sched.enque( 340, o.nand.inp_b.set, (True,) )
-	sched.enque( 360, o.nand.inp_a.set, (False,) )
+	sched.enque( 300, o.nand.inp_a.set, ('L',) )
+	sched.enque( 300, o.nand.inp_b.set, ('L',) )
+	sched.enque( 320, o.nand.inp_a.set, ('H',) )
+	sched.enque( 340, o.nand.inp_b.set, ('H',) )
+	sched.enque( 360, o.nand.inp_a.set, ('L',) )
+

# メイン関数部分の set() メソッド呼び出しで、
# bool を 'L', 'H' 指定に変更してみてます


+	test1 = Obj(o, 'test1')
+	NAND(test1)
+	Joint(test1)
+	Lamp(test1)
+	o.test1.jt.connect( o.test1.nand.out )
+	o.test1.jt.connect( o.test1.nand.inp_a )
+	o.test1.jt.new_pin('O').connect( o.test1.lamp.inp )
+	sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )
+	sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )
 
 	sched.main_loop()

# ここが今回のお試し追加分です
# 'test1' という名前の基板(?) に
# NAND, Joint, Lamp をのっけておいて

# JointにNAND出力をつなぎ
# JointにNAND入力の一方をつなぎ
# Jointに出力端子を一つ追加して、その先にLampの入力端子をつなぎ

# 時刻 400 で NANDのもう一方の入力端子を 'H' にセット
# 時刻 500 で NANDのもう一方の入力端子を 'L' にセット

それでは実行してみます。

$ cat nand.py-3-diff.txt | patch

$ ./nand.py -v
0 o.foo v=0
0 o.foo v=1
0 o.foo v=3
0 o.foo v=6
0 o.foo v=10
0 o.foo v=15
0 o.foo v=21
0 o.foo v=28
0 o.foo v=36
0 o.foo v=45
0 o.foo v=55
10 o.slow.foo v=0
12 o.slow_12.foo v=0
20 o.slow.foo v=1
24 o.slow_12.foo v=1
30 o.slow.foo v=3
36 o.slow_12.foo v=3
40 o.slow.foo v=6
48 o.slow_12.foo v=6
50 o.slow.foo v=10
60 o.slow_12.foo v=10
60 o.slow.foo v=15
70 o.slow.foo v=21
72 o.slow_12.foo v=15
80 o.slow.foo v=28
84 o.slow_12.foo v=21
90 o.slow.foo v=36
96 o.slow_12.foo v=28
100 o.slow.foo v=45
108 o.slow_12.foo v=36
110 o.slow.foo v=55
120 o.slow_12.foo v=45
132 o.slow_12.foo v=55
200 o.foo.out H
200 o.slow.foo.inp H
210 o.slow.foo.out L
210 o.slow_12.foo.inp L
222 o.slow_12.foo.out H
300 o.nand.inp_a L
300 o.nand.inp_b L
300 o.nand update
310 o.nand.out H
320 o.nand.inp_a H
320 o.nand update
340 o.nand.inp_b H
340 o.nand update
350 o.nand.out L
360 o.nand.inp_a L
360 o.nand update
370 o.nand.out H
400 o.test1.nand.inp_b H
400 o.test1.nand update
410 o.test1.nand.out H
410 o.test1.jt.pin0_I H
410 o.test1.jt.pin1_O H
410 o.test1.nand.inp_a H
410 o.test1.jt.pin2_O H
410 o.test1.lamp.inp H
410 o.test1.nand update
410 o.test1.lamp H
420 o.test1.nand.out L
420 o.test1.jt.pin0_I L
420 o.test1.jt.pin1_O L
420 o.test1.nand.inp_a L
420 o.test1.jt.pin2_O L
420 o.test1.lamp.inp L
420 o.test1.nand update
420 o.test1.lamp L
430 o.test1.nand.out H
430 o.test1.jt.pin0_I H
430 o.test1.jt.pin1_O H
430 o.test1.nand.inp_a H
430 o.test1.jt.pin2_O H
430 o.test1.lamp.inp H
430 o.test1.nand update
430 o.test1.lamp H
440 o.test1.nand.out L
440 o.test1.jt.pin0_I L
440 o.test1.jt.pin1_O L
440 o.test1.nand.inp_a L
440 o.test1.jt.pin2_O L
440 o.test1.lamp.inp L
440 o.test1.nand update
440 o.test1.lamp L
450 o.test1.nand.out H
450 o.test1.jt.pin0_I H
450 o.test1.jt.pin1_O H
450 o.test1.nand.inp_a H
450 o.test1.jt.pin2_O H
450 o.test1.lamp.inp H
450 o.test1.nand update
450 o.test1.lamp H
460 o.test1.nand.out L
460 o.test1.jt.pin0_I L
460 o.test1.jt.pin1_O L
460 o.test1.nand.inp_a L
460 o.test1.jt.pin2_O L
460 o.test1.lamp.inp L
460 o.test1.nand update
460 o.test1.lamp L
470 o.test1.nand.out H
470 o.test1.jt.pin0_I H
470 o.test1.jt.pin1_O H
470 o.test1.nand.inp_a H
470 o.test1.jt.pin2_O H
470 o.test1.lamp.inp H
470 o.test1.nand update
470 o.test1.lamp H
480 o.test1.nand.out L
480 o.test1.jt.pin0_I L
480 o.test1.jt.pin1_O L
480 o.test1.nand.inp_a L
480 o.test1.jt.pin2_O L
480 o.test1.lamp.inp L
480 o.test1.nand update
480 o.test1.lamp L
490 o.test1.nand.out H
490 o.test1.jt.pin0_I H
490 o.test1.jt.pin1_O H
490 o.test1.nand.inp_a H
490 o.test1.jt.pin2_O H
490 o.test1.lamp.inp H
490 o.test1.nand update
490 o.test1.lamp H
500 o.test1.nand.inp_b L
500 o.test1.nand.out L
500 o.test1.jt.pin0_I L
500 o.test1.nand update
500 o.test1.jt.pin1_O L
500 o.test1.nand.inp_a L
500 o.test1.jt.pin2_O L
500 o.test1.lamp.inp L
500 o.test1.nand update
500 o.test1.lamp L
510 o.test1.nand.out H
510 o.test1.jt.pin0_I H
510 o.test1.jt.pin1_O H
510 o.test1.nand.inp_a H
510 o.test1.jt.pin2_O H
510 o.test1.lamp.inp H
510 o.test1.nand update
510 o.test1.lamp H
$ 

# 時刻 400 未満は、おかわりなく
# 時刻 400 以降が、追加した「お試し」の結果です

# デバッグ出力おおすぎるので -v なしで実行してみます

$ ./nand.py               
410 o.test1.lamp H
420 o.test1.lamp L
430 o.test1.lamp H
440 o.test1.lamp L
450 o.test1.lamp H
460 o.test1.lamp L
470 o.test1.lamp H
480 o.test1.lamp L
490 o.test1.lamp H
500 o.test1.lamp L
510 o.test1.lamp H
$ 

# やりたかった事はこれです
# 時刻 400 で NANDの一方の入力を 'H' にセットしてるので
# 以降、この NANDは、もう一方の入力について、NOTとして振舞います。

# 時間 10 の遅延で 出力が 'H' になり、その先のランプが 'H'
# 出力はJointで分岐して、NOTとして振舞う入力に戻されます。

# NOTなので、さらに時間 10 遅延で今度は出力が 'L'

# 時間 500 で NANDの一方の入力を 'L' にセットするまで、
# この発振が続いてます
# OKです


時間の単位

ここまで時間の単位は気にせずやってきました。

実際のNANDゲートのlatencyは、10ナノ秒とかのオーダーです。

+class NAND(Obj):
+	def __init__(self, parent, name='nand', latency=10):
+		Obj.__init__(self, parent, name, latency)
+		Pin(self, 'out', 'O')
+		Pin(self, 'inp_a', 'I')
+		Pin(self, 'inp_b', 'I')

ここでのデフォルト値の指定を10にしてるので、 そのまま値をナノ秒として考えれば良さそうではありますが...

ミリ(1/10^3)、マイクロ(1/10^6)、ナノ(1/10^9)
キロ(10^3)、メガ(10^6)、ギガ(10^9)

32ビットの整数で保持してるかどうかは処理系に依存かもしれないですが
まぁだいたい -2ギガ から 2ギガ

2 * 1024 * 1024 * 1024

を超えると負の値になって破綻します

2ギガナノ秒って、2秒!!!

ここまで、500ナノ秒くらいまでしか扱ってなかったのですが、 32ビットの整数で保持されるとすると、2秒が限界。

これはなんとかせねばなりません。

ということで、秒とナノ秒の2つの整数の組み(tuple)で保持するように変更してみます。

nand.py-4-diff.txt

--- nand.py-
+++ nand.py
@@ -116,12 +116,12 @@
 class Sched:
 	def __init__(self):
 		self.que = []
-		self.now = 0
+		self.now = tm_tup(0)
 	
 	def enque(self, latency, f, args=() ):
 		tm = self.now
 		if latency:
-			tm = tm + latency
+			tm = tm_add( tm, tm_tup(latency) )
 		t = (tm, f, args)
 
 		if self.exist_same(t):

# 時間の中身を扱うのはほぼスケジューラだけです
# Objは与えられたものを保持してスケジューラに渡すだけです

# 後述のtm_tup()関数でnowの初期値を生成するようにしてます

# 現在時刻にlatency時間を足す処理は、後述のtm_add()関数を使うようにしてます


@@ -130,7 +130,7 @@
 		n = len(self.que)
 		while n > 0:
 			(tm_n, _, _) = self.que[ n - 1 ]
-			if tm >= tm_n:
+			if tm_gt(tm, tm_n) or tm == tm_n:
 				break
 			n -= 1
 		self.que.insert(n, t)

# 時刻の比較処理も、後述のtm_gt()関数を使います
# 一致判定はtupleなのでそのまま'=='で


@@ -150,9 +150,25 @@
 def bool_v(v):
 	return {'H': True, 'L': False, 'Hi-Z': None }.get(v, v)
 
+def tm_tup(v):
+	return (0, v) if type(v) == int else v
+

# v が整数ならナノ秒として扱い(0秒, vナノ秒)のtupleを返します
# v が整数でなければtupleとみなして、そのままの値を返します


+def tm_gt( (s1,n1), (s2,n2) ):
+	return n1 > n2 if s1 == s2 else s1 > s2
+

# greater than です
# (s1,n1) が (s2,n2) より大きか判定します


+def tm_add( (s1,n1), (s2,n2) ):
+	giga = 1000000000
+	n1 += n2
+	s1 += s2 + n1 / giga
+	n1 %= giga
+	return (s1, n1)
+

# 足し算です


+def tm_str( (s,n) ):
+	return '{}.{:0>9}'.format(s, n)
+
 def dbgout(s, always=False):
 	if always or '-v' in sys.argv:
-		print '{} {}'.format( sched.now, s )
+		print '{} {}'.format( tm_str(sched.now), s )
 		sys.stdout.flush()
 
# (秒, ナノ秒)形式の表示関連です


 if __name__ == "__main__":
@@ -188,6 +204,8 @@
 	sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )
 	sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )
 
+	sched.enque( (2,0), o.test1.lamp.inp.set, ('L',) )
+
 	sched.main_loop()
 
 # EOF

# 最後に時刻2秒にランプの入力端子を'L'に落とす
# メソッド呼び出しの登録を追加してみます

それでは実行してみます。

$ cat nand.py-4-diff.txt | patch

$ ./nand.py
0.000000410 o.test1.lamp H
0.000000420 o.test1.lamp L
0.000000430 o.test1.lamp H
0.000000440 o.test1.lamp L
0.000000450 o.test1.lamp H
0.000000460 o.test1.lamp L
0.000000470 o.test1.lamp H
0.000000480 o.test1.lamp L
0.000000490 o.test1.lamp H
0.000000500 o.test1.lamp L
0.000000510 o.test1.lamp H
2.000000000 o.test1.lamp L
$ 

# 時間の表示が変わり、時刻2秒でランプ消灯
# OKです


端子クラスの修正

端子のクラスを見直してみると、入力端子の場合はconnの先につながっている他の端子について、 無関心です。

Pinクラスのset()メソッドで、出力端子はconnの先の他の端子のset()メソッドを呼び出して、 変化を伝えますが、入力端子は親のupdate()メソッド呼び出しをスケジューラに登録するだけです。

端子の方向をメンバ direc に保持してましたが、入力端子の場合は conn が None の場合として 判定することにすれば direc は廃止できそうです。

Pinクラス生成時に明示的に入力、出力の指定はせず、 メンバ conn に別の端子がつながっていれば、それに値の変化を伝え、 何もつながってなければ、親に変化を伝えることにします。

nand.py-5-diff.txt

--- nand.py-
+++ nand.py
@@ -22,16 +22,12 @@
 		sched.enque(self.latency, f, args)
 
 class Pin(Obj):
-	def __init__(self, parent, name, direc='IO', v=None):
+	def __init__(self, parent, name, v=None):
 		Obj.__init__(self, parent, name)
-		self.direc = direc
-		self.v = v
+		self.v = None
 		self.conn = None
-
-	def connect(self, other):
-		self.conn = other
-		if other and other.conn != self:
-			other.conn = self
+		if v != None:
+			self.enque( self.set, (v,) )
 
 	def set(self, v):
 		v = bool_v(v)

# Pinクラスから direc を廃止

# ついでに v が指定された時は、
# スケジューラに値のセットを登録するようにしました


@@ -39,21 +35,18 @@
 			return
 		self.v = v
 		self.dbgout( str_v(v) )
-		if self.has('O') and self.conn:
+		if self.conn:
 			self.conn.set(v)
-		if self.has('I') and self.parent and hasattr(self.parent, 'update'):
+		elif self.parent and hasattr(self.parent, 'update'):
 			self.enque(self.parent.update)
 
-	def has(self, direc): # direc 'I' or 'O'
-		return direc in self.direc
-

# Pinクラスの set() メソッドの処理の修正です
# has() メソッドを使うまでもなく、conn の有無で処理を分岐するようにしました
# has() メソッドも廃止です


 class Foo(Obj):
 	def __init__(self, parent, latency=None):
 		Obj.__init__(self, parent, 'foo', latency)
 		self.v = 0
 
-		Pin(self, 'inp', 'I')
-		Pin(self, 'out', 'O')
+		Pin(self, 'inp')
+		Pin(self, 'out')
 
 		self.enque( self.add, (0, ) )
 
@@ -70,9 +63,9 @@
 class NAND(Obj):
 	def __init__(self, parent, name='nand', latency=10):
 		Obj.__init__(self, parent, name, latency)
-		Pin(self, 'out', 'O')
-		Pin(self, 'inp_a', 'I')
-		Pin(self, 'inp_b', 'I')
+		Pin(self, 'out')
+		Pin(self, 'inp_a')
+		Pin(self, 'inp_b')
 
 	def update(self):
 		self.dbgout('update')

# Pinインスタンス生成箇所で direc の 'I', 'O' 指定を削除してます


@@ -84,30 +77,25 @@
 		Obj.__init__(self, parent, name, latency)
 		self.pins = []
 
-	def connect(self, other_pin):
-		direc = { 'I': 'O', 'O': 'I' }.get(other_pin.direc, 'IO')
-		pin = self.new_pin(direc)
-		pin.connect(other_pin)
-
-	def new_pin(self, direc='IO'):
+	def new_pin(self):
 		i = len(self.pins)
-		name = 'pin{}_{}'.format(i, direc)
-		pin = Pin(self, name, direc)
+		name = 'pin{}'.format(i)
+		pin = Pin(self, name)
 		self.pins.append(pin)
 		return pin
 
 	def update(self):
-		ivs = [ pin.v for pin in self.pins if pin.has('I') ]
+		ivs = [ pin.v for pin in self.pins if not pin.conn ]
 		f = lambda r, v: r if v == None else ( v if r == None else r or v ) # OR
 		v = reduce(f, ivs, None)
 		for pin in self.pins:
-			if pin.has('O'):
+			if pin.conn:
 				pin.set(v)
 
# Jointクラスの関連箇所の修正です
# connect() メソッドは廃止
# new_pin() メソッドも direc 指定は廃止
# Pinクラスのhas()メソッドは廃止済みなので、connの有無の判定に変更してます


 class Lamp(Obj):
 	def __init__(self, parent, name='lamp', latency=None):
 		Obj.__init__(self, parent, name, latency)
-		Pin(self, 'inp', 'I')
+		Pin(self, 'inp')
 
 	def update(self):
 		msg = str_v( self.inp.v )

# ランプクラスのPin生成箇所も direc 指定削除です


@@ -183,8 +171,8 @@
 	slow_12 = Obj(o, 'slow_12')
 	Foo(slow_12, 12)
 
-	o.foo.out.connect( o.slow.foo.inp )
-	o.slow.foo.out.connect( o.slow_12.foo.inp )
+	o.foo.out.conn = o.slow.foo.inp
+	o.slow.foo.out.conn = o.slow_12.foo.inp
 	sched.enque( 200, o.foo.out.set, ('H',) )
 
 	NAND(o)
@@ -198,13 +186,13 @@
 	NAND(test1)
 	Joint(test1)
 	Lamp(test1)
-	o.test1.jt.connect( o.test1.nand.out )
-	o.test1.jt.connect( o.test1.nand.inp_a )
-	o.test1.jt.new_pin('O').connect( o.test1.lamp.inp )
+	o.test1.nand.out.conn = o.test1.jt.new_pin()
+	o.test1.jt.new_pin().conn = o.test1.nand.inp_a
+	o.test1.jt.new_pin().conn = o.test1.lamp.inp
+
 	sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )
 	sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )
-
-	sched.enque( (2,0), o.test1.lamp.inp.set, ('L',) )
+	sched.enque( (3,0), o.test1.lamp.inp.set, ('L',) )
 
 	sched.main_loop()

# connect() メソッドは廃止で
# Pinクラスのconnメンバに直接セットするようにしました

# 最後はランプ消灯の登録を、時刻2秒から3秒に修正です
# 2秒では32ビット整数の範囲だったので、
# 確実にオーバーフローする3秒で試してみます

それでは実行してみます。

$ cat nand.py-5-diff.txt | patch

$ ./nand.py
0.000000410 o.test1.lamp H
0.000000420 o.test1.lamp L
0.000000430 o.test1.lamp H
0.000000440 o.test1.lamp L
0.000000450 o.test1.lamp H
0.000000460 o.test1.lamp L
0.000000470 o.test1.lamp H
0.000000480 o.test1.lamp L
0.000000490 o.test1.lamp H
0.000000500 o.test1.lamp L
0.000000510 o.test1.lamp H
3.000000000 o.test1.lamp L
$ 

# 大丈夫そうです
# 次は -v つきで

$ ./nand.py -v
0.000000000 o.foo v=0
0.000000000 o.foo v=1
0.000000000 o.foo v=3
0.000000000 o.foo v=6
0.000000000 o.foo v=10
0.000000000 o.foo v=15
0.000000000 o.foo v=21
0.000000000 o.foo v=28
0.000000000 o.foo v=36
0.000000000 o.foo v=45
0.000000000 o.foo v=55
0.000000010 o.slow.foo v=0
0.000000012 o.slow_12.foo v=0
0.000000020 o.slow.foo v=1
0.000000024 o.slow_12.foo v=1
0.000000030 o.slow.foo v=3
0.000000036 o.slow_12.foo v=3
0.000000040 o.slow.foo v=6
0.000000048 o.slow_12.foo v=6
0.000000050 o.slow.foo v=10
0.000000060 o.slow_12.foo v=10
0.000000060 o.slow.foo v=15
0.000000070 o.slow.foo v=21
0.000000072 o.slow_12.foo v=15
0.000000080 o.slow.foo v=28
0.000000084 o.slow_12.foo v=21
0.000000090 o.slow.foo v=36
0.000000096 o.slow_12.foo v=28
0.000000100 o.slow.foo v=45
0.000000108 o.slow_12.foo v=36
0.000000110 o.slow.foo v=55
0.000000120 o.slow_12.foo v=45
0.000000132 o.slow_12.foo v=55
0.000000200 o.foo.out H
0.000000200 o.slow.foo.inp H
0.000000210 o.slow.foo.out L
0.000000210 o.slow_12.foo.inp L
0.000000222 o.slow_12.foo.out H
0.000000300 o.nand.inp_a L
0.000000300 o.nand.inp_b L
0.000000300 o.nand update
0.000000310 o.nand.out H
0.000000310 o.nand update
0.000000320 o.nand.inp_a H
0.000000320 o.nand update
0.000000340 o.nand.inp_b H
0.000000340 o.nand update
0.000000350 o.nand.out L
0.000000350 o.nand update
0.000000360 o.nand.inp_a L
0.000000360 o.nand update
0.000000370 o.nand.out H
0.000000370 o.nand update
0.000000400 o.test1.nand.inp_b H
0.000000400 o.test1.nand update
0.000000410 o.test1.nand.out H
0.000000410 o.test1.jt.pin0 H
0.000000410 o.test1.jt.pin1 H
0.000000410 o.test1.nand.inp_a H
0.000000410 o.test1.jt.pin2 H
0.000000410 o.test1.lamp.inp H
0.000000410 o.test1.nand update
0.000000410 o.test1.lamp H
0.000000420 o.test1.nand.out L
0.000000420 o.test1.jt.pin0 L
0.000000420 o.test1.jt.pin1 L
0.000000420 o.test1.nand.inp_a L
0.000000420 o.test1.jt.pin2 L
0.000000420 o.test1.lamp.inp L
0.000000420 o.test1.nand update
0.000000420 o.test1.lamp L
0.000000430 o.test1.nand.out H
0.000000430 o.test1.jt.pin0 H
0.000000430 o.test1.jt.pin1 H
0.000000430 o.test1.nand.inp_a H
0.000000430 o.test1.jt.pin2 H
0.000000430 o.test1.lamp.inp H
0.000000430 o.test1.nand update
0.000000430 o.test1.lamp H
0.000000440 o.test1.nand.out L
0.000000440 o.test1.jt.pin0 L
0.000000440 o.test1.jt.pin1 L
0.000000440 o.test1.nand.inp_a L
0.000000440 o.test1.jt.pin2 L
0.000000440 o.test1.lamp.inp L
0.000000440 o.test1.nand update
0.000000440 o.test1.lamp L
0.000000450 o.test1.nand.out H
0.000000450 o.test1.jt.pin0 H
0.000000450 o.test1.jt.pin1 H
0.000000450 o.test1.nand.inp_a H
0.000000450 o.test1.jt.pin2 H
0.000000450 o.test1.lamp.inp H
0.000000450 o.test1.nand update
0.000000450 o.test1.lamp H
0.000000460 o.test1.nand.out L
0.000000460 o.test1.jt.pin0 L
0.000000460 o.test1.jt.pin1 L
0.000000460 o.test1.nand.inp_a L
0.000000460 o.test1.jt.pin2 L
0.000000460 o.test1.lamp.inp L
0.000000460 o.test1.nand update
0.000000460 o.test1.lamp L
0.000000470 o.test1.nand.out H
0.000000470 o.test1.jt.pin0 H
0.000000470 o.test1.jt.pin1 H
0.000000470 o.test1.nand.inp_a H
0.000000470 o.test1.jt.pin2 H
0.000000470 o.test1.lamp.inp H
0.000000470 o.test1.nand update
0.000000470 o.test1.lamp H
0.000000480 o.test1.nand.out L
0.000000480 o.test1.jt.pin0 L
0.000000480 o.test1.jt.pin1 L
0.000000480 o.test1.nand.inp_a L
0.000000480 o.test1.jt.pin2 L
0.000000480 o.test1.lamp.inp L
0.000000480 o.test1.nand update
0.000000480 o.test1.lamp L
0.000000490 o.test1.nand.out H
0.000000490 o.test1.jt.pin0 H
0.000000490 o.test1.jt.pin1 H
0.000000490 o.test1.nand.inp_a H
0.000000490 o.test1.jt.pin2 H
0.000000490 o.test1.lamp.inp H
0.000000490 o.test1.nand update
0.000000490 o.test1.lamp H
0.000000500 o.test1.nand.inp_b L
0.000000500 o.test1.nand.out L
0.000000500 o.test1.jt.pin0 L
0.000000500 o.test1.nand update
0.000000500 o.test1.jt.pin1 L
0.000000500 o.test1.nand.inp_a L
0.000000500 o.test1.jt.pin2 L
0.000000500 o.test1.lamp.inp L
0.000000500 o.test1.nand update
0.000000500 o.test1.lamp L
0.000000510 o.test1.nand.out H
0.000000510 o.test1.jt.pin0 H
0.000000510 o.test1.jt.pin1 H
0.000000510 o.test1.nand.inp_a H
0.000000510 o.test1.jt.pin2 H
0.000000510 o.test1.lamp.inp H
0.000000510 o.test1.nand update
0.000000510 o.test1.lamp H
3.000000000 o.test1.lamp.inp L
3.000000000 o.test1.lamp L
$ 

# OKです


外野からちょっかい出してみる

メイン関数にあたる部分に、ごちゃごちゃとお試し用のコードが並んできました。

python はインタプリタなので「exec」なる荒技が使えます。

標準入力を使って、各種メソッドの呼び出しなどをさせてみます。

nand.py-6-diff.txt

--- nand.py-
+++ nand.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 
 import sys
+import time
+import select
 
 class Obj:
 	def __init__(self, parent, name, latency=None):

# time.sleep() と select.select() を使うので追加です


@@ -127,10 +129,29 @@
 		return next( ( True for a in self.que if a == t ), False )
 
 	def main_loop(self):
-		while len(self.que) > 0:
-			(tm, f, args) = self.que.pop(0)
-			self.now = tm
-			f(*args)
+		self.run = True
+		while self.run:
+			cmd_exec( sys.stdin )
+			if self.que:
+				(tm, f, args) = self.que.pop(0)
+				self.now = tm
+				f(*args)
+			else:
+				time.sleep(0.5)
+
+	def quit(self):
+		dbgout('quit')
+		self.run = False
+

# メソッドquit()が呼ばれると終了するように変更しました
# スケジューラはキューが空になっても終了せず、
# 0.5 秒寝て何かしら変化を待ちます...

# そしてスケジューラのメインループから cmd_exec()関数の呼び出しを追加


+def cmd_exec(f):
+	while select.select( [f], [], [], 0 )[0]:
+		s = f.readline()
+		if not s:
+			break
+		s = s.strip()
+		if s:
+			dbgout( 'cmd_exec "{}"'.format(s) )
+			exec s
 
 def str_v(v):
 	return { True: 'H', False: 'L', None: 'Hi-Z' }.get(v, '?')

# 追加した cmd_exec() 関数の本体です

# 指定のファイル f (今回の場合は呼び元で標準入力を指定) がリード可能か確かめて
# 1行リードしてみて空文字なら関数を抜けます
# (標準入力がパイプの場合、どうも select では常にリード可能になるようなので...)

# 先頭の空白や、末尾の改行を掃除して、
# それでも空文字にならなければ、exec で実行


@@ -163,36 +184,6 @@
 	sched = Sched()
 
 	o = Obj(None, 'o')
-	Foo(o)
-
-	slow = Obj(o, 'slow')
-	Foo(slow, 10)
-
-	slow_12 = Obj(o, 'slow_12')
-	Foo(slow_12, 12)
-
-	o.foo.out.conn = o.slow.foo.inp
-	o.slow.foo.out.conn = o.slow_12.foo.inp
-	sched.enque( 200, o.foo.out.set, ('H',) )
-
-	NAND(o)
-	sched.enque( 300, o.nand.inp_a.set, ('L',) )
-	sched.enque( 300, o.nand.inp_b.set, ('L',) )
-	sched.enque( 320, o.nand.inp_a.set, ('H',) )
-	sched.enque( 340, o.nand.inp_b.set, ('H',) )
-	sched.enque( 360, o.nand.inp_a.set, ('L',) )
-
-	test1 = Obj(o, 'test1')
-	NAND(test1)
-	Joint(test1)
-	Lamp(test1)
-	o.test1.nand.out.conn = o.test1.jt.new_pin()
-	o.test1.jt.new_pin().conn = o.test1.nand.inp_a
-	o.test1.jt.new_pin().conn = o.test1.lamp.inp
-
-	sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )
-	sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )
-	sched.enque( (3,0), o.test1.lamp.inp.set, ('L',) )
 
 	sched.main_loop()
 
# メイン関数部分で試してた事は、
# 名前 'o' の Objインスタンスを生成して、
# グローバル変数 o に保持する事だけを残し
# 他は全て削除

それでは実行してみます。

$ cat nand.py-6-diff.txt | patch

$ ./nand.py -v

# ここで
sched.quit()
# とキー入力

0.000000000 cmd_exec "sched.quit()"
0.000000000 quit
$ 

# 無事終了できます


$ ./nand.py -v

# ここで
Foo(o)
# とキー入力

0.000000000 cmd_exec "Foo(o)"
0.000000000 o.foo v=0
0.000000000 o.foo v=1
0.000000000 o.foo v=3
0.000000000 o.foo v=6
0.000000000 o.foo v=10
0.000000000 o.foo v=15
0.000000000 o.foo v=21
0.000000000 o.foo v=28
0.000000000 o.foo v=36
0.000000000 o.foo v=45
0.000000000 o.foo v=55

# ここで
Lamp(o)
# とキー入力

0.000000000 cmd_exec "Lamp(o)"

# ここで
sched.enque( (1,0), o.lamp.inp.set, ('H',) )
# とキー入力

0.000000000 cmd_exec "sched.enque( (1,0), o.lamp.inp.set, ('H',) )"
1.000000000 o.lamp.inp H
1.000000000 o.lamp H

# ここで
sched.quit()
# とキー入力

1.000000000 cmd_exec "sched.quit()"
1.000000000 quit
$ 

# 大丈夫そうです

これまでメイン関数にあたる部分でやってた処理は削除しましたが、 同じ内容をテキストファイルに用意してみました。

cmd.txt

$ cat cmd.txt
Foo(o)

slow = Obj(o, 'slow')
Foo(slow, 10)

slow_12 = Obj(o, 'slow_12')
Foo(slow_12, 12)

o.foo.out.conn = o.slow.foo.inp
o.slow.foo.out.conn = o.slow_12.foo.inp
sched.enque( 200, o.foo.out.set, ('H',) )

NAND(o)
sched.enque( 300, o.nand.inp_a.set, ('L',) )
sched.enque( 300, o.nand.inp_b.set, ('L',) )
sched.enque( 320, o.nand.inp_a.set, ('H',) )
sched.enque( 340, o.nand.inp_b.set, ('H',) )
sched.enque( 360, o.nand.inp_a.set, ('L',) )

test1 = Obj(o, 'test1')
NAND(test1)
Joint(test1)
Lamp(test1)
o.test1.nand.out.conn = o.test1.jt.new_pin()
o.test1.jt.new_pin().conn = o.test1.nand.inp_a
o.test1.jt.new_pin().conn = o.test1.lamp.inp

sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )
sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )
sched.enque( (3,0), o.test1.lamp.inp.set, ('L',) )

sched.enque( (5,0), sched.quit )
$ 

# まんまコピペして行頭の空白を削り、
# 末尾に sched.enque( (5,0), sched.quit ) を追加してます


$ ./nand.py -v < cmd.txt
0.000000000 cmd_exec "Foo(o)"
0.000000000 cmd_exec "slow = Obj(o, 'slow')"
0.000000000 cmd_exec "Foo(slow, 10)"
0.000000000 cmd_exec "slow_12 = Obj(o, 'slow_12')"
0.000000000 cmd_exec "Foo(slow_12, 12)"
0.000000000 cmd_exec "o.foo.out.conn = o.slow.foo.inp"
0.000000000 cmd_exec "o.slow.foo.out.conn = o.slow_12.foo.inp"
0.000000000 cmd_exec "sched.enque( 200, o.foo.out.set, ('H',) )"
0.000000000 cmd_exec "NAND(o)"
0.000000000 cmd_exec "sched.enque( 300, o.nand.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( 300, o.nand.inp_b.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( 320, o.nand.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( 340, o.nand.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( 360, o.nand.inp_a.set, ('L',) )"
0.000000000 cmd_exec "test1 = Obj(o, 'test1')"
0.000000000 cmd_exec "NAND(test1)"
0.000000000 cmd_exec "Joint(test1)"
0.000000000 cmd_exec "Lamp(test1)"
0.000000000 cmd_exec "o.test1.nand.out.conn = o.test1.jt.new_pin()"
0.000000000 cmd_exec "o.test1.jt.new_pin().conn = o.test1.nand.inp_a"
0.000000000 cmd_exec "o.test1.jt.new_pin().conn = o.test1.lamp.inp"
0.000000000 cmd_exec "sched.enque( 400, o.test1.nand.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( 500, o.test1.nand.inp_b.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (3,0), o.test1.lamp.inp.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (5,0), sched.quit )"
0.000000000 o.foo v=0
0.000000000 o.foo v=1
0.000000000 o.foo v=3
0.000000000 o.foo v=6
0.000000000 o.foo v=10
0.000000000 o.foo v=15
0.000000000 o.foo v=21
0.000000000 o.foo v=28
0.000000000 o.foo v=36
0.000000000 o.foo v=45
0.000000000 o.foo v=55
0.000000010 o.slow.foo v=0
0.000000012 o.slow_12.foo v=0
0.000000020 o.slow.foo v=1
0.000000024 o.slow_12.foo v=1
0.000000030 o.slow.foo v=3
0.000000036 o.slow_12.foo v=3
0.000000040 o.slow.foo v=6
0.000000048 o.slow_12.foo v=6
0.000000050 o.slow.foo v=10
0.000000060 o.slow_12.foo v=10
0.000000060 o.slow.foo v=15
0.000000070 o.slow.foo v=21
0.000000072 o.slow_12.foo v=15
0.000000080 o.slow.foo v=28
0.000000084 o.slow_12.foo v=21
0.000000090 o.slow.foo v=36
0.000000096 o.slow_12.foo v=28
0.000000100 o.slow.foo v=45
0.000000108 o.slow_12.foo v=36
0.000000110 o.slow.foo v=55
0.000000120 o.slow_12.foo v=45
0.000000132 o.slow_12.foo v=55
0.000000200 o.foo.out H
0.000000200 o.slow.foo.inp H
0.000000210 o.slow.foo.out L
0.000000210 o.slow_12.foo.inp L
0.000000222 o.slow_12.foo.out H
0.000000300 o.nand.inp_a L
0.000000300 o.nand.inp_b L
0.000000300 o.nand update
0.000000310 o.nand.out H
0.000000310 o.nand update
0.000000320 o.nand.inp_a H
0.000000320 o.nand update
0.000000340 o.nand.inp_b H
0.000000340 o.nand update
0.000000350 o.nand.out L
0.000000350 o.nand update
0.000000360 o.nand.inp_a L
0.000000360 o.nand update
0.000000370 o.nand.out H
0.000000370 o.nand update
0.000000400 o.test1.nand.inp_b H
0.000000400 o.test1.nand update
0.000000410 o.test1.nand.out H
0.000000410 o.test1.jt.pin0 H
0.000000410 o.test1.jt.pin1 H
0.000000410 o.test1.nand.inp_a H
0.000000410 o.test1.jt.pin2 H
0.000000410 o.test1.lamp.inp H
0.000000410 o.test1.nand update
0.000000410 o.test1.lamp H
0.000000420 o.test1.nand.out L
0.000000420 o.test1.jt.pin0 L
0.000000420 o.test1.jt.pin1 L
0.000000420 o.test1.nand.inp_a L
0.000000420 o.test1.jt.pin2 L
0.000000420 o.test1.lamp.inp L
0.000000420 o.test1.nand update
0.000000420 o.test1.lamp L
0.000000430 o.test1.nand.out H
0.000000430 o.test1.jt.pin0 H
0.000000430 o.test1.jt.pin1 H
0.000000430 o.test1.nand.inp_a H
0.000000430 o.test1.jt.pin2 H
0.000000430 o.test1.lamp.inp H
0.000000430 o.test1.nand update
0.000000430 o.test1.lamp H
0.000000440 o.test1.nand.out L
0.000000440 o.test1.jt.pin0 L
0.000000440 o.test1.jt.pin1 L
0.000000440 o.test1.nand.inp_a L
0.000000440 o.test1.jt.pin2 L
0.000000440 o.test1.lamp.inp L
0.000000440 o.test1.nand update
0.000000440 o.test1.lamp L
0.000000450 o.test1.nand.out H
0.000000450 o.test1.jt.pin0 H
0.000000450 o.test1.jt.pin1 H
0.000000450 o.test1.nand.inp_a H
0.000000450 o.test1.jt.pin2 H
0.000000450 o.test1.lamp.inp H
0.000000450 o.test1.nand update
0.000000450 o.test1.lamp H
0.000000460 o.test1.nand.out L
0.000000460 o.test1.jt.pin0 L
0.000000460 o.test1.jt.pin1 L
0.000000460 o.test1.nand.inp_a L
0.000000460 o.test1.jt.pin2 L
0.000000460 o.test1.lamp.inp L
0.000000460 o.test1.nand update
0.000000460 o.test1.lamp L
0.000000470 o.test1.nand.out H
0.000000470 o.test1.jt.pin0 H
0.000000470 o.test1.jt.pin1 H
0.000000470 o.test1.nand.inp_a H
0.000000470 o.test1.jt.pin2 H
0.000000470 o.test1.lamp.inp H
0.000000470 o.test1.nand update
0.000000470 o.test1.lamp H
0.000000480 o.test1.nand.out L
0.000000480 o.test1.jt.pin0 L
0.000000480 o.test1.jt.pin1 L
0.000000480 o.test1.nand.inp_a L
0.000000480 o.test1.jt.pin2 L
0.000000480 o.test1.lamp.inp L
0.000000480 o.test1.nand update
0.000000480 o.test1.lamp L
0.000000490 o.test1.nand.out H
0.000000490 o.test1.jt.pin0 H
0.000000490 o.test1.jt.pin1 H
0.000000490 o.test1.nand.inp_a H
0.000000490 o.test1.jt.pin2 H
0.000000490 o.test1.lamp.inp H
0.000000490 o.test1.nand update
0.000000490 o.test1.lamp H
0.000000500 o.test1.nand.inp_b L
0.000000500 o.test1.nand.out L
0.000000500 o.test1.jt.pin0 L
0.000000500 o.test1.nand update
0.000000500 o.test1.jt.pin1 L
0.000000500 o.test1.nand.inp_a L
0.000000500 o.test1.jt.pin2 L
0.000000500 o.test1.lamp.inp L
0.000000500 o.test1.nand update
0.000000500 o.test1.lamp L
0.000000510 o.test1.nand.out H
0.000000510 o.test1.jt.pin0 H
0.000000510 o.test1.jt.pin1 H
0.000000510 o.test1.nand.inp_a H
0.000000510 o.test1.jt.pin2 H
0.000000510 o.test1.lamp.inp H
0.000000510 o.test1.nand update
0.000000510 o.test1.lamp H
3.000000000 o.test1.lamp.inp L
3.000000000 o.test1.lamp L
5.000000000 quit
$ 

# 時刻 0 の時点で、cmd.txt の中身は、実行あるいはキューに登録されて
# これまでと同様に振舞った後、
# 最後にスケジューラに登録された、「5秒後に sched.quit()」が実行で
# 終了します


# -v なしだと

$ ./nand.py < cmd.txt          
0.000000410 o.test1.lamp H
0.000000420 o.test1.lamp L
0.000000430 o.test1.lamp H
0.000000440 o.test1.lamp L
0.000000450 o.test1.lamp H
0.000000460 o.test1.lamp L
0.000000470 o.test1.lamp H
0.000000480 o.test1.lamp L
0.000000490 o.test1.lamp H
0.000000500 o.test1.lamp L
0.000000510 o.test1.lamp H
3.000000000 o.test1.lamp L

# ランプの表示だけになって、こんな表示
# OKです


NANDえも出来るの手始めに

「NANDゲートがあれば何でもできる」の手始めとして、 NOT, AND, OR, NOR, XOR のクラスを追加してみます。

nand.py-7-diff.txt

--- nand.py-
+++ nand.py
@@ -94,6 +94,73 @@
 			if pin.conn:
 				pin.set(v)
 
+class NOT(Obj):
+	def __init__(self, parent, name='not_', latency=10):
+		Obj.__init__(self, parent, name)
+		NAND(self, 'nand', latency)
+		Pin(self, 'inp').conn = self.nand.inp_a
+		Pin(self, 'H', 'H').conn = self.nand.inp_b
+		self.nand.out.conn = Pin(self, 'out')
+

# NOTクラス
# NANDの片方の入力を常に'H'にプルアップした状態にしてNOTを作ります
# 名前 'not' は予約語なので 'not_' を使います

# 内部に「NANDインスタンス」を生成
# 「NOTとしての入力端子」を生成して、それを「NANDの入力A」につなぎます
# 「常に値が'H'の端子」として、初期値'H'で端子を生成します
# 「常に値が'H'の端子の出力」を、「NANDの入力B」につなぎます
# 「NOTとしての出力端子」を生成して、「NANDの出力」をそれにつなぎます


+class AND(Obj):
+	def __init__(self, parent, name='and_', latency=10):
+		Obj.__init__(self, parent, name)
+		NAND(self, 'nand', latency)
+		Pin(self, 'inp_a').conn = self.nand.inp_a
+		Pin(self, 'inp_b').conn = self.nand.inp_b
+		NOT(self, 'not_', latency)
+		self.nand.out.conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
+

# ANDクラス
# NANDの出力にNOTをつなぎANDにします
# 名前 'and' は予約語なので 'and_' を使います

# 「ANDとしての2つの入力端子」を生成して、「NANDの2つの入力」につなぎます
# 「NANDの出力」に「NOTの入力」をつなぎ
# 「ANDとしての出力端子」を生成して、「NOTの出力」にそれをつなぎます


+class OR(Obj):
+	def __init__(self, parent, name='or_', latency=10):
+		Obj.__init__(self, parent, name)
+		NOT(self, 'not_a', latency)
+		Pin(self, 'inp_a').conn = self.not_a.inp
+		NOT(self, 'not_b', latency)
+		Pin(self, 'inp_b').conn = self.not_b.inp
+		NAND(self, 'nand', latency)
+		self.not_a.out.conn = self.nand.inp_a
+		self.not_b.out.conn = self.nand.inp_b
+		self.nand.out.conn = Pin(self, 'out')
+

# ORクラス
# NANDの2つの入力に、それぞれNOTをつないでORにします
# 名前 'or' は予約語なので 'or_' を使います

# 「ORとしての2つの入力端子」を生成して、それぞれ「2つのNOTの入力」をつなぎます
# 「2つのNOTの出力」を「NANDの2つの入力」につなぎ
# 「ORとしての出力端子を」生成して、「NANDの出力」をそれにつなぎます


+class NOR(Obj):
+	def __init__(self, parent, name='nor', latency=10):
+		Obj.__init__(self, parent, name)
+		OR(self, 'or_', latency)
+		Pin(self, 'inp_a').conn = self.or_.inp_a
+		Pin(self, 'inp_b').conn = self.or_.inp_b
+		NOT(self, 'not_', latency)
+		self.or_.out.conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
+

# NORクラス
# 既出のORクラスを使って、その出力にNOTをつなぎNORにします

# ANDの時に、NAND出力側にNOTをつないでANDとしたのと同じ要領です
# NORの場合も、ORの出力側にNOTをつないでNORとにします


+class XOR(Obj):
+	def __init__(self, parent, name='xor', latency=10):
+		Obj.__init__(self, parent, name)
+		NAND(self, 'nand_i', latency)
+		NAND(self, 'nand_a', latency)
+		NAND(self, 'nand_b', latency)
+		NAND(self, 'nand_o', latency)
+		Joint(self, 'jt_a')
+		Joint(self, 'jt_b')
+		Joint(self, 'jt_c')
+
+		Pin(self, 'inp_a').conn = self.jt_a.new_pin()
+		self.jt_a.new_pin().conn = self.nand_i.inp_a
+		self.jt_a.new_pin().conn = self.nand_a.inp_a
+
+		Pin(self, 'inp_b').conn = self.jt_b.new_pin()
+		self.jt_b.new_pin().conn = self.nand_i.inp_b
+		self.jt_b.new_pin().conn = self.nand_b.inp_b
+
+		self.nand_i.out.conn = self.jt_c.new_pin()
+		self.jt_c.new_pin().conn = self.nand_a.inp_b
+		self.jt_c.new_pin().conn = self.nand_b.inp_a
+
+		self.nand_a.out.conn = self.nand_o.inp_a
+		self.nand_b.out.conn = self.nand_o.inp_b
+		self.nand_o.out.conn = Pin(self, 'out')
+

# XORクラス
# これは素人なのでWebで検索
# 4つのNANDをつないでます

                       +---+
---@-------------------|    \
   |                   |     |O---.
   |   +---+      .----|    /     |   +---+
   .---|    \     |    +---+      .---|    \
       |     |O---@                   |     |O---
   .---|    /     |    +---+      .---|    /
   |   +---+      .----|    \     |   +---+
   |                   |     |O---.
---@-------------------|    /
                       +---+

標準入力から動作確認用のコマンド群を流し込んで、動かしてみます。

cmd_gate_ck.txt

$ cat cmd_gate_ck.txt
NAND(o)
sched.enque( (1,1000), o.nand.inp_a.set, ('H',) )
sched.enque( (1,1100), o.nand.inp_b.set, ('H',) )
sched.enque( (1,1200), o.nand.inp_a.set, ('L',) )
sched.enque( (1,1300), o.nand.inp_b.set, ('L',) )

NOT(o)
sched.enque( (1,2000), o.not_.inp.set, ('H',) )
sched.enque( (1,2100), o.not_.inp.set, ('L',) )

AND(o)
sched.enque( (1,3000), o.and_.inp_a.set, ('H',) )
sched.enque( (1,3100), o.and_.inp_b.set, ('H',) )
sched.enque( (1,3200), o.and_.inp_a.set, ('L',) )
sched.enque( (1,3300), o.and_.inp_b.set, ('L',) )

OR(o)
sched.enque( (1,4000), o.or_.inp_a.set, ('H',) )
sched.enque( (1,4100), o.or_.inp_b.set, ('H',) )
sched.enque( (1,4200), o.or_.inp_a.set, ('L',) )
sched.enque( (1,4300), o.or_.inp_b.set, ('L',) )

NOR(o)
sched.enque( (1,5000), o.nor.inp_a.set, ('H',) )
sched.enque( (1,5100), o.nor.inp_b.set, ('H',) )
sched.enque( (1,5200), o.nor.inp_a.set, ('L',) )
sched.enque( (1,5300), o.nor.inp_b.set, ('L',) )

XOR(o)
sched.enque( (1,6000), o.xor.inp_a.set, ('H',) )
sched.enque( (1,6100), o.xor.inp_b.set, ('H',) )
sched.enque( (1,6200), o.xor.inp_a.set, ('L',) )
sched.enque( (1,6300), o.xor.inp_b.set, ('L',) )

sched.enque( (5,0), sched.quit )
$ 

$ ./nand.py -v < cmd_gate_ck.txt | tee out_gate_ck.txt
0.000000000 cmd_exec "NAND(o)"
0.000000000 cmd_exec "sched.enque( (1,1000), o.nand.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,1100), o.nand.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,1200), o.nand.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,1300), o.nand.inp_b.set, ('L',) )"
0.000000000 cmd_exec "NOT(o)"
0.000000000 cmd_exec "sched.enque( (1,2000), o.not_.inp.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,2100), o.not_.inp.set, ('L',) )"
0.000000000 cmd_exec "AND(o)"
0.000000000 cmd_exec "sched.enque( (1,3000), o.and_.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,3100), o.and_.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,3200), o.and_.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,3300), o.and_.inp_b.set, ('L',) )"
0.000000000 cmd_exec "OR(o)"
0.000000000 cmd_exec "sched.enque( (1,4000), o.or_.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,4100), o.or_.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,4200), o.or_.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,4300), o.or_.inp_b.set, ('L',) )"
0.000000000 cmd_exec "NOR(o)"
0.000000000 cmd_exec "sched.enque( (1,5000), o.nor.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,5100), o.nor.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,5200), o.nor.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,5300), o.nor.inp_b.set, ('L',) )"
0.000000000 cmd_exec "XOR(o)"
0.000000000 cmd_exec "sched.enque( (1,6000), o.xor.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,6100), o.xor.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,6200), o.xor.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,6300), o.xor.inp_b.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (5,0), sched.quit )"
0.000000000 o.not_.H H
0.000000000 o.not_.nand.inp_b H
0.000000000 o.and_.not_.H H
0.000000000 o.and_.not_.nand.inp_b H
0.000000000 o.or_.not_a.H H
0.000000000 o.or_.not_a.nand.inp_b H
0.000000000 o.or_.not_b.H H
0.000000000 o.or_.not_b.nand.inp_b H
0.000000000 o.nor.or_.not_a.H H
0.000000000 o.nor.or_.not_a.nand.inp_b H
0.000000000 o.nor.or_.not_b.H H
0.000000000 o.nor.or_.not_b.nand.inp_b H
0.000000000 o.nor.not_.H H
0.000000000 o.nor.not_.nand.inp_b H
0.000000000 o.not_.nand update
0.000000000 o.and_.not_.nand update
0.000000000 o.or_.not_a.nand update
0.000000000 o.or_.not_b.nand update
0.000000000 o.nor.or_.not_a.nand update
0.000000000 o.nor.or_.not_b.nand update
0.000000000 o.nor.not_.nand update
0.000000010 o.not_.nand.out H
0.000000010 o.not_.out H
0.000000010 o.and_.not_.nand.out H
0.000000010 o.and_.not_.out H
0.000000010 o.and_.out H
0.000000010 o.or_.not_a.nand.out H
0.000000010 o.or_.not_a.out H
0.000000010 o.or_.nand.inp_a H
0.000000010 o.or_.not_b.nand.out H
0.000000010 o.or_.not_b.out H
0.000000010 o.or_.nand.inp_b H
0.000000010 o.nor.or_.not_a.nand.out H
0.000000010 o.nor.or_.not_a.out H
0.000000010 o.nor.or_.nand.inp_a H
0.000000010 o.nor.or_.not_b.nand.out H
0.000000010 o.nor.or_.not_b.out H
0.000000010 o.nor.or_.nand.inp_b H
0.000000010 o.nor.not_.nand.out H
0.000000010 o.nor.not_.out H
0.000000010 o.nor.out H
0.000000010 o.or_.nand update
0.000000010 o.nor.or_.nand update
0.000000020 o.or_.nand.out L
0.000000020 o.or_.out L
0.000000020 o.nor.or_.nand.out L
0.000000020 o.nor.or_.out L
0.000000020 o.nor.not_.inp L
0.000000020 o.nor.not_.nand.inp_a L
0.000000020 o.nor.not_.nand update
1.000001000 o.nand.inp_a H
1.000001000 o.nand update
1.000001010 o.nand.out H
1.000001010 o.nand update
1.000001100 o.nand.inp_b H
1.000001100 o.nand update
1.000001110 o.nand.out L
1.000001110 o.nand update
1.000001200 o.nand.inp_a L
1.000001200 o.nand update
1.000001210 o.nand.out H
1.000001210 o.nand update
1.000001300 o.nand.inp_b L
1.000001300 o.nand update
1.000002000 o.not_.inp H
1.000002000 o.not_.nand.inp_a H
1.000002000 o.not_.nand update
1.000002010 o.not_.nand.out L
1.000002010 o.not_.out L
1.000002100 o.not_.inp L
1.000002100 o.not_.nand.inp_a L
1.000002100 o.not_.nand update
1.000002110 o.not_.nand.out H
1.000002110 o.not_.out H
1.000003000 o.and_.inp_a H
1.000003000 o.and_.nand.inp_a H
1.000003000 o.and_.nand update
1.000003010 o.and_.nand.out H
1.000003010 o.and_.not_.inp H
1.000003010 o.and_.not_.nand.inp_a H
1.000003010 o.and_.not_.nand update
1.000003020 o.and_.not_.nand.out L
1.000003020 o.and_.not_.out L
1.000003020 o.and_.out L
1.000003100 o.and_.inp_b H
1.000003100 o.and_.nand.inp_b H
1.000003100 o.and_.nand update
1.000003110 o.and_.nand.out L
1.000003110 o.and_.not_.inp L
1.000003110 o.and_.not_.nand.inp_a L
1.000003110 o.and_.not_.nand update
1.000003120 o.and_.not_.nand.out H
1.000003120 o.and_.not_.out H
1.000003120 o.and_.out H
1.000003200 o.and_.inp_a L
1.000003200 o.and_.nand.inp_a L
1.000003200 o.and_.nand update
1.000003210 o.and_.nand.out H
1.000003210 o.and_.not_.inp H
1.000003210 o.and_.not_.nand.inp_a H
1.000003210 o.and_.not_.nand update
1.000003220 o.and_.not_.nand.out L
1.000003220 o.and_.not_.out L
1.000003220 o.and_.out L
1.000003300 o.and_.inp_b L
1.000003300 o.and_.nand.inp_b L
1.000003300 o.and_.nand update
1.000004000 o.or_.inp_a H
1.000004000 o.or_.not_a.inp H
1.000004000 o.or_.not_a.nand.inp_a H
1.000004000 o.or_.not_a.nand update
1.000004010 o.or_.not_a.nand.out L
1.000004010 o.or_.not_a.out L
1.000004010 o.or_.nand.inp_a L
1.000004010 o.or_.nand update
1.000004020 o.or_.nand.out H
1.000004020 o.or_.out H
1.000004100 o.or_.inp_b H
1.000004100 o.or_.not_b.inp H
1.000004100 o.or_.not_b.nand.inp_a H
1.000004100 o.or_.not_b.nand update
1.000004110 o.or_.not_b.nand.out L
1.000004110 o.or_.not_b.out L
1.000004110 o.or_.nand.inp_b L
1.000004110 o.or_.nand update
1.000004200 o.or_.inp_a L
1.000004200 o.or_.not_a.inp L
1.000004200 o.or_.not_a.nand.inp_a L
1.000004200 o.or_.not_a.nand update
1.000004210 o.or_.not_a.nand.out H
1.000004210 o.or_.not_a.out H
1.000004210 o.or_.nand.inp_a H
1.000004210 o.or_.nand update
1.000004300 o.or_.inp_b L
1.000004300 o.or_.not_b.inp L
1.000004300 o.or_.not_b.nand.inp_a L
1.000004300 o.or_.not_b.nand update
1.000004310 o.or_.not_b.nand.out H
1.000004310 o.or_.not_b.out H
1.000004310 o.or_.nand.inp_b H
1.000004310 o.or_.nand update
1.000004320 o.or_.nand.out L
1.000004320 o.or_.out L
1.000005000 o.nor.inp_a H
1.000005000 o.nor.or_.inp_a H
1.000005000 o.nor.or_.not_a.inp H
1.000005000 o.nor.or_.not_a.nand.inp_a H
1.000005000 o.nor.or_.not_a.nand update
1.000005010 o.nor.or_.not_a.nand.out L
1.000005010 o.nor.or_.not_a.out L
1.000005010 o.nor.or_.nand.inp_a L
1.000005010 o.nor.or_.nand update
1.000005020 o.nor.or_.nand.out H
1.000005020 o.nor.or_.out H
1.000005020 o.nor.not_.inp H
1.000005020 o.nor.not_.nand.inp_a H
1.000005020 o.nor.not_.nand update
1.000005030 o.nor.not_.nand.out L
1.000005030 o.nor.not_.out L
1.000005030 o.nor.out L
1.000005100 o.nor.inp_b H
1.000005100 o.nor.or_.inp_b H
1.000005100 o.nor.or_.not_b.inp H
1.000005100 o.nor.or_.not_b.nand.inp_a H
1.000005100 o.nor.or_.not_b.nand update
1.000005110 o.nor.or_.not_b.nand.out L
1.000005110 o.nor.or_.not_b.out L
1.000005110 o.nor.or_.nand.inp_b L
1.000005110 o.nor.or_.nand update
1.000005200 o.nor.inp_a L
1.000005200 o.nor.or_.inp_a L
1.000005200 o.nor.or_.not_a.inp L
1.000005200 o.nor.or_.not_a.nand.inp_a L
1.000005200 o.nor.or_.not_a.nand update
1.000005210 o.nor.or_.not_a.nand.out H
1.000005210 o.nor.or_.not_a.out H
1.000005210 o.nor.or_.nand.inp_a H
1.000005210 o.nor.or_.nand update
1.000005300 o.nor.inp_b L
1.000005300 o.nor.or_.inp_b L
1.000005300 o.nor.or_.not_b.inp L
1.000005300 o.nor.or_.not_b.nand.inp_a L
1.000005300 o.nor.or_.not_b.nand update
1.000005310 o.nor.or_.not_b.nand.out H
1.000005310 o.nor.or_.not_b.out H
1.000005310 o.nor.or_.nand.inp_b H
1.000005310 o.nor.or_.nand update
1.000005320 o.nor.or_.nand.out L
1.000005320 o.nor.or_.out L
1.000005320 o.nor.not_.inp L
1.000005320 o.nor.not_.nand.inp_a L
1.000005320 o.nor.not_.nand update
1.000005330 o.nor.not_.nand.out H
1.000005330 o.nor.not_.out H
1.000005330 o.nor.out H
1.000006000 o.xor.inp_a H
1.000006000 o.xor.jt_a.pin0 H
1.000006000 o.xor.jt_a.pin1 H
1.000006000 o.xor.nand_i.inp_a H
1.000006000 o.xor.jt_a.pin2 H
1.000006000 o.xor.nand_a.inp_a H
1.000006000 o.xor.nand_i update
1.000006000 o.xor.nand_a update
1.000006010 o.xor.nand_i.out H
1.000006010 o.xor.jt_c.pin0 H
1.000006010 o.xor.nand_a.out H
1.000006010 o.xor.nand_o.inp_a H
1.000006010 o.xor.jt_c.pin1 H
1.000006010 o.xor.nand_a.inp_b H
1.000006010 o.xor.jt_c.pin2 H
1.000006010 o.xor.nand_b.inp_a H
1.000006010 o.xor.nand_o update
1.000006010 o.xor.nand_a update
1.000006010 o.xor.nand_b update
1.000006020 o.xor.nand_o.out H
1.000006020 o.xor.out H
1.000006020 o.xor.nand_a.out L
1.000006020 o.xor.nand_o.inp_a L
1.000006020 o.xor.nand_b.out H
1.000006020 o.xor.nand_o.inp_b H
1.000006020 o.xor.nand_o update
1.000006100 o.xor.inp_b H
1.000006100 o.xor.jt_b.pin0 H
1.000006100 o.xor.jt_b.pin1 H
1.000006100 o.xor.nand_i.inp_b H
1.000006100 o.xor.jt_b.pin2 H
1.000006100 o.xor.nand_b.inp_b H
1.000006100 o.xor.nand_i update
1.000006100 o.xor.nand_b update
1.000006110 o.xor.nand_i.out L
1.000006110 o.xor.jt_c.pin0 L
1.000006110 o.xor.nand_b.out L
1.000006110 o.xor.nand_o.inp_b L
1.000006110 o.xor.jt_c.pin1 L
1.000006110 o.xor.nand_a.inp_b L
1.000006110 o.xor.jt_c.pin2 L
1.000006110 o.xor.nand_b.inp_a L
1.000006110 o.xor.nand_o update
1.000006110 o.xor.nand_a update
1.000006110 o.xor.nand_b update
1.000006120 o.xor.nand_a.out H
1.000006120 o.xor.nand_o.inp_a H
1.000006120 o.xor.nand_b.out H
1.000006120 o.xor.nand_o.inp_b H
1.000006120 o.xor.nand_o update
1.000006130 o.xor.nand_o.out L
1.000006130 o.xor.out L
1.000006200 o.xor.inp_a L
1.000006200 o.xor.jt_a.pin0 L
1.000006200 o.xor.jt_a.pin1 L
1.000006200 o.xor.nand_i.inp_a L
1.000006200 o.xor.jt_a.pin2 L
1.000006200 o.xor.nand_a.inp_a L
1.000006200 o.xor.nand_i update
1.000006200 o.xor.nand_a update
1.000006210 o.xor.nand_i.out H
1.000006210 o.xor.jt_c.pin0 H
1.000006210 o.xor.jt_c.pin1 H
1.000006210 o.xor.nand_a.inp_b H
1.000006210 o.xor.jt_c.pin2 H
1.000006210 o.xor.nand_b.inp_a H
1.000006210 o.xor.nand_a update
1.000006210 o.xor.nand_b update
1.000006220 o.xor.nand_b.out L
1.000006220 o.xor.nand_o.inp_b L
1.000006220 o.xor.nand_o update
1.000006230 o.xor.nand_o.out H
1.000006230 o.xor.out H
1.000006300 o.xor.inp_b L
1.000006300 o.xor.jt_b.pin0 L
1.000006300 o.xor.jt_b.pin1 L
1.000006300 o.xor.nand_i.inp_b L
1.000006300 o.xor.jt_b.pin2 L
1.000006300 o.xor.nand_b.inp_b L
1.000006300 o.xor.nand_i update
1.000006300 o.xor.nand_b update
1.000006310 o.xor.nand_b.out H
1.000006310 o.xor.nand_o.inp_b H
1.000006310 o.xor.nand_o update
1.000006320 o.xor.nand_o.out L
1.000006320 o.xor.out L
5.000000000 quit
$ 

out_gate_ck.txt

なかなかの大漁ですね。


NOTから順に見てみます

$ grep o.not_ out_gate_ck.txt
0.000000000 cmd_exec "sched.enque( (1,2000), o.not_.inp.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,2100), o.not_.inp.set, ('L',) )"

# 「とある時刻に入力端子を'H'にする」メソッド呼び出しを登録して
# 「その100ナノ秒後に入力端子を'L'にする」メソッド呼び出しを登録


0.000000000 o.not_.H H

# NOTゲート内部のNANDゲート入力Bには、常に'H'の端子をつないでます
# そのH端子インスタンス生成時に、set('H')がスケジューラに登録されます
# その set('H') の呼び出し結果がこれです
# NOTゲート内部のHという名前の端子が'H'にセットされてます


0.000000000 o.not_.nand.inp_b H
0.000000000 o.not_.nand update
0.000000010 o.not_.nand.out H
0.000000010 o.not_.out H

# H端子 から NANDの入力Bに 'H' が伝わり
# デフォルトの遅延10ナノ秒後に
# NANDの出力端子が None('Hi-Z') から 'H' に変化
# その'H'が、NOTゲートの出力端子に伝わってます


1.000002000 o.not_.inp H

# 登録された「とある時刻」になり、
# 「NOTとしての入力」が'H'にセットされ


1.000002000 o.not_.nand.inp_a H
1.000002000 o.not_.nand update
1.000002010 o.not_.nand.out L
1.000002010 o.not_.out L

# NOT内部のNANDの入力Aが'H'に
# NANDの入力A,Bとも'H'なので、10ナノ秒後にNAND出力は'L'
# 「NOTとしての出力」に伝わり'L'


1.000002100 o.not_.inp L
1.000002100 o.not_.nand.inp_a L
1.000002100 o.not_.nand update
1.000002110 o.not_.nand.out H
1.000002110 o.not_.out H
$ 

# 「とある時刻」の100ナノ秒後になり、
# 今度は逆に「NOTとしての入力」が'L'にセットされ
# 内部のNANDの出力が'H'になり
# 「NOTとしての出力」も'H'に


つづいてAND

$ grep o.and_ out_gate_ck.txt
0.000000000 cmd_exec "sched.enque( (1,3000), o.and_.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,3100), o.and_.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,3200), o.and_.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,3300), o.and_.inp_b.set, ('L',) )"

# とある時刻にAND入力Aを'H'
# その100ナノ秒後にAND入力Bを'H'
# その100ナノ秒後にAND入力Aを'L'
# その100ナノ秒後にAND入力Bを'L'
# という呼び出しをスケジューラに登録


0.000000000 o.and_.not_.H H

# ANDの内部にNOTがあり、そのNOT内部に常に'H'の端子があります
# そのH端子がまず'H'に設定されます


0.000000000 o.and_.not_.nand.inp_b H
0.000000000 o.and_.not_.nand update
0.000000010 o.and_.not_.nand.out H

# 「ANDの中のNOTの中のNAND」の出力がNone('Hi-Z')から'H'に


0.000000010 o.and_.not_.out H
0.000000010 o.and_.out H

# ANDの中のNOTの出力が'H'
# 「ANDとしての出力」が'H'

# ということで、「ここでのAND」は入力が初期状態のとき
# 内部にかかえるNOTの都合で、出力は'H'になります


1.000003000 o.and_.inp_a H
1.000003000 o.and_.nand.inp_a H
1.000003000 o.and_.nand update
1.000003010 o.and_.nand.out H

# 「とある時刻」になり「ANDとしての入力A」が'H'
# 「AND直下のNAND」の入力Aが'H'
# (「AND直下のNAND」であり、
#  「ANDの中NOTの中NAND」の方ではありません)
# 10ナノ秒後に「AND直下のNAND出力」がNone(Hi-Z)から'H'


1.000003010 o.and_.not_.inp H
1.000003010 o.and_.not_.nand.inp_a H
1.000003010 o.and_.not_.nand update
1.000003020 o.and_.not_.nand.out L
1.000003020 o.and_.not_.out L
1.000003020 o.and_.out L

# 「AND直下のNAND出力」'H'が、「ANDの中のNOTの...
# ああ、ややこしや
# 「AND直下のNAND出力」はその先の「NOT」で反転して'L'
# 結局「ANDとしての出力」は'L'


1.000003100 o.and_.inp_b H
1.000003100 o.and_.nand.inp_b H
1.000003100 o.and_.nand update
1.000003110 o.and_.nand.out L
1.000003110 o.and_.not_.inp L
1.000003110 o.and_.not_.nand.inp_a L
1.000003110 o.and_.not_.nand update
1.000003120 o.and_.not_.nand.out H
1.000003120 o.and_.not_.out H
1.000003120 o.and_.out H

# 「とある時刻」の100ナノ秒後に「ANDとしての入力B」が'H'
# 「AND直下のNAND」の入力は2つも'H'
# 10ナノ秒後に「AND直下のNAND」出力が'L'
# その先のNOTで反転して「ANDとしての出力」は'H'

1.000003200 o.and_.inp_a L
1.000003200 o.and_.nand.inp_a L
1.000003200 o.and_.nand update
1.000003210 o.and_.nand.out H
1.000003210 o.and_.not_.inp H
1.000003210 o.and_.not_.nand.inp_a H
1.000003210 o.and_.not_.nand update
1.000003220 o.and_.not_.nand.out L
1.000003220 o.and_.not_.out L
1.000003220 o.and_.out L

# 「とある時刻」の200ナノ秒後に「ANDとしての入力A」が'L'
# 「AND直下のNAND」の2つの入力は'L'と'H'
# 10ナノ秒後に「AND直下のNAND」出力が'H'
# その先のNOTで反転して「ANDとしての出力」は'L'


1.000003300 o.and_.inp_b L
1.000003300 o.and_.nand.inp_b L
1.000003300 o.and_.nand update
$ 

# 「とある時刻」の300ナノ秒後に「ANDとしての入力B」が'L'
# 「AND直下のNAND」の入力は2つも'L'
# 「AND直下のNAND」出力は'H'のまま変化なし
# その先のNOTで反転して「ANDとしての出力」も'L'のまま


さらにOR

なんか大丈夫そうなので、ちょいとハショリます。

$ grep 'enque.*o.or_' out_gate_ck.txt
0.000000000 cmd_exec "sched.enque( (1,4000), o.or_.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,4100), o.or_.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,4200), o.or_.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,4300), o.or_.inp_b.set, ('L',) )"
$

# ANDの場合と同じで
# 入力Aを'H', Bを'H', Aを'L', Bを'L'
# と変化させる呼び出しを登録


$ grep o.or_.out out_gate_ck.txt
0.000000020 o.or_.out L
1.000004020 o.or_.out H
1.000004320 o.or_.out L
$ 

# ORとしての出力の変化は
# Aを'H'にした20ナノ秒後に出力'H'
# A,Bとも'L'に落ちた20ナノ秒後に出力'L'
# (内部の入力側のNOTとNANDで、10+10=20ナノの遅延)


もういっちょNOR

$ grep 'enque.*o.nor' out_gate_ck.txt
0.000000000 cmd_exec "sched.enque( (1,5000), o.nor.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,5100), o.nor.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,5200), o.nor.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,5300), o.nor.inp_b.set, ('L',) )"
$ 

# ANDの場合と同じで
# 入力Aを'H', Bを'H', Aを'L', Bを'L'
# と変化させる呼び出しを登録


$ grep o.nor.out out_gate_ck.txt 
0.000000010 o.nor.out H
1.000005030 o.nor.out L
1.000005330 o.nor.out H
$ 

# NORとしての出力の変化は
# 初期状態からすぐに「出力側のNOT」が'H'になるので、
# 10ナノ秒後に「NORとしての出力」は'H'

# Aを'H'にした30ナノ秒後に出力は'L'
# A,Bとも'L'に落ちた30ナノ秒後に出力'H'
# (内部の「入力側のNOT」と「NAND」と「出力側のNOT」で、
#  10+10+10=30ナノの遅延)


ラストXOR

$ grep 'enque.*o.xor' out_gate_ck.txt
0.000000000 cmd_exec "sched.enque( (1,6000), o.xor.inp_a.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,6100), o.xor.inp_b.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,6200), o.xor.inp_a.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,6300), o.xor.inp_b.set, ('L',) )"
$ 

# ANDの場合と同じで
# 入力Aを'H', Bを'H', Aを'L', Bを'L'
# と変化させる呼び出しを登録


                       +---+
---@-------------------|    \
   |                   | (A) |O---.
   |   +---+      .----|    /     |   +---+
   .---|    \     |    +---+      .---|    \
       | (I) |O---@                   | (O) |O---
   .---|    /     |    +---+      .---|    /
   |   +---+      .----|    \     |   +---+
   |                   | (B) |O---.
---@-------------------|    /
                       +---+


$ grep o.xor.out out_gate_ck.txt
out_gate_ck.txt:1.000006020 o.xor.out H

# XORとしての出力の変化は
# Aを'H'にした20ナノ秒後に出力は'H'

# これは
# NAND(A)の出力が10ナノ秒の遅延でNoneから'L'に
# NAND(O)の出力が10ナノ秒の遅延でNoneから'H'に
# 10+10=20ナノの遅延


out_gate_ck.txt:1.000006130 o.xor.out L

# Bを'H'にした30ナノ秒後に出力は'L'

# これは
# NAND(I)の入力は2つとも'H'
# NAND(I)の出力が10ナノ秒の遅延で'L'
# NAND(A),NAND(B)の片方(内側)の入力が'L'
# NAND(A),NAND(B)それぞれの出力は10ナノ秒の遅延で'H'
# NAND(O)の入力は2つも'H'
# NAND(O)の出力が10ナノ秒の遅延で'L'
# 10+10+10=30ナノの遅延


out_gate_ck.txt:1.000006230 o.xor.out H

# Aを'L'にした30ナノ秒後に出力は'H'

# これは
# NAND(I)の入力は'L','H'
# NAND(I)の出力が10ナノ秒の遅延で'H'
# NAND(B)の入力は'H','H'
# NAND(B)の出力が10ナノ秒の遅延で'L'
# NAND(A)もそれなりに動作するものの...
# NAND(O)の片側(下側)の入力は'L'
# NAND(O)の出力が10ナノ秒の遅延で'H'
# 10+10+10=30ナノの遅延

out_gate_ck.txt:1.000006320 o.xor.out L
$ 

# A,Bとも'L'に落ちた30ナノ秒後に出力'L'

# これは
# NAND(I)の入力は'L','H'から'L','L'
# NAND(I)の出力は'H'のまま変化なし
# NAND(A)の入力は'H','H'
# NAND(A)の出力が10ナノ秒の遅延で'L'
# NAND(O)の入力は2つも'H'
# NAND(O)の出力が10ナノ秒の遅延で'L'
# 10+10+10=30ナノの遅延


現実の時間でLチカさせてみる

実行すると、だだだーっと結果が表示されるのも「ざん無い」ので、 なるべく現実の時間に合わせて動作させてみます。

簡単な「クロックもどき」なクラスを作って、ランプをつなぎ、 いわゆる「Lチカ」させてみます。

nand.py-8-diff.txt

--- nand.py-
+++ nand.py
@@ -42,26 +42,6 @@
 		elif self.parent and hasattr(self.parent, 'update'):
 			self.enque(self.parent.update)
 
-class Foo(Obj):
-	def __init__(self, parent, latency=None):
-		Obj.__init__(self, parent, 'foo', latency)
-		self.v = 0
-
-		Pin(self, 'inp')
-		Pin(self, 'out')
-
-		self.enque( self.add, (0, ) )
-
-	def add(self, a):
-		self.v += a
-		self.dbgout( 'v={}'.format(self.v) )
-		if self.v < 55:
-			self.enque( self.add, (a+1,) )
-
-	def update(self):
-		v = not self.inp.v
-		self.enque( self.out.set, (v,) )
-
 class NAND(Obj):
 	def __init__(self, parent, name='nand', latency=10):
 		Obj.__init__(self, parent, name, latency)

# もう使うことも無さそうなので削除しておきます


@@ -98,8 +78,10 @@
 	def __init__(self, parent, name='not_', latency=10):
 		Obj.__init__(self, parent, name)
 		NAND(self, 'nand', latency)
-		Pin(self, 'inp').conn = self.nand.inp_a
-		Pin(self, 'H', 'H').conn = self.nand.inp_b
+		Joint(self, 'jt')
+		Pin(self, 'inp').conn = self.jt.new_pin()
+		self.jt.new_pin().conn = self.nand.inp_a
+		self.jt.new_pin().conn = self.nand.inp_b
 		self.nand.out.conn = Pin(self, 'out')
 
 class AND(Obj):

# NOTクラスの実装の変更です
# NOTクラスの常に 'H' 端子が、どうも気持ちわるいので
# NANDの2つの入力をJointでまとめて、NOTとしての入力とするように
# 変更しました


@@ -161,6 +143,20 @@
 		self.nand_b.out.conn = self.nand_o.inp_b
 		self.nand_o.out.conn = Pin(self, 'out')
 
+class CLK(Obj):
+	def __init__(self, parent, hz=1.0, name='clk'):
+		Obj.__init__(self, parent, name)
+		latency = tm_from_fsec( 1.0 / ( 2 * hz ) )
+		NAND(self, 'nand', latency)
+		Joint(self)
+		NOT(self)
+
+		Pin(self, 'en', 'L').conn = self.nand.inp_a
+		self.nand.out.conn = self.jt.new_pin()
+		self.jt.new_pin().conn = self.nand.inp_b
+		self.jt.new_pin().conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
+
 class Lamp(Obj):
 	def __init__(self, parent, name='lamp', latency=None):
 		Obj.__init__(self, parent, name, latency)

# 現実にはありえない実装ですが「クロックもどき」として
# CLKクラスを追加しました

# en入力端子に'H'を入れると、out出力端子に
# 指定の周波数で'H','L'が変化します


@@ -197,12 +193,15 @@
 
 	def main_loop(self):
 		self.run = True
+		start = time.time()
 		while self.run:
 			cmd_exec( sys.stdin )
 			if self.que:
 				#dbgout( 'que={}'.format(self.que) )
 				(tm, f, args) = self.que.pop(0)
 				self.now = tm
+				if '-f' not in sys.argv:
+					tm_sleep(tm, start)
 				f(*args)
 			else:
 				time.sleep(0.5)

# スケジューラのmain_loopに処理の追加です
# 現実時間の開示時刻をstartに記録し
# -f オプションが指定されてない限りは
# なるべく現実時刻に同期させようとします

# -f は ping のFloodオプションのつもりです ;-p)
# 通常は現実の時間に合わせようと適宜sleepします

# -f のときは寝ないでがんばります


@@ -234,7 +233,6 @@
 	return n1 > n2 if s1 == s2 else s1 > s2
 
 def tm_add( (s1,n1), (s2,n2) ):
-	giga = 1000000000
 	n1 += n2
 	s1 += s2 + n1 / giga
 	n1 %= giga
@@ -243,11 +241,26 @@
 def tm_str( (s,n) ):
 	return '{}.{:0>9}'.format(s, n)
 
+def tm_sleep( tm, start_fsec ):
+	wait = start_fsec + tm_fsec(tm) - time.time()
+	if wait > 0:
+		time.sleep(wait)
+
+def tm_fsec( (s, n) ):
+	return s + float(n) / giga
+	
+def tm_from_fsec(fsec):
+	s = int(fsec)
+	n = int( (fsec - s) * giga )
+	return (s, n)
+
 def dbgout(s, always=False):
 	if always or '-v' in sys.argv:
 		print '{} {}'.format( tm_str(sched.now), s )
 		sys.stdout.flush()
 
+giga = 1000000000
+
 if __name__ == "__main__":
 	sched = Sched()
 
# giga = 1000000000 があちこちにあるのも何なので
# とりあえずグローバル変数 giga に保持させました

# tm_sleep() は、start_fsec の時刻から tm だけ経過した
# 時刻になるまで寝ます
# もう手遅れなら、寝ずにすぐ返ります

# tm_fsec(tm) は、引数(秒,ナノ秒)の組を、
# 小数の秒にして返します

# tm_from_fsec(fsec) は逆に、引数の小数の秒を、
# (秒,ナノ秒)の組にして返します

今回のお試しの構成です。

cmd_L_chika.txt

$ cat cmd_L_chika.txt
CLK(o, 1.0, 'clk_1')
CLK(o, 10.0, 'clk_10')
Lamp(o)
o.clk_1.out.conn = o.clk_10.en
o.clk_10.out.conn = o.lamp.inp
sched.enque( (1,0), o.clk_1.en.set, ('H',) )
sched.enque( (4,0), o.clk_1.en.set, ('L',) )
sched.enque( (5,0), sched.quit )
$ 

# 1 Hz と 10 Hz の2つの「クロックもどき」と
# Lampを1つ用意して

# 1 Hzクロックの出力を、10 Hzクロックのenable端子に入れて
# 10 Hzクロックの出力を、ランプの入力に入れます

# 時刻 1秒 になると、1 Hzクロックのenable端子を'H'に
# 時刻 4秒 になると、1 Hzクロックのenable端子を'L'に
# 時刻 5秒 になると、スケジューラの quit() メソッドを呼び出して
# 終了

それでは実行してみます。

$ ./nand.py < cmd_L_chika.txt
0.050000010 o.lamp L
1.550000020 o.lamp H
1.600000020 o.lamp L
1.650000020 o.lamp H
1.700000020 o.lamp L
1.750000020 o.lamp H
1.800000020 o.lamp L
1.850000020 o.lamp H
1.900000020 o.lamp L
1.950000020 o.lamp H
2.000000020 o.lamp L
2.550000020 o.lamp H
2.600000020 o.lamp L
2.650000020 o.lamp H
2.700000020 o.lamp L
2.750000020 o.lamp H
2.800000020 o.lamp L
2.850000020 o.lamp H
2.900000020 o.lamp L
2.950000020 o.lamp H
3.000000020 o.lamp L
3.550000020 o.lamp H
3.600000020 o.lamp L
3.650000020 o.lamp H
3.700000020 o.lamp L
3.750000020 o.lamp H
3.800000020 o.lamp L
3.850000020 o.lamp H
3.900000020 o.lamp L
3.950000020 o.lamp H
4.000000020 o.lamp L
$ 

# わりと現実時間っぽく表示が更新されてます


$ ./nand.py -f < cmd_L_chika.txt
  :
$ 

# -f オプションを付けると、これまで通りな
# だだだーっとした表示です


$ ./nand.py -f -v < cmd_L_chika.txt

# さらに -v オプションを追加すると
# いっきに詳細な結果が表示されます

# OKです


RSフリップフロップなぞ

状態を保持する基本とは、RSフリップフロップらしいので、 RSFFクラスを作ってみます。

むか〜し、TTL ICの規格表とかで見たような気がします。

nand.py-9-diff.txt

--- nand.py-
+++ nand.py
@@ -143,6 +143,24 @@
 		self.nand_b.out.conn = self.nand_o.inp_b
 		self.nand_o.out.conn = Pin(self, 'out')
 
+class RSFF(Obj):
+	def __init__(self, parent, name='rsff', latency=10):
+		Obj.__init__(self, parent, name)
+		NAND(self, 'nand_r', latency)
+		NAND(self, 'nand_s', latency)
+		Joint(self, 'jt_r')
+		Joint(self, 'jt_s')
+
+		Pin(self, 'R', 'H').conn = self.nand_r.inp_a
+		self.nand_r.out.conn = self.jt_r.new_pin()
+		self.jt_r.new_pin().conn = Pin(self, 'Q')
+		self.jt_r.new_pin().conn = self.nand_s.inp_a
+
+		Pin(self, 'S', 'L').conn = self.nand_s.inp_b
+		self.nand_s.out.conn = self.jt_s.new_pin()
+		self.jt_s.new_pin().conn = Pin(self, 'nQ')
+		self.jt_s.new_pin().conn = self.nand_r.inp_b
+
 class CLK(Obj):
 	def __init__(self, parent, hz=1.0, name='clk'):
 		Obj.__init__(self, parent, name)


# 端子の接続などは

        +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |
   |    +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   |    +---+      |
   .----|    \     |
        |     |O---@------ nQ
S ------|    /
        +---+


+---+---+----+----+
|R  |S  |Q   |nQ  |
+---+---+----+----+
|1  |1  |keep|keep|
+---+---+----+----+
|1  |0  |0   |1   |
+---+---+----+----+
|0  |1  |1   |0   |
+---+---+----+----+
|0  |0  |X   |X   |
+---+---+----+----+

# こういう「やつ」ですね

とりあえず動作確認です

cmd_rsff.txt

$ cat cmd_rsff.txt
RSFF(o)
sched.enque( (1,0), o.rsff.S.set, ('H',) )
sched.enque( (1,1000), o.rsff.R.set, ('L',) )
sched.enque( (1,2000), o.rsff.R.set, ('H',) )
sched.enque( (1,3000), o.rsff.S.set, ('L',) )
sched.enque( (1,4000), o.rsff.S.set, ('H',) )
sched.enque( (5,0), sched.quit )
$ 

$ ./nand.py -v -f < cmd_rsff.txt > out_rsff.txt
$ 

$ grep Q out_rsff.txt
0.000000010 o.rsff.Q H
0.000000010 o.rsff.nQ H
0.000000020 o.rsff.Q L
1.000001010 o.rsff.Q H
1.000001020 o.rsff.nQ L
1.000003010 o.rsff.nQ H
1.000003020 o.rsff.Q L
$ 

# 起動直後、バタっとしてますが
# まぁ、OKでしょう

out_rsff.txt


多入力のゲート

RSフリップフロップの次は、常識的に考えてJKフリップフロップのような気がしますが...

どうもWikipediaによると、JKフリップフロップで3入力のNANDゲートを使ってる様子。

ここはひとつ、3入力以上のAND, OR, NAND, NORをまとめてやっつけておきます。

nand.py-10-diff.txt

--- nand.py-
+++ nand.py
@@ -143,6 +143,84 @@
 		self.nand_b.out.conn = self.nand_o.inp_b
 		self.nand_o.out.conn = Pin(self, 'out')
 
+class AND_N(Obj):
+	def __init__(self, parent, n, name='and_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		AND(self, 'and_', latency)
+		if n == 2:
+			Pin(self, 'inp_0').conn = self.and_.inp_a
+			Pin(self, 'inp_1').conn = self.and_.inp_b
+
+		elif n == 3:
+			AND_N(self, 2, 'and_n', latency)
+			new_pin_conn(self, self.and_n, 0)
+			self.and_n.out.conn = self.and_.inp_a
+			Pin(self, 'inp_2').conn = self.and_.inp_b
+
+		else: # n >= 4
+			nb = n / 2
+			na = n - nb
+
+			AND_N(self, na, 'and_na', latency)
+			new_pin_conn(self, self.and_na, 0)
+			self.and_na.out.conn = self.and_.inp_a
+
+			AND_N(self, nb, 'and_nb', latency)
+			new_pin_conn(self, self.and_nb, na)
+			self.and_nb.out.conn = self.and_.inp_b
+
+		sef.and_.out.conn = Pin(self, 'out')
+

# N入力のANDです

# まず内部に従来の2入力のANDを用意して
# 2のときは内部のinp_a, inp_bをinp_0, inp_1につなぐだけ
# 3のときは2と1に分けて、2を再帰
# 4以上では、半分に分けてそれぞれ再帰
# 用意した2入力のANDの結果を、全体の出力に

# 再帰して取得したインスタンスの入力を、
# 自身の入力に順につないでいく処理は、
# 後述の new_pin_conn() 関数にまかせてます


+class OR_N(Obj):
+	def __init__(self, parent, n, name='or_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		OR(self, 'or_', latency)
+		if n == 2:
+			Pin(self, 'inp_0').conn = self.or_.inp_a
+			Pin(self, 'inp_1').conn = self.or_.inp_b
+
+		elif n == 3:
+			OR_N(self, 2, 'or_n', latency)
+			new_pin_conn(self, self.or_n, 0)
+			self.or_n.out.conn = self.or_.inp_a
+			Pin(self, 'inp_2').conn = self.or_.inp_b
+
+		else: # n >= 4
+			nb = n / 2
+			na = n - nb
+
+			OR_N(self, na, 'or_na', latency)
+			new_pin_conn(self, self.or_na, 0)
+			self.or_na.out.conn = self.or_.inp_a
+
+			OR_N(self, nb, 'or_nb', latency)
+			new_pin_conn(self, self.or_nb, na)
+			self.or_nb.out.conn = self.or_.inp_b
+
+		sef.or_.out.conn = Pin(self, 'out')
+

# AND_N を元にして、ちょいちょいと書き換えて OR_N に
# ANDがORに変わっただけで、やってる事は同じです


+class NAND_N(Obj):
+	def __init__(self, parent, n, name='nand_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		AND_N(self, 'and_n', latency)
+		new_pin_conn(self, self.and_n, 0)
+		NOT(self, 'not_', latency)
+		self.and_n.out.conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
+

# AND_N の出力に NOT をつないで、できあがり


+class NOR_N(Obj):
+	def __init__(self, parent, n, name='nor_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		OR_N(self, 'or_n', latency)
+		new_pin_conn(self, self.or_n, 0)
+		NOT(self, 'not_', latency)
+		self.or_n.out.conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
+

# OR_N の出力に NOT をつないで、できあがり


@@ -238,6 +316,14 @@
 			dbgout( 'cmd_exec "{}"'.format(s) )
 			exec s
 
+def new_pin_conn(obj, in_obj, sta_i):
+	for i in range(in_obj.n):
+		pin = getattr( in_obj, name_inp(i) )
+		Pin( obj, name_inp( sta_i + i ) ).conn = pin
+
+def name_inp(i):
+	return 'inp_{}'.format(i)
+
 def str_v(v):
 	return { True: 'H', False: 'L', None: 'Hi-Z' }.get(v, '?')
 
# 例えば AND_N の場合では、
# obj が自身の AND_N で、
# in_obj は内部的に再帰で生成した AND_N のインスタンス

# 自身のPin inp_{sta_i} を、in_obj の inp_0 に接続
# 自身のPin inp_{sta_i +1} を、in_obj の inp_1 に接続
# 自身のPin inp_{sta_i +2} を、in_obj の inp_2 に接続
#   :
# と、in_objの入力のPinの数だけ、
# 自身にPinを生成して、つないでいきます

作ったはいいものの、動作確認が少々面倒ですな。

動作確認用のコマンドを生成するプログラムを作ってみます。

cmd_gate_n.py

$ cat cmd_gate_n.py
#!/usr/bin/env python

from nand import tm_add

def test(klass_name, name, nlst, tm, tm_step):
	for n in nlst:
		tm = test_klass( klass_name, n, '{}{}'.format(name, n), tm, tm_step )
	return tm

def test_klass(klass_name, n, name, tm, tm_step):
	print "{}(o, {}, '{}')".format(klass_name, n, name)
	print "Lamp(o, 'lamp_{}')".format(name)
	print "o.{}.out.conn = o.lamp_{}.inp".format(name, name)
	tm = test_pin( n, name, tm, tm_step, ('L',) )
	tm = test_pin( n, name, tm, tm_step, ('H',) )
	tm = test_pin( n, name, tm, tm_step, ('L',) )
	return tm

def test_pin(n, name, tm, tm_step, v):
	s = "sched.enque( {}, o.{}.inp_{}.set, {} )"
	for i in range(n):
		print s.format(tm, name, i, v)
		tm = tm_add(tm, tm_step)
	return tm

if __name__ == "__main__":
	tm_step = (0,   1000000)
	tm = (1, 100000000)
	nlst = [ 2, 3, 4, 20, 64 ]
	(s, ns) = test( 'AND_N', 'and_', nlst, tm, tm_step )
	tm = (s+1, 0)
	(s, ns) = test( 'OR_N', 'or_', nlst, tm, tm_step )
	tm = (s+1, 0)
	(s, ns) = test( 'NAND_N', 'nand_', nlst, tm, tm_step )
	tm = (s+1, 0)
	(s, ns) = test( 'NOR_N', 'nor_', nlst, tm, tm_step )
	tm = (s+1, 0)
	print "sched.enque( {}, sched.quit )".format(tm)

# EOF
$ 

nand.py から tm_add() を import して使ってます。

そのまま実行してみると、、、

$ ./cmd_gate_n.py
AND_N(o, 2, 'and_2')
Lamp(o, 'lamp_and_2')
o.and_2.out.conn = o.lamp_and_2.inp
sched.enque( (1, 100000000), o.and_2.inp_0.set, ('L',) )
sched.enque( (1, 101000000), o.and_2.inp_1.set, ('L',) )
sched.enque( (1, 102000000), o.and_2.inp_0.set, ('H',) )
sched.enque( (1, 103000000), o.and_2.inp_1.set, ('H',) )
sched.enque( (1, 104000000), o.and_2.inp_0.set, ('L',) )
sched.enque( (1, 105000000), o.and_2.inp_1.set, ('L',) )
AND_N(o, 3, 'and_3')
Lamp(o, 'lamp_and_3')
o.and_3.out.conn = o.lamp_and_3.inp
sched.enque( (1, 106000000), o.and_3.inp_0.set, ('L',) )
sched.enque( (1, 107000000), o.and_3.inp_1.set, ('L',) )
sched.enque( (1, 108000000), o.and_3.inp_2.set, ('L',) )
sched.enque( (1, 109000000), o.and_3.inp_0.set, ('H',) )
sched.enque( (1, 110000000), o.and_3.inp_1.set, ('H',) )
sched.enque( (1, 111000000), o.and_3.inp_2.set, ('H',) )
sched.enque( (1, 112000000), o.and_3.inp_0.set, ('L',) )
sched.enque( (1, 113000000), o.and_3.inp_1.set, ('L',) )
sched.enque( (1, 114000000), o.and_3.inp_2.set, ('L',) )
  :

NOR_N(o, 64, 'nor_64')
Lamp(o, 'lamp_nor_64')
o.nor_64.out.conn = o.lamp_nor_64.inp
sched.enque( (4, 87000000), o.nor_64.inp_0.set, ('L',) )
sched.enque( (4, 88000000), o.nor_64.inp_1.set, ('L',) )
sched.enque( (4, 89000000), o.nor_64.inp_2.set, ('L',) )
  :

sched.enque( (4, 276000000), o.nor_64.inp_61.set, ('L',) )
sched.enque( (4, 277000000), o.nor_64.inp_62.set, ('L',) )
sched.enque( (4, 278000000), o.nor_64.inp_63.set, ('L',) )
sched.enque( (5, 0), sched.quit )
$ 

てな具合に、nand.py に喰わせるコマンドを出力します。

やってる事の概略は

、、、です。

それでは実行してみます。

$ ./cmd_gate_n.py | ./nand.py
1.100000020 o.lamp_and_2 L
1.103000020 o.lamp_and_2 H
1.104000020 o.lamp_and_2 L
1.106000040 o.lamp_and_3 L
1.111000020 o.lamp_and_3 H
1.112000040 o.lamp_and_3 L
1.115000040 o.lamp_and_4 L
1.122000040 o.lamp_and_4 H
1.123000040 o.lamp_and_4 L
1.127000100 o.lamp_and_20 L
1.166000080 o.lamp_and_20 H
1.167000100 o.lamp_and_20 L
1.187000120 o.lamp_and_64 L
1.314000120 o.lamp_and_64 H
1.315000120 o.lamp_and_64 L
2.000000020 o.lamp_or_2 H
2.001000020 o.lamp_or_2 L
2.002000020 o.lamp_or_2 H
2.005000020 o.lamp_or_2 L
2.006000040 o.lamp_or_3 H
2.008000020 o.lamp_or_3 L
2.009000040 o.lamp_or_3 H
2.014000020 o.lamp_or_3 L
2.015000040 o.lamp_or_4 H
2.018000040 o.lamp_or_4 L
2.019000040 o.lamp_or_4 H
2.026000040 o.lamp_or_4 L
2.027000100 o.lamp_or_20 H
2.046000080 o.lamp_or_20 L
2.047000100 o.lamp_or_20 H
2.086000080 o.lamp_or_20 L
2.087000120 o.lamp_or_64 H
2.150000120 o.lamp_or_64 L
2.151000120 o.lamp_or_64 H
2.278000120 o.lamp_or_64 L
3.000000030 o.lamp_nand_2 H
3.003000030 o.lamp_nand_2 L
3.004000030 o.lamp_nand_2 H
3.006000050 o.lamp_nand_3 H
3.011000030 o.lamp_nand_3 L
3.012000050 o.lamp_nand_3 H
3.015000050 o.lamp_nand_4 H
3.022000050 o.lamp_nand_4 L
3.023000050 o.lamp_nand_4 H
3.027000110 o.lamp_nand_20 H
3.066000090 o.lamp_nand_20 L
3.067000110 o.lamp_nand_20 H
3.087000130 o.lamp_nand_64 H
3.214000130 o.lamp_nand_64 L
3.215000130 o.lamp_nand_64 H
4.000000030 o.lamp_nor_2 L
4.001000030 o.lamp_nor_2 H
4.002000030 o.lamp_nor_2 L
4.005000030 o.lamp_nor_2 H
4.006000050 o.lamp_nor_3 L
4.008000030 o.lamp_nor_3 H
4.009000050 o.lamp_nor_3 L
4.014000030 o.lamp_nor_3 H
4.015000050 o.lamp_nor_4 L
4.018000050 o.lamp_nor_4 H
4.019000050 o.lamp_nor_4 L
4.026000050 o.lamp_nor_4 H
4.027000110 o.lamp_nor_20 L
4.046000090 o.lamp_nor_20 H
4.047000110 o.lamp_nor_20 L
4.086000090 o.lamp_nor_20 H
4.087000130 o.lamp_nor_64 L
4.150000130 o.lamp_nor_64 H
4.151000130 o.lamp_nor_64 L
4.278000130 o.lamp_nor_64 H
$ 


# 一応

$ ./cmd_gate_n.py > cmd_gate_n.txt

# しておいて
# どこで出力が変化したか、突き合わせて見ておきます


# AND_N
1.100000020 o.lamp_and_2 L # inp_0 への L
1.103000020 o.lamp_and_2 H # inp_1 への H
1.104000020 o.lamp_and_2 L # inp_0 への L

1.106000040 o.lamp_and_3 L # ここらは省略して
1.111000020 o.lamp_and_3 H #   :
1.112000040 o.lamp_and_3 L
1.115000040 o.lamp_and_4 L
1.122000040 o.lamp_and_4 H
1.123000040 o.lamp_and_4 L
1.127000100 o.lamp_and_20 L
1.166000080 o.lamp_and_20 H
1.167000100 o.lamp_and_20 L

1.187000120 o.lamp_and_64 L # inp_0 への L
1.314000120 o.lamp_and_64 H # inp_63 への H
1.315000120 o.lamp_and_64 L # inp_0 への L


# OR_N
2.000000020 o.lamp_or_2 H # inp_0 への L ( 入力側 NOT 使ってる事情 )
2.001000020 o.lamp_or_2 L # inp_1 への L
2.002000020 o.lamp_or_2 H # inp_0 への H
2.005000020 o.lamp_or_2 L # inp_1 への L

2.006000040 o.lamp_or_3 H # ここらは省略して
2.008000020 o.lamp_or_3 L #   :
2.009000040 o.lamp_or_3 H
2.014000020 o.lamp_or_3 L
2.015000040 o.lamp_or_4 H
2.018000040 o.lamp_or_4 L
2.019000040 o.lamp_or_4 H
2.026000040 o.lamp_or_4 L
2.027000100 o.lamp_or_20 H
2.046000080 o.lamp_or_20 L
2.047000100 o.lamp_or_20 H
2.086000080 o.lamp_or_20 L

2.087000120 o.lamp_or_64 H # inp_0 への L ( 入力側 NOT 使ってる事情 )
2.150000120 o.lamp_or_64 L # inp_63 への L
2.151000120 o.lamp_or_64 H # inp_0 への H
2.278000120 o.lamp_or_64 L # inp_63 への L

# 全入力に'L'をセットして 'None' が無い状態からは、
# 普通に OR として動作


# NAND_N
3.000000030 o.lamp_nand_2 H # ここらは省略して
3.003000030 o.lamp_nand_2 L #   :
3.004000030 o.lamp_nand_2 H
3.006000050 o.lamp_nand_3 H
3.011000030 o.lamp_nand_3 L
3.012000050 o.lamp_nand_3 H
3.015000050 o.lamp_nand_4 H
3.022000050 o.lamp_nand_4 L
3.023000050 o.lamp_nand_4 H
3.027000110 o.lamp_nand_20 H
3.066000090 o.lamp_nand_20 L
3.067000110 o.lamp_nand_20 H

3.087000130 o.lamp_nand_64 H # inp_0 への L
3.214000130 o.lamp_nand_64 L # inp_63 への H
3.215000130 o.lamp_nand_64 H # inp_0 への L

# NOR_N
4.000000030 o.lamp_nor_2 L # ここらは省略して
4.001000030 o.lamp_nor_2 H #   :
4.002000030 o.lamp_nor_2 L
4.005000030 o.lamp_nor_2 H
4.006000050 o.lamp_nor_3 L
4.008000030 o.lamp_nor_3 H
4.009000050 o.lamp_nor_3 L
4.014000030 o.lamp_nor_3 H
4.015000050 o.lamp_nor_4 L
4.018000050 o.lamp_nor_4 H
4.019000050 o.lamp_nor_4 L
4.026000050 o.lamp_nor_4 H
4.027000110 o.lamp_nor_20 L
4.046000090 o.lamp_nor_20 H
4.047000110 o.lamp_nor_20 L
4.086000090 o.lamp_nor_20 H

4.087000130 o.lamp_nor_64 L # inp_0 への L ( 入力側 NOT 使ってる事情 )
4.150000130 o.lamp_nor_64 H # inp_63 への L
4.151000130 o.lamp_nor_64 L # inp_0 への H
4.278000130 o.lamp_nor_64 H # inp_63 への L

とりあえず、入力端子全てがHかLに確定していれば OK ということで。


JKフリップフロップでも

では満を持して(こんなのばっかり)、Wikipediaの回路図のJKフリップフロップでも試してみます。

回路図は入力側に3入力のNANDがあって、出力側にRSFFをつないだ構成ですね。

       .-------------------------.
       |   +---+                 |
       .---|    \     +-------+  |
    J -----|     |O---| R   Q |----@--- Q
       .---|    /     |       |  | |
       |   +---+      |       |  | |
CLK ---@              |(RSFF) |  | |
       |   +---+      |       |  | |
       .---|    \     |       |  | |
    K -----|     |O---| S  nQ |--@----- nQ
       .---|    /     +-------+    |
       |   +---+                   |
       .---------------------------.

真理値表は

+---+---+---+-------+
| J | K | Q | nextQ |
+---+---+---+-------+
| 0 | 0 | 0 | 0     |
+---+---+---+-------+
| 0 | 1 | 0 | 0     |
+---+---+---+-------+
| 1 | 0 | 0 | 1     |
+---+---+---+-------+
| 1 | 0 | 1 | 1     |
+---+---+---+-------+
| 1 | 1 | 0 | 1     |
+---+---+---+-------+
| 1 | 1 | 1 | 0     |
+---+---+---+-------+

nand.py-11-diff.txt

--- nand.py-
+++ nand.py
@@ -239,6 +239,34 @@
 		self.jt_s.new_pin().conn = Pin(self, 'nQ')
 		self.jt_s.new_pin().conn = self.nand_r.inp_b
 
+class JKFF(Obj):
+	def __init__(self, parent, name='jkff', latency=10):
+		Obj.__init__(self, parent, name)
+		RSFF(self, 'rsff', latency)
+		NAND_N(self, 3, 'nand_3_J', latency)
+		NAND_N(self, 3, 'nand_3_K', latency)
+		Joint(self, 'jt_Q')
+		Joint(self, 'jt_nQ')
+		Joint(self, 'jt_clk')
+
+		Pin(self, 'CLK', 'L').conn = self.jt_clk.new_pin()
+
+		Pin(self, 'J', 'L').conn = self.nand_3_J.inp_1
+		self.jt_clk.new_pin().conn = self.nand_3_J.inp_2
+		self.nand_3_J.out.conn = self.rsff.R
+
+		Pin(self, 'K', 'L').conn = self.nand_3_K.inp_1
+		self.jt_clk.new_pin().conn = self.nand_3_K.inp_2
+		self.nand_3_K.out.conn = self.rsff.S
+
+		self.rsff.Q.conn = self.jt_Q.new_pin()
+		self.jt_Q.new_pin().conn = Pin(self, 'Q')
+		self.jt_Q.new_pin().conn = self.nand_3_K.inp_0
+
+		self.rsff.nQ.conn = self.jt_nQ.new_pin()
+		self.jt_nQ.new_pin().conn = Pin(self, 'nQ')
+		self.jt_nQ.new_pin().conn = self.nand_3_J.inp_0
+
 class CLK(Obj):
 	def __init__(self, parent, hz=1.0, name='clk'):
 		Obj.__init__(self, parent, name)

# そのまんま、つないでます

さて、動作確認のコマンドのテキストファイルを作ってみますが、、、 コマンドファイルにコメントを入れても大丈夫でしょうか?

$ python
  :
>>> s = '# hoge'
>>> exec s
>>> 

# 何も起きないので、大丈夫そうですね

cmd_jkff.txt

$ cat cmd_jkff.txt
JKFF(o)
Lamp(o)
o.jkff.Q.conn = o.lamp.inp

# J=L, K=L
sched.enque( (1, 100000000), o.jkff.J.set, ('L',) )
sched.enque( (1, 110000000), o.jkff.K.set, ('L',) )
sched.enque( (1, 120000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 130000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 140000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 150000000), o.jkff.CLK.set, ('L',) )

# J=H, K=L
sched.enque( (1, 200000000), o.jkff.J.set, ('H',) )
sched.enque( (1, 220000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 230000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 240000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 250000000), o.jkff.CLK.set, ('L',) )

# J=L, K=L
sched.enque( (1, 300000000), o.jkff.J.set, ('L',) )
sched.enque( (1, 320000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 330000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 340000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 350000000), o.jkff.CLK.set, ('L',) )

# J=L, K=H
sched.enque( (1, 400000000), o.jkff.K.set, ('H',) )
sched.enque( (1, 420000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 430000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 440000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 450000000), o.jkff.CLK.set, ('L',) )

# J=H, K=H
sched.enque( (1, 500000000), o.jkff.J.set, ('H',) )
sched.enque( (1, 520000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 530000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 540000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 550000000), o.jkff.CLK.set, ('L',) )

sched.enque( (5, 0), sched.quit )
$ 

# JKFFの出力Qにランプをつないで

# J, K 入力を変えてみては
# CLK 端子をバタバタさせてみてます

それでは実行してみます。

$ ./nand.py < cmd_jkff.txt
0.000000010 o.lamp H
0.000000020 o.lamp L
1.220000040 o.lamp H
1.420000050 o.lamp L
1.520000040 o.lamp H
1.520000110 o.lamp L
1.520000160 o.lamp H
1.520000230 o.lamp L
1.520000280 o.lamp H
1.520000350 o.lamp L
1.520000400 o.lamp H
1.520000470 o.lamp L
1.520000520 o.lamp H
1.520000590 o.lamp L
1.520000640 o.lamp H
1.520000710 o.lamp L
1.520000760 o.lamp H
1.520000830 o.lamp L
1.520000880 o.lamp H
1.520000950 o.lamp L
1.520001000 o.lamp H
1.520001070 o.lamp L
1.520001120 o.lamp H
1.520001190 o.lamp L
1.520001240 o.lamp H
1.520001310 o.lamp L
1.520001360 o.lamp H
1.520001430 o.lamp L
1.520001480 o.lamp H
1.520001550 o.lamp L
1.520001600 o.lamp H
1.520001670 o.lamp L
1.520001720 o.lamp H
1.520001790 o.lamp L
1.520001840 o.lamp H
1.520001910 o.lamp L
1.520001960 o.lamp H
1.520002030 o.lamp L
1.520002080 o.lamp H
1.520002150 o.lamp L
  :
  :
^C

きえーっ。J=H, K=H にして CLK=H に上げた途端、発振してしまいました。

それまでは、意図した通りそれらしく動作してましたが、、、

回路図を見直してみまするに、J=H, K=H, CLK=H にしたときは、 入力側の3入力NANDは、Q,nQからの入力をNOTとして反転するだけなので

.---------------------.
|   +                 |
|   |\     +-------+  |
.---| >O---| R   Q |----@--- Q
    |/     |       |  | |
    +      |       |  | |
	   |(RSFF) |  | |
    +      |       |  | |
    |\     |       |  | |
.---| >O---| S  nQ |--@----- nQ
|   |/     +-------+    |
|   +                   |
.-----------------------.

ん?

Q,Qnが0,1なら、R,Sに0,1が入り、いくらか遅延してQ,Qnが1,0に。

Q,Qnが1,0なら、R,Sに1,0が入り、いくらか遅延してQ,Qnが0,1に。

そりゃ発振するのでは?

素人なものでWebで検索してみると、、、 どうも「エッジトリガ」なタイプにしないとダメみたいですね。


エッジトリガなJKFF

と言う事で、Webを検索して拾ってきたエッジトリガなJKFFの接続です。

         .----------------------------------------.
         |   +---+      +------+                  |
         .---|    \     |      |                  |
             |     |O---| R  Q |                  |
J -----------|    /     |      |                  |
             +---+      |      |                  |
                        |      |                  |
             +---+      |      |       +------+   |
       .-----|    \     |      |       |      |   |
       |     |     |----| S nQ |---@---| R  Q |---|--@--- Q
       | .---|    /     |      |   |   |      |   |  |
       | |   +---+      +------+   |   |      |   |  |
       | .----------\ /------------.   |      |   |  |
CLK ---@             X                 |      |   |  |
       | .----------/ \------------.   |      |   |  |
       | |   +---+      +------+   |   |      |   |  |
       | .---|    \     |      |   |   |      |   |  |
       |     |     |----| R  Q |---@---| S nQ |---@--|--- nQ
       .-----|    /     |      |       |      |      |
             +---+      |      |       +------+      |
                        |      |                     |
             +---+      |      |                     |
K -----------|    \     |      |                     |
             |     |O---| S nQ |                     |
         .---|    /     |      |                     |
         |   +---+      +------+                     |
         .-------------------------------------------.


そのままつないで試してみますと

先のWikipediaの回路図で作ったJKFFクラスを、エッジトリガな回路図のものに書き換えてみます。

nand.py-12-diff.txt

# 差分が判りにくいので更新後のソースで

class JKFF(Obj):
	def __init__(self, parent, name='jkff', latency=10):
		Obj.__init__(self, parent, name)
		RSFF(self, 'rsff_o', latency)
		RSFF(self, 'rsff_j', latency)
		RSFF(self, 'rsff_k', latency)
		NAND(self, 'nand_j', latency)
		NAND(self, 'nand_k', latency)
		AND(self, 'and_j', latency)
		AND(self, 'and_k', latency)
		Joint(self, 'jt_clk')
		Joint(self, 'jt_j_i')
		Joint(self, 'jt_k_i')
		Joint(self, 'jt_j_o')
		Joint(self, 'jt_k_o')

		Pin(self, 'CLK', 'L').conn = self.jt_clk.new_pin()
		self.jt_clk.new_pin().conn = self.and_j.inp_a
		self.jt_clk.new_pin().conn = self.and_k.inp_b

		Pin(self, 'J', 'L').conn = self.nand_j.inp_b
		Pin(self, 'K', 'L').conn = self.nand_k.inp_a

		self.nand_j.out.conn = self.rsff_j.R
		self.and_j.out.conn = self.rsff_j.S
		self.and_k.out.conn = self.rsff_k.R
		self.nand_k.out.conn = self.rsff_k.S

		self.rsff_j.nQ.conn = self.jt_j_i.new_pin()
		self.jt_j_i.new_pin().conn = self.rsff_o.R
		self.jt_j_i.new_pin().conn = self.and_k.inp_a
		self.jt_k_i.new_pin().conn = self.and_j.inp_b
		self.jt_k_i.new_pin().conn = self.rsff_o.S
		self.rsff_k.Q.conn = self.jt_k_i.new_pin()

		self.rsff_o.Q.conn = self.jt_j_o.new_pin()
		self.jt_j_o.new_pin().conn = self.nand_k.inp_b
		self.jt_j_o.new_pin().conn = Pin(self, 'Q')

		self.rsff_o.nQ.conn = self.jt_k_o.new_pin()
		self.jt_k_o.new_pin().conn = self.nand_j.inp_a
		self.jt_k_o.new_pin().conn = Pin(self, 'nQ')

# 3つのRSFF
# 2つのNAND
# 2つのAND
# 5つのJoint
# 回路図そのままです

それでは実行してみます。

$ ./nand.py < cmd_jkff.txt
0.000000010 o.lamp H
0.000000020 o.lamp L
0.000000030 o.lamp H
0.000000040 o.lamp L
0.000000050 o.lamp H
0.000000060 o.lamp L
0.000000070 o.lamp H
0.000000080 o.lamp L
0.000000090 o.lamp H
0.000000100 o.lamp L
0.000000110 o.lamp H
0.000000120 o.lamp L
0.000000130 o.lamp H
0.000000140 o.lamp L
0.000000150 o.lamp H
0.000000160 o.lamp L
0.000000170 o.lamp H
0.000000180 o.lamp L
  :

# きえーっ!
# 前回に増して、ハナから発振してます!

とりあえず -v オプションで追いかけてみます。

$ ./nand.py -v < cmd_jkff.txt | grep 'rsff_o.[RSQn][ Q]' | head -20
0.000000000 o.jkff.rsff_o.R H
0.000000000 o.jkff.rsff_o.S L
0.000000010 o.jkff.rsff_o.Q H
0.000000010 o.jkff.rsff_o.nQ H
0.000000010 o.jkff.rsff_o.S H
0.000000020 o.jkff.rsff_o.Q L
0.000000020 o.jkff.rsff_o.nQ L
0.000000020 o.jkff.rsff_o.S L
0.000000030 o.jkff.rsff_o.nQ H
0.000000030 o.jkff.rsff_o.Q H
0.000000030 o.jkff.rsff_o.S H
0.000000040 o.jkff.rsff_o.Q L
0.000000040 o.jkff.rsff_o.nQ L
0.000000050 o.jkff.rsff_o.nQ H
0.000000050 o.jkff.rsff_o.Q H
0.000000060 o.jkff.rsff_o.Q L
0.000000060 o.jkff.rsff_o.nQ L
0.000000070 o.jkff.rsff_o.nQ H
0.000000070 o.jkff.rsff_o.Q H
0.000000080 o.jkff.rsff_o.Q L
  :

# 最終的に出力側のRSFFの出力端子Qが変動してるのは確かなので、
# 出力側のRSFFの入力R, Sと出力Q, nQ を表示してみました

# 時刻 0 で初期化からの R=H, S=L
#   R側のNANDの入力はH, (None) --> Noneが偽扱いで次の出力はH
#   S側のNANDの入力は(None), L --> 次の出力はH

# 時刻 10 では、時刻 0 の時の結果が出てQ=H, nQ=H
# さらに K側から S=H が来てるので
#   R側のNANDの入力はH, H --> 次の出力はL
#   S側のNANDの入力はH, H --> 次の出力はL

# 時刻 20 では、時刻 10 の時の結果が出てQ=L, nQ=L
# さらに K側から S=L が来てるので
#   R側のNANDの入力はH, L --> 次の出力はH
#   S側のNANDの入力はL, L --> 次の出力はH

# 時刻 30 では、時刻 20 の時の結果が出てQ=H, nQ=H
# さらに K側から S=H が来てるので
#   R側のNANDの入力はH, H --> 次の出力はL
#   S側のNANDの入力はH, H --> 次の出力はL

# ここまでで、K側からのSの変化は止まりますが

# 時刻 40 では、時刻 30 の時の結果が出てQ=L, nQ=L
#   R側のNANDの入力はH, L --> 次の出力はH
#   S側のNANDの入力はH, L --> 次の出力はH

# 時刻 50 では、時刻 40 の時の結果が出てQ=H, nQ=H
#   R側のNANDの入力はH, H --> 次の出力はL
#   S側のNANDの入力はH, H --> 次の出力はL
  :

# てな具合に、R, Sへの入力はH, Hのままで
# Q, nQ が発振してます

Q, nQ が同時に同じ値を出力してるので、状態もへったくれもありません。 R, S とも H で状態キープなはずが、NANDはNOTと化して互いの出力を反転し続ける動作になって、 発振してます。

一応RSFFの初期化としてR=H, S=Lをenqueしてましたが、 その状態に出力が定まる前に、K側からの入力変化が入って発狂してるような、、、

だとしたら、RSFFの出力端子も初期化としてQ=L, nQ=Hをenqueしてみれば、 サクっと状態が定まり、何か進展するかもしれません。


RSFFの出力側も初期化してみる

やってみます。

nand.py-13-diff.txt

--- nand.py-
+++ nand.py
@@ -29,7 +29,10 @@
 		self.v = None
 		self.conn = None
 		if v != None:
-			self.enque( self.set, (v,) )
+			self.enque_set(v)
+
+	def enque_set(self, v):
+		self.enque( self.set, (v,) )
 
 	def set(self, v):
 		v = bool_v(v)

# Pinクラスにset()メソッド呼び出しをenqueするメソッドを追加しておいて...


@@ -233,11 +236,13 @@
 		self.nand_r.out.conn = self.jt_r.new_pin()
 		self.jt_r.new_pin().conn = Pin(self, 'Q')
 		self.jt_r.new_pin().conn = self.nand_s.inp_a
+		self.nand_r.out.enque_set('L')
 
 		Pin(self, 'S', 'L').conn = self.nand_s.inp_b
 		self.nand_s.out.conn = self.jt_s.new_pin()
 		self.jt_s.new_pin().conn = Pin(self, 'nQ')
 		self.jt_s.new_pin().conn = self.nand_r.inp_b
+		self.nand_s.out.enque_set('H')
 
 class JKFF(Obj):
 	def __init__(self, parent, name='jkff', latency=10):

# RSFFクラスの初期化時に、内部NANDの出力端子に値をセットするよう
# スケジューラに登録を追加してます

# 「出力側の初期化」と言いつつ、相方のNANDの入力もこれで決まるはずです

それでは実行してみます。

$ ./nand.py < cmd_jkff.txt
0.000000000 o.lamp L
1.220000040 o.lamp H
1.420000050 o.lamp L
1.520000040 o.lamp H
1.540000050 o.lamp L
$ 

! 何やらそれらしい表示で終了しました。

$ cat cmd_jkff.txt 
JKFF(o)
Lamp(o)
o.jkff.Q.conn = o.lamp.inp

# J=L, K=L
sched.enque( (1, 100000000), o.jkff.J.set, ('L',) )
sched.enque( (1, 110000000), o.jkff.K.set, ('L',) )
sched.enque( (1, 120000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 130000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 140000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 150000000), o.jkff.CLK.set, ('L',) )

# J=H, K=L
sched.enque( (1, 200000000), o.jkff.J.set, ('H',) )
sched.enque( (1, 220000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 230000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 240000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 250000000), o.jkff.CLK.set, ('L',) )

# J=L, K=L
sched.enque( (1, 300000000), o.jkff.J.set, ('L',) )
sched.enque( (1, 320000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 330000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 340000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 350000000), o.jkff.CLK.set, ('L',) )

# J=L, K=H
sched.enque( (1, 400000000), o.jkff.K.set, ('H',) )
sched.enque( (1, 420000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 430000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 440000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 450000000), o.jkff.CLK.set, ('L',) )

# J=H, K=H
sched.enque( (1, 500000000), o.jkff.J.set, ('H',) )
sched.enque( (1, 520000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 530000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 540000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 550000000), o.jkff.CLK.set, ('L',) )

sched.enque( (5, 0), sched.quit )
$ 

cmd_jkff.txt の時刻と突き合わせてみて、 latency の細かいところはさて置き、だいたい意図した動作になってます。

RSFFに手を入れたので、RSFF自身の動作も確かめ直しておきます。

以前に動作確認に使ったコマンドのファイルと結果の記録です。

cmd_rsff.txt

out_rsff.txt

$ cat cmd_rsff.txt
RSFF(o)
sched.enque( (1,0), o.rsff.S.set, ('H',) )
sched.enque( (1,1000), o.rsff.R.set, ('L',) )
sched.enque( (1,2000), o.rsff.R.set, ('H',) )
sched.enque( (1,3000), o.rsff.S.set, ('L',) )
sched.enque( (1,4000), o.rsff.S.set, ('H',) )
sched.enque( (5,0), sched.quit )
$ 

$ ./nand.py -v < cmd_rsff.txt 
0.000000000 cmd_exec "RSFF(o)"
0.000000000 cmd_exec "sched.enque( (1,0), o.rsff.S.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,1000), o.rsff.R.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,2000), o.rsff.R.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (1,3000), o.rsff.S.set, ('L',) )"
0.000000000 cmd_exec "sched.enque( (1,4000), o.rsff.S.set, ('H',) )"
0.000000000 cmd_exec "sched.enque( (5,0), sched.quit )"
0.000000000 o.rsff.R H
0.000000000 o.rsff.nand_r.inp_a H
0.000000000 o.rsff.nand_r.out L
0.000000000 o.rsff.jt_r.pin0 L
0.000000000 o.rsff.S L
0.000000000 o.rsff.nand_s.inp_b L
0.000000000 o.rsff.nand_s.out H
0.000000000 o.rsff.jt_s.pin0 H
0.000000000 o.rsff.nand_r update
0.000000000 o.rsff.jt_r.pin1 L
0.000000000 o.rsff.Q L
0.000000000 o.rsff.jt_r.pin2 L
0.000000000 o.rsff.nand_s.inp_a L
0.000000000 o.rsff.nand_s update
0.000000000 o.rsff.jt_s.pin1 H
0.000000000 o.rsff.nQ H
0.000000000 o.rsff.jt_s.pin2 H
0.000000000 o.rsff.nand_r.inp_b H
0.000000000 o.rsff.nand_r update
0.000000010 o.rsff.nand_r.out H
0.000000010 o.rsff.jt_r.pin0 H
0.000000010 o.rsff.nand_r.out L
0.000000010 o.rsff.jt_r.pin0 L
1.000000000 o.rsff.S H
1.000000000 o.rsff.nand_s.inp_b H
1.000000000 o.rsff.nand_s update
1.000001000 o.rsff.R L
1.000001000 o.rsff.nand_r.inp_a L
1.000001000 o.rsff.nand_r update
1.000001010 o.rsff.nand_r.out H
1.000001010 o.rsff.jt_r.pin0 H
1.000001010 o.rsff.jt_r.pin1 H
1.000001010 o.rsff.Q H
1.000001010 o.rsff.jt_r.pin2 H
1.000001010 o.rsff.nand_s.inp_a H
1.000001010 o.rsff.nand_s update
1.000001020 o.rsff.nand_s.out L
1.000001020 o.rsff.jt_s.pin0 L
1.000001020 o.rsff.jt_s.pin1 L
1.000001020 o.rsff.nQ L
1.000001020 o.rsff.jt_s.pin2 L
1.000001020 o.rsff.nand_r.inp_b L
1.000001020 o.rsff.nand_r update
1.000002000 o.rsff.R H
1.000002000 o.rsff.nand_r.inp_a H
1.000002000 o.rsff.nand_r update
1.000003000 o.rsff.S L
1.000003000 o.rsff.nand_s.inp_b L
1.000003000 o.rsff.nand_s update
1.000003010 o.rsff.nand_s.out H
1.000003010 o.rsff.jt_s.pin0 H
1.000003010 o.rsff.jt_s.pin1 H
1.000003010 o.rsff.nQ H
1.000003010 o.rsff.jt_s.pin2 H
1.000003010 o.rsff.nand_r.inp_b H
1.000003010 o.rsff.nand_r update
1.000003020 o.rsff.nand_r.out L
1.000003020 o.rsff.jt_r.pin0 L
1.000003020 o.rsff.jt_r.pin1 L
1.000003020 o.rsff.Q L
1.000003020 o.rsff.jt_r.pin2 L
1.000003020 o.rsff.nand_s.inp_a L
1.000003020 o.rsff.nand_s update
1.000004000 o.rsff.S H
1.000004000 o.rsff.nand_s.inp_b H
1.000004000 o.rsff.nand_s update
5.000000000 quit
$ 

# 以前の記録の out_rsff.txt と比べてみます

$ ./nand.py -v < cmd_rsff.txt | diff -u -Lout_rsff.txt -L- out_rsff.txt -
--- out_rsff.txt
+++ -
@@ -7,31 +7,27 @@
 0.000000000 cmd_exec "sched.enque( (5,0), sched.quit )"
 0.000000000 o.rsff.R H
 0.000000000 o.rsff.nand_r.inp_a H
+0.000000000 o.rsff.nand_r.out L
+0.000000000 o.rsff.jt_r.pin0 L
 0.000000000 o.rsff.S L
 0.000000000 o.rsff.nand_s.inp_b L
+0.000000000 o.rsff.nand_s.out H
+0.000000000 o.rsff.jt_s.pin0 H
 0.000000000 o.rsff.nand_r update
+0.000000000 o.rsff.jt_r.pin1 L
+0.000000000 o.rsff.Q L
+0.000000000 o.rsff.jt_r.pin2 L
+0.000000000 o.rsff.nand_s.inp_a L
 0.000000000 o.rsff.nand_s update
+0.000000000 o.rsff.jt_s.pin1 H
+0.000000000 o.rsff.nQ H
+0.000000000 o.rsff.jt_s.pin2 H
+0.000000000 o.rsff.nand_r.inp_b H
+0.000000000 o.rsff.nand_r update
 0.000000010 o.rsff.nand_r.out H
 0.000000010 o.rsff.jt_r.pin0 H
-0.000000010 o.rsff.nand_s.out H
-0.000000010 o.rsff.jt_s.pin0 H
-0.000000010 o.rsff.jt_r.pin1 H
-0.000000010 o.rsff.Q H
-0.000000010 o.rsff.jt_r.pin2 H
-0.000000010 o.rsff.nand_s.inp_a H
-0.000000010 o.rsff.jt_s.pin1 H
-0.000000010 o.rsff.nQ H
-0.000000010 o.rsff.jt_s.pin2 H
-0.000000010 o.rsff.nand_r.inp_b H
-0.000000010 o.rsff.nand_s update
-0.000000010 o.rsff.nand_r update
-0.000000020 o.rsff.nand_r.out L
-0.000000020 o.rsff.jt_r.pin0 L
-0.000000020 o.rsff.jt_r.pin1 L
-0.000000020 o.rsff.Q L
-0.000000020 o.rsff.jt_r.pin2 L
-0.000000020 o.rsff.nand_s.inp_a L
-0.000000020 o.rsff.nand_s update
+0.000000010 o.rsff.nand_r.out L
+0.000000010 o.rsff.jt_r.pin0 L
 1.000000000 o.rsff.S H
 1.000000000 o.rsff.nand_s.inp_b H
 1.000000000 o.rsff.nand_s update
$ 

# 時刻 0 の追加分は、正に初期化の追加分の影響でしょうか?
# 時刻 10 nsec, 20 nsec の変化が削減されて、、、
# 時刻 1 sec 以降の動作は従来のものと一致
# よしよし


# 以前のようにgrepして出力だけにしてみると

$ ./nand.py -v < cmd_rsff.txt | grep Q
0.000000000 o.rsff.Q L
0.000000000 o.rsff.nQ H
1.000001010 o.rsff.Q H
1.000001020 o.rsff.nQ L
1.000003010 o.rsff.nQ H
1.000003020 o.rsff.Q L
$ 


# 以前の結果では

$ grep Q out_rsff.txt
0.000000010 o.rsff.Q H
0.000000010 o.rsff.nQ H
0.000000020 o.rsff.Q L
1.000001010 o.rsff.Q H
1.000001020 o.rsff.nQ L
1.000003010 o.rsff.nQ H
1.000003020 o.rsff.Q L
$ 

# 「起動直後、バタっとしてますが、まぁ、OKでしょう」
# としてた「バタっ」が消えてますね
# めでたしめでたし


Lチカ小休止

JKFFの次は常識的に考えてT-FFとかD-FFになりそうですが、 ちょっと整理した後、ランプクラスを改造して遊んでみます。

引数の渡し方をやや改良

まずコマンドファイルで、何回も書かされるやつをば。

メソッド登録の引数のタプル形式が少々面倒です。

$ head cmd_jkff.txt 
JKFF(o)
Lamp(o)
o.jkff.Q.conn = o.lamp.inp

# J=L, K=L
sched.enque( (1, 100000000), o.jkff.J.set, ('L',) )
sched.enque( (1, 110000000), o.jkff.K.set, ('L',) )
sched.enque( (1, 120000000), o.jkff.CLK.set, ('H',) )
sched.enque( (1, 130000000), o.jkff.CLK.set, ('L',) )
sched.enque( (1, 140000000), o.jkff.CLK.set, ('H',) )
$ 

こういう ('L',) とか ('H',) のやつですね。

Pythonの可変長引数の形式で、すっきりさせてみます。

# 例えば

$ python
 :
>>> def foo(a=()):
...   print a
... 
>>> def bar(*a):
...   print a
... 
>>> foo()
()
>>> bar()
()
>>> foo( (1,2) )
(1, 2)
>>> bar( 1, 2 )
(1, 2)
>>> bar( (1, 2) )
((1, 2),)
>>> 

この例のbar()の方の形式で、メソッドに渡したい引数を受け取るようにしておけば、 呼び出し側で、何回も丸カッコを書かずに済みます。

逆に、これまでのようにtupleにして bar( (1, 2) ) として呼び出すと、 (1, 2)tuple が、要素1のtupleの先頭の要素として渡ります。

従来の呼び出し方法も可能にして、互換性を持たせたいので、 引数の個数が1で、その中身の型がtupleなら従来の方式とみなして扱うようにします。

この弊害で、引数が1つで、tupleを渡される事を期待してる関数は、 登録出来なくなってしまいますが、まぁ「あきらめ」が肝心ですな...

nand.py-14-diff.txt

$ cat nand.py-14-diff.txt
--- nand.py-
+++ nand.py
@@ -20,8 +20,8 @@
 		pa = self.parent.get_name() + '.' if self.parent else ''
 		return pa + me
 
-	def enque(self, f, args=() ):
-		sched.enque(self.latency, f, args)
+	def enque(self, f, *args):
+		sched.enque(self.latency, f, *args)
 
 class Pin(Obj):
 	def __init__(self, parent, name, v=None):

# Objクラスの enque() メソッドの引数 args
# 可変長引数の形式に


@@ -315,7 +315,9 @@
 		self.que = []
 		self.now = tm_tup(0)
 	
-	def enque(self, latency, f, args=() ):
+	def enque(self, latency, f, *args):
+		if len(args) == 1 and type( args[0] ) == tuple:
+			args = args[0]
 		tm = self.now
 		if latency:
 			tm = tm_add( tm, tm_tup(latency) )
$ 

# Schedクラスの enque() メソッドの引数 args
# 可変長引数の形式に

それでは実行してみます。


# まずは従来のコマンドで同じように動くか?

$ ./nand.py < cmd_jkff.txt
0.000000000 o.lamp L
1.220000040 o.lamp H
1.420000050 o.lamp L
1.520000040 o.lamp H
1.540000050 o.lamp L
$ 

# とくに、おかわり無く


# では、('L',) とかを 'L' に変更したコマンドファイル
# cmd_jkff2.txt を生成してみます

$ cat cmd_jkff.txt | sed -e s/\(\'/\'/ -e s/,\)// | tee cmd_jkff2.txt
JKFF(o)
Lamp(o)
o.jkff.Q.conn = o.lamp.inp

# J=L, K=L
sched.enque( (1, 100000000), o.jkff.J.set, 'L' )
sched.enque( (1, 110000000), o.jkff.K.set, 'L' )
sched.enque( (1, 120000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 130000000), o.jkff.CLK.set, 'L' )
sched.enque( (1, 140000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 150000000), o.jkff.CLK.set, 'L' )

# J=H, K=L
sched.enque( (1, 200000000), o.jkff.J.set, 'H' )
sched.enque( (1, 220000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 230000000), o.jkff.CLK.set, 'L' )
sched.enque( (1, 240000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 250000000), o.jkff.CLK.set, 'L' )

# J=L, K=L
sched.enque( (1, 300000000), o.jkff.J.set, 'L' )
sched.enque( (1, 320000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 330000000), o.jkff.CLK.set, 'L' )
sched.enque( (1, 340000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 350000000), o.jkff.CLK.set, 'L' )

# J=L, K=H
sched.enque( (1, 400000000), o.jkff.K.set, 'H' )
sched.enque( (1, 420000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 430000000), o.jkff.CLK.set, 'L' )
sched.enque( (1, 440000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 450000000), o.jkff.CLK.set, 'L' )

# J=H, K=H
sched.enque( (1, 500000000), o.jkff.J.set, 'H' )
sched.enque( (1, 520000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 530000000), o.jkff.CLK.set, 'L' )
sched.enque( (1, 540000000), o.jkff.CLK.set, 'H' )
sched.enque( (1, 550000000), o.jkff.CLK.set, 'L' )

sched.enque( (5, 0), sched.quit )
$ 


# そして、これを実行

$ ./nand.py < cmd_jkff2.txt 
0.000000000 o.lamp L
1.220000040 o.lamp H
1.420000050 o.lamp L
1.520000040 o.lamp H
1.540000050 o.lamp L
$ 

# 同様に動いとります


JKFFでLチカしてみる

ランプクラスを改造して、もうちょっと「それらしく」Lチカさせてみます。

nand.py-15-diff.txt

$ cat nand.py-15-diff.txt
--- nand.py-
+++ nand.py
@@ -5,10 +5,11 @@
 import select
 
 class Obj:
-	def __init__(self, parent, name, latency=None):
+	def __init__(self, parent, name, latency=None, pos=(0,0)):
 		self.parent = parent
 		self.name = name
 		self.latency = latency
+		self.pos = pos
 		if parent:
 			setattr(parent, name, self)
 
# Objクラスに位置情報を追加です
# 指定が無ければ座標(0,0)がデフォルト値です


@@ -23,6 +24,11 @@
 	def enque(self, f, *args):
 		sched.enque(self.latency, f, *args)
 
+	def get_pos(self):
+		(px, py) = self.parent.get_pos() if self.parent else (0,0)
+		(x, y) = self.pos
+		return (px + x, py + y)
+
 class Pin(Obj):
 	def __init__(self, parent, name, v=None):
 		Obj.__init__(self, parent, name)

# Objクラスに位置情報を取得する get_pos() メソッドを追加です
# このメソッドは、絶対座標(グローバル座標)を返します
# 自分が保持してる位置情報は、親の上に乗っかっての相対座標です


@@ -302,13 +308,26 @@
 		self.not_.out.conn = Pin(self, 'out')
 
 class Lamp(Obj):
-	def __init__(self, parent, name='lamp', latency=None):
-		Obj.__init__(self, parent, name, latency)
+	def __init__(self, parent, name='lamp', latency=None, pos=(0,0)):
+		Obj.__init__(self, parent, name, latency, pos)
 		Pin(self, 'inp')
 
# ランプクラスでは、位置指定追加してObjに保持させておいて


 	def update(self):
-		msg = str_v( self.inp.v )
+		v = self.inp.v
+		msg = str_v(v)
 		self.enque( self.dbgout, (msg, True) )
+		self.enque( self.show, v )

# update()メソッドでスケジューラへの登録を追加
# 追加した show メソッドの呼び出しを登録してます
# メソッドへの引数は入力端子の値を渡します

+
+	def show(self, v):
+		(x, y) = self.get_pos()
+		lst = [
+			'ESC[{};{}H'.format(y+1, x+1), # move
+			'ESC[{}m'.format(7) if v else '', # reverse on if v
+			self.name,
+			'ESC[{}m'.format(27) if v else '', # reverse off if v
+		]
+		print ''.join(lst)
+		sys.stdout.flush()
 
 class Sched:
 	def __init__(self):
$ 

# 追加した show() メソッドです
# 自分の位置の絶対座標(x, y)を取得して
# エスケープシーケンスでカーソル移動
# 引数の入力端子の値がTrueならエスケープシーケンスでリバース表示装飾モードに入れて
# 自分のランプの名前を表示
# リバース表示モードにしていたなら元のモードに戻しておきます

実はこのエスケープシーケンスは、世をしのぶ仮の姿です。

ESCの文字列はただの’E','S','C'の3文字に過ぎません。

従来のランプの表示と一緒にエスケープシーケスが出力されると混乱するので、 あえて爪を隠してます。

では今回のLチカのコマンドをば。

cmd_lamp.txt

$ cat cmd_lamp.txt
Obj(o, 'b', None, (4,4))

JKFF(o.b)

CLK(o.b, 0.2, 'clk_J').out.conn = Joint(o.b, 'jt_J').new_pin()
o.b.jt_J.new_pin().conn = Lamp(o.b, 'J', None, (0,0)).inp
o.b.jt_J.new_pin().conn = o.b.jkff.J

CLK(o.b, 1, 'clk').out.conn = Joint(o.b, 'jt_CLK').new_pin()
o.b.jt_CLK.new_pin().conn = Lamp(o.b, 'CLK', None, (0,2)).inp
o.b.jt_CLK.new_pin().conn = o.b.jkff.CLK

CLK(o.b, 0.3, 'clk_K').out.conn = Joint(o.b, 'jt_K').new_pin()
o.b.jt_K.new_pin().conn = Lamp(o.b, 'K', None, (0,4)).inp
o.b.jt_K.new_pin().conn = o.b.jkff.K

o.b.jkff.Q.conn = Lamp(o.b, 'Q', None, (6, 0)).inp
o.b.jkff.nQ.conn = Lamp(o.b, 'nQ', None, (6, 4)).inp

sched.enque( (1,0), o.b.clk.en.set, 'H' )
sched.enque( (1,0), o.b.clk_J.en.set, 'H' )
sched.enque( (1,0), o.b.clk_K.en.set, 'H' )

sched.enque( (60,0), sched.quit )

# EOF
$ 


# 'b'と言うボードを(4,4)の位置に用意して
# ボードbの上でJFKKを生成

# Jの端子側に
# 0.2Hzのクロックを生成して、ジョイントにつなぎ
# ボードb上の(0,0)に'J'ランプを生成して、ジョイントにつなぎ
# JKFFのJ端子をジョイントに

# CLKの端子側も同様に
# 1Hzのクロックを生成して、ジョイントにつなぎ
# ボードb上の(0,2)に'CLK'ランプを生成して、ジョイントにつなぎ
# JKFFのCLK端子をジョイントに

# Kの端子側も同様に
# 0.3Hzのクロックを生成して、ジョイントにつなぎ
# ボードb上の(0,4)に'K'ランプを生成して、ジョイントにつなぎ
# JKFFのK端子をジョイントに

# 続いて出力側
# ボードb上の(6,0)に'Q'ランプを生成して、JKFFのQ端子につなぎ
# ボードb上の(6,4)に'nQ'ランプを生成して、JKFFのnQ端子につなぎ

# 時刻1秒のときに、各3つのクロックのen端子を'H'にセットして
# クロック出力を開始させます

# 時刻1分で終了です

それでは「とりあえず」実行してみます。

$ ./nand.py < cmd_lamp.txt
0.000000000 o.b.Q L
ESC[5;11HQ
0.000000000 o.b.nQ H
ESC[9;11HESC[7mnQESC[27m
0.500000010 o.b.CLK L
ESC[7;5HCLK
1.500000010 o.b.CLK H
ESC[7;5HESC[7mCLKESC[27m
1.666666676 o.b.K L
ESC[9;5HK
2.000000010 o.b.CLK L
ESC[7;5HCLK
2.500000010 o.b.J L
ESC[5;5HJ

  :

59.500000010 o.b.CLK H
ESC[7;5HESC[7mCLKESC[27m
59.999999986 o.b.K H
ESC[9;5HESC[7mKESC[27m
$ 

何やら呪文が表示されて、1分が経過して無事終了。

それでは、もう一つ別の端末を開いて試してみます。

# 別の端末から

$ mkfifo /tmp/aa
$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末に戻って

$ ./nand.py < cmd_lamp.txt | tee /tmp/aa | sed -e '/ESC/d'
0.000000000 o.b.Q L
0.000000000 o.b.nQ H
0.500000010 o.b.CLK L
1.500000010 o.b.CLK H
1.666666676 o.b.K L
2.000000010 o.b.CLK L
2.500000010 o.b.J L
2.500000010 o.b.CLK H
3.000000010 o.b.CLK L
3.333333342 o.b.K H
3.500000010 o.b.CLK H
4.000000010 o.b.CLK L
4.500000010 o.b.CLK H
  :

# そして別の端末では5つのランプがチカチカし始めます。

動画だとクロックの立ち上がりで出力側が変化する様子が、見て取れます。


せっかくなので3つ並べてみました

位置情報を親からの相対座標で保持してるので、 簡単に同じものをズラして配置できます。

せっかくなので3つ並べてみました。

# 別の端末側は、以前と同じ

$ mkfifo /tmp/aa
$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます


# 元の端末に戻って、今回は

$ ( cat cmd_lamp.txt ; \
echo "o.b.name = 'a'" ; \
sed -e 's/4,4/24,4/' -e 's/, 1,/, 3,/' cmd_lamp.txt ; \
echo "o.b.name = 'c'" ; \
sed -e 's/4,4/44,4/' -e 's/, 1,/, 7,/' cmd_lamp.txt ; \
) | tee cmd_lamp3.txt | ./nand.py | tee /tmp/aa | sed -e '/ESC/d'


# 複数行の入力ですが、行末 '\' なので1行入力の扱いです

# コマンドは、まず従来の cmd_lamp.txt を出力してから
# 「oの上に作ったボードb」 の名前をこっそり 'a' に変更します

# 続いて同じ cmd_lamp.txt を出力しますが
# sed でボードの配置座標(4,4) を (24,4) に置換
# さらに CLKの周波数 1 Hz を 3 Hz に置換

# この2回目のボードb の名前もまた、こっそりと'c'に変更しておいて

# 続いて3回目、同じ cmd_lamp.txt を出力
# sed でボードの配置座標(4,4) を (44,4) に置換
# さらに CLKの周波数 1 Hz を 7 Hz に置換

# 2回目以降の Obj(o, 'b', ... ) の生成時では
# 既存の o.b を上書きして新しいObjをポイントするようになりますが
# 古い方の o.b はスケジューラへのキューイングは終了してるので
# 特に問題ありません

# ランプの「エスケープシーケンスを使ってない方」の出力で
# 全部 o.b.xxx と表示されると、ややこしいので
# こっそりと名前を変更してます

# 都合3回
# sched.enque( (60,0), sched.quit )
# をスケジューラに登録しようとしますが
# 同じ時刻の同じメソッド呼び出しになるので
# 2つ目以降は、はじかれて登録されません

# このコマンド出力を
# tee cmd_lamp3.txt で「一応」ファイルにも保存

# そしてコマンドを
# ./nand.py に喰わせます

# nand.py のシミュレーションの出力を
# tee /tmp/aa で FIFO 側にも振りつつ

# sed -e '/ESC/d' で
# エスケープシーケンスの呪文の行は削除して
# 0.000000000 o.b.Q L
# 0.000000000 o.b.nQ H
# 0.500000010 o.b.CLK L
#  :
# 的な出力だけ表示します

# 別端末側では
# $ sed -n -e 's/ESC/^[/gp' /tmp/aa
# で待ち受けてるので

# FIFOからの出力を
# sed で 文字列'ESC'を
# 制御文字のエスケープ(0x1B) に置換した行だけを表示してます


T-FF と D-FF

ほいじゃあ T-FF と D-FF です。

と言っても実体は JKFF です。

T-FF は JKFF の入力 J と K を 'H' に。

D-FF は JKFF の J への入力を反転して K に入れます。

あと、クロック用のCLKクラスの実装をもうちょっと「まし」にしたのと、 確認用としてCLKクラスとJointクラスに、オプションでランプ点灯を追加してみました。

CLKクラスとJointクラスは互換性を持たせたつもりなので、 従来のコマンドを与えて実行しても、たぶん大丈夫なはずです。

nand.py-16-diff.txt

$ cat nand.py-16-diff.txt
--- nand.py-
+++ nand.py
@@ -64,9 +64,11 @@
 		self.enque( self.out.set, (v,) )
 
 class Joint(Obj):
-	def __init__(self, parent, name='jt', latency=None):
-		Obj.__init__(self, parent, name, latency)
+	def __init__(self, parent, name='jt', latency=None, pos=(0,0), lamp_name=None):
+		Obj.__init__(self, parent, name, latency, pos)
 		self.pins = []
+		if lamp_name:
+			self.new_pin().conn = Lamp(self, lamp_name).inp
 
 	def new_pin(self):
 		i = len(self.pins)

# lamp_name を与えたときは、ランプを取り付けます
# Jointとして指定された位置で点灯します


@@ -293,19 +295,46 @@
 		self.jt_k_o.new_pin().conn = self.nand_j.inp_a
 		self.jt_k_o.new_pin().conn = Pin(self, 'nQ')
 
-class CLK(Obj):
-	def __init__(self, parent, hz=1.0, name='clk'):
+class TFF(Obj):
+	def __init__(self, parent, name='tff', latency=10):
 		Obj.__init__(self, parent, name)
+		JKFF(self, 'jkff', latency)
+		Pin(self, 'CLK', 'L').conn = self.jkff.CLK
+		self.jkff.J.enque_set('H')
+		self.jkff.K.enque_set('H')
+		self.jkff.Q.conn = Pin(self, 'Q')
+		self.jkff.nQ.conn = Pin(self, 'nQ')
+

# ちょっと CLK の差分がオーバーハングしてますが
# T-FF の追加です
# まんま JKFF で、J, K を 'H'にしてるだけです


+class DFF(Obj):
+	def __init__(self, parent, name='dff', latency=10):
+		Obj.__init__(self, parent, name)
+		JKFF(self, 'jkff', latency)
+		Pin(self, 'CLK', 'L').conn = self.jkff.CLK
+		Pin(self, 'D', 'L').conn = Joint(self, 'jt').new_pin()
+		self.jt.new_pin().conn = self.jkff.J
+		self.jt.new_pin().conn = NOT(self, 'not_', latency).inp
+		self.not_.out.conn = self.jkff.K
+		self.jkff.Q.conn = Pin(self, 'Q')
+		self.jkff.nQ.conn = Pin(self, 'nQ')
+

# D-FF の追加です
# これまたまんま JKFF で、
# D-FF としての入力端子D を JKFF の J に
# D を NOT で反転させたものを K に入れてるだけです


+class CLK(Obj):
+	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
+		Obj.__init__(self, parent, name, None, pos)
 		latency = tm_from_fsec( 1.0 / ( 2 * hz ) )
 		NAND(self, 'nand', latency)
 		Joint(self)
-		NOT(self)
+		AND(self)
 
-		Pin(self, 'en', 'L').conn = self.nand.inp_a
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+		self.jt_en.new_pin().conn = self.nand.inp_a
 		self.nand.out.conn = self.jt.new_pin()
 		self.jt.new_pin().conn = self.nand.inp_b
-		self.jt.new_pin().conn = self.not_.inp
-		self.not_.out.conn = Pin(self, 'out')
+		self.jt.new_pin().conn = self.and_.inp_b
+		self.jt_en.new_pin().conn = self.and_.inp_a
+		self.and_.out.conn = Joint(self, 'jt_out').new_pin()
+		self.jt_out.new_pin().conn = Pin(self, 'out')
+		if lamp_name:
+			self.jt_out.new_pin().conn = Lamp(self, lamp_name).inp
 
 class Lamp(Obj):
 	def __init__(self, parent, name='lamp', latency=None, pos=(0,0)):


# CLKの改造です

#       .---------------.
#       |    +---+      |            [ Lamp ]
#       .----|    \     |    +---+     |
#            |     |O---@----|    \    |
# en ---@----|    /          |     |---@--- out
#       |    +---+      .----|    /
#       |               |    +---+
#       .---------------.

# こういう接続にしてみました

# 初期状態から en を 'H' に上げると
# AND側のlatencyの遅延だけで、out が 'H' に上がり
# スタートするはずです

# en を 'L' に落すと
# AND側のlatencyの遅延だけで、out が 'L' に落ちます

続いて確認用のお試しコマンド。

cmd_tff_dff.txt

$ cat cmd_tff_dff.txt
o.pos = (4,4)

CLK(o, 0.3, 'clk_03', (0,0), 'CLK03').out.conn = CLK(o, 8.9, 'clk_89', (10,0), 'CLK8').en
o.clk_89.out.conn = TFF(o).CLK
o.tff.Q.conn = Joint(o, 'jt_t', None, (20,0), 'Q_T').new_pin()
o.jt_t.new_pin().conn = DFF(o).D
CLK(o, 2.3, 'clk_23', (20, 2), 'CLK_D').out.conn = o.dff.CLK
o.dff.Q.conn = Lamp(o, 'Q_D', None, (30,0)).inp

sched.enque( (1,0), o.clk_03.en.set, 'H' )
sched.enque( (1,0), o.clk_89.en.set, 'H' )
sched.enque( (1,0), o.clk_23.en.set, 'H' )
sched.enque( (20,0), sched.quit )

# EOF
$ 

# ちょいと Obj o の位置を (4,4)にズラしておいて

# 0.3 Hzのクロック出力を
# 8.9 Hzのクロックのen端子に入れます

# 8.9 Hzのクロック出力を
# TFFのCLK端子に入れます

# TFFの出力Q をジョイント(ランプQ_T)に入れつつ
# DFFのD端子につなぎます

# これでD端子への入力は、
# バタバタしては'H'か'L'の状態で固まり、
# バタバタしては'H'か'L'の状態で固まり、、、
# と繰り返してくれる「ハズ」

# それとは無関係なタイミングで
# 2.3 Hzのクロックを DFFのCLK端子に入れておきます

# あとはDFFの出力QにランプQ_Dをつないで
# DFFへのCLKの立上りで、ちゃんと Q_D が変化するか
# 確認します

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_tff_dff.txt | tee /tmp/aa |sed -e '/ESC/d'
0.000000000 o.Q_D L
0.000000000 o.jt_t.Q_T L
0.000000020 o.clk_89.CLK8 L
0.000000020 o.clk_03.CLK03 L
0.000000020 o.clk_23.CLK_D L
1.000000020 o.clk_89.CLK8 H
1.000000020 o.clk_23.CLK_D H
1.000000060 o.jt_t.Q_T H
1.000000100 o.Q_D H
1.056179795 o.clk_89.CLK8 L
1.112359570 o.clk_89.CLK8 H
1.112359620 o.jt_t.Q_T L
1.168539345 o.clk_89.CLK8 L
1.217391324 o.clk_23.CLK_D L
1.224719120 o.clk_89.CLK8 H
1.224719160 o.jt_t.Q_T H
1.280898895 o.clk_89.CLK8 L
1.337078670 o.clk_89.CLK8 H
  :


# 別の端末側で表示を確認


カウンタとデコーダ

T-FF が手に入ったので、早速バイナリ・カウンタなど。

カウンタとなるとセットでデコーダも。

なんだが、雑誌「初歩のラジオ」を見て 日本橋の共立でICやLEDを買ってきて、 ハンダ付けしてた頃が思い出されて、懐かしいです。

複数のビットの端子をまとめて扱うのに便利なように、 いくつかクラスやメソッドを追加してます。

nand.py-17-diff.txt

$ cat nand.py-17-diff.txt
--- nand.py-
+++ nand.py
@@ -85,6 +85,28 @@
 			if pin.conn:
 				pin.set(v)
 
+class Joint_N(Obj):
+	def __init__(self, parent, n, name='jt_n', latency=None, pos=(0,0), slide=('y',1), lamp_name=None):
+		Obj.__init__(self, parent, name, latency, pos)
+		self.n = n
+		pos = (0, 0)
+		for i in range(n):
+			lamp_nm = name_i(lamp_name, i) if lamp_name else None
+			Joint( self, name_i('jt', i), latency, pos, lamp_nm )
+			pos = step_pos_slide(pos, slide)
+
+	def new_pin_conn(self, targ, targ_pin_name, n=-1, direc='to_targ'):
+		if n < 0:
+			n = self.n
+		for i in range(n):
+			targ_pin = getattr( targ, name_i(targ_pin_name, i), None )
+			jt = getattr( self, name_i('jt', i), None )
+			if targ_pin and jt:
+				if direc == 'to_targ':
+					jt.new_pin().conn = targ_pin
+				else:
+					targ_pin.conn = jt.new_pin()
+
 class NOT(Obj):
 	def __init__(self, parent, name='not_', latency=10):
 		Obj.__init__(self, parent, name)

# N個分のJointをまとめて生成して保持するクラスです
# new_pin_conn() メソッドでは
# 保持してるJoint群の new_pin() を呼び出して端子を追加し、
# ターゲットの部品上の端子群に、まとめてつなぎます


@@ -317,6 +339,55 @@
 		self.jkff.Q.conn = Pin(self, 'Q')
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 
+class COUNTER(Obj):
+	def __init__(self, parent, bit_n, name='counter', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = bit_n
+		clk = Pin(self, 'CLK', 'L')
+		for i in range(bit_n):
+			tff = TFF( self, name_i('tff', i), latency )
+			clk.conn = tff.CLK
+			tff.Q.conn = Pin( self, name_i('out', i), 'L' )
+			clk = tff.nQ
+			

# バイナリ・カウンタのクラスです
# bit_n でビット数を指定します
# ビットの数だけ TFF を生成して
# ビット出力の反転(nQ)を、次の桁のCLKに入れてます

# CLK端子の立上りで出力が反転するので
# ビット出力の立下がりで、次の桁のビットが反転します


+class DECODER(Obj):
+	def __init__(self, parent, bit_n, name='decoder', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = bit_n
+		if bit_n == 1:
+			Pin(self, 'inp_0', 'L').conn = Joint(self, 'jt_inp').new_pin()
+			self.jt_inp.new_pin().conn = NOT(self, 'not_', latency).inp
+			self.not_.out.conn = AND(self, 'and_0', latency).inp_a
+			self.jt_inp.new_pin().conn = AND(self, 'and_1', latency).inp_a
+
+			Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+			self.jt_en.new_pin().conn = self.and_0.inp_b
+			self.jt_en.new_pin().conn = self.and_1.inp_b
+
+			self.and_0.out.conn = Pin(self, 'out_0')
+			self.and_1.out.conn = Pin(self, 'out_1')
+		else:
+			for i in range(bit_n):
+				Pin( self, name_i('inp', i), 'L' )
+			bn1 = bit_n - 1
+			DECODER(self, bn1, 'dec_u', latency)
+			DECODER(self, bn1, 'dec_d', latency)
+			Joint_N(self, bn1, 'jt_n')
+			self.jt_n.new_pin_conn(self, 'inp', bn1, direc='from_targ')
+			self.jt_n.new_pin_conn(self.dec_u, 'inp')
+			self.jt_n.new_pin_conn(self.dec_d, 'inp')
+
+			DECODER(self, 1, 'dec_1', latency)
+			msb = getattr( self, name_i('inp', bn1) )
+			msb.conn = self.dec_1.inp_0
+			Pin(self, 'en', 'L').conn = self.dec_1.en
+			self.dec_1.out_0.conn = self.dec_d.en
+			self.dec_1.out_1.conn = self.dec_u.en
+
+			n1 = 1 << bn1
+			new_pin_conn(self, self.dec_d, 0, n1, 'out', direc='from_in_obj')
+			new_pin_conn(self, self.dec_u, n1, n1, 'out', direc='from_in_obj')
+
 class CLK(Obj):
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
 		Obj.__init__(self, parent, name, None, pos)

# カウンタからの入力をデコードするクラスです

# 効率は無視で、とりあえず動くものを用意してみました

# ビット数分の inp_0, inp_1, ... な入力端子があります
# 2の(ビット数)乗分の out_0, out_1, ... な出力端子があります

# en端子があり'L'のときは out_x 出力は全て'L'になります
# en 'H' のときに out_x のどれか1つが'H'になります

# 基本部分はビット数1のときの前半のコードです
# inp_0 が 'L' なら out_0 だけが 'H'
# inp_0 が 'H' なら out_1 だけが 'H'

# else 以降は、ビット数が2以上の場合
# 再帰でビット数-1 のデコーダ2つと、ビット数が 1 のデコーダを用意
# ビット数が1のデコーダは入力のMSBにつなぎ
# ビット数-1の2つのデコーダは入力の下位側ビットにつなぎます
# MSBのデコード結果の2ビットを
# ビット数-1の2つのデコーダのen端子に
# あとは、出力をゴニョゴニョと並べて、できあがり


@@ -358,6 +429,20 @@
 		print ''.join(lst)
 		sys.stdout.flush()
 
+class Lamp_N(Obj):
+	def __init__(self, parent, n, name='lamp_n', latency=None, pos=(0,0), slide=('y',1)):
+		Obj.__init__(self, parent, name, latency, pos)
+		self.n = n
+		pos = (0, 0)
+		for i in range(n):
+			lamp = Lamp( self, name_i(name, i), latency, pos )
+			pos = step_pos_slide(pos, slide)
+			Pin( self, name_i('inp', i), 'L' ).conn = lamp.inp
+
+	def conn_targ(self, targ, targ_pin_name, n=-1):
+		n = self.n if n < 0 else n
+		conn_n( n, targ, 0, targ_pin_name, self, 0, 'inp' )
+
 class Sched:
 	def __init__(self):
 		self.que = []

# ランプN個をまとめて生成して保持するクラスです
# conn_targ() メソッドで
# 指定の部品の指定の端子群に、つなぎます


@@ -414,13 +499,33 @@
 			dbgout( 'cmd_exec "{}"'.format(s) )
 			exec s
 
-def new_pin_conn(obj, in_obj, sta_i):
-	for i in range(in_obj.n):
-		pin = getattr( in_obj, name_inp(i) )
-		Pin( obj, name_inp( sta_i + i ) ).conn = pin
+def step_pos_slide( pos, slide=('y',1) ):
+	(x, y) = pos
+	(direc, step) = slide
+	x += step if direc != 'y' else 0
+	y += step if direc == 'y' else 0
+	return (x, y)
+
+def new_pin_conn(obj, in_obj, sta_i, n=-1, name='inp', direc='to_in_obj'):
+	if n < 0:
+		n = in_obj.n
+	for i in range(n):
+		Pin( obj, name_i( name, sta_i + i ) )
+	direc_rev = (direc != 'to_in_obj')
+	conn_n(n, obj, sta_i, name, in_obj, 0, name, direc_rev)
+

# なにやら差分が泣き分かれてますが
# new_pin_conn() 関数は、互換性を保ちつつ機能追加です

# n を明示的に指定できるようにしつつ
# 端子の名前も inp_x 以外も指定可能に
# 接続の方向も、逆向きにつなげれるようにしました

# step_pos_slider() 関数は、
# 座標をズラして更新していくとき用のユーティリティ関数です


+def conn_n(n, from_obj, from_sta_i, from_name, to_obj, to_sta_i, to_name, direc_rev=False):
+	for i in range(n):
+		from_pin = getattr( from_obj, name_i( from_name, from_sta_i + i ), None )
+		to_pin = getattr( to_obj, name_i( to_name, to_sta_i + i ), None )
+		if from_pin and to_pin:
+			if direc_rev:
+				to_pin.conn = from_pin
+			else:
+				from_pin.conn = to_pin
 
# n 個の接続をするメソッドが増えてきそうなので
# できるだけ、この conn_n() 関数を底として使うようにします

# from から to へと n組の端子をつなぎます
# direc_rev=True に指定すると、逆に to から from へとつなぎます


-def name_inp(i):
-	return 'inp_{}'.format(i)
+def name_i(name, i):
+	return '{}_{}'.format(name, i)
 
 def str_v(v):
 	return { True: 'H', False: 'L', None: 'Hi-Z' }.get(v, '?')
$ 

# name_inp(i) は廃止してname_i(name, i) を追加しました
# かつての name_inp(i) はname_i('inp', i) と同等です

続いて確認用のお試しコマンド。

cmd_cnt_dec.txt

$ cat cmd_cnt_dec.txt
o.pos = (4,4)

COUNTER(o, 4, 'cnt')
CLK(o, 4, 'clk', (0,0), 'CLK').out.conn = o.cnt.CLK
Joint_N(o, 4, 'jt_4', None, (10,0), ('y',1), 'B')
o.jt_4.new_pin_conn(o.cnt, 'out', direc='from_targ')

DECODER(o, 4, 'dec')
o.jt_4.new_pin_conn(o.dec, 'inp', direc='to_targ')
Lamp_N(o, 1<<4, 'O', None, (20,0)).conn_targ(o.dec, 'out')

sched.enque( (1,0), o.clk.en.set, 'H' )
sched.enque( (1,0), o.dec.en.set, 'H' )
sched.enque( (20,0), sched.quit )

# EOF
$ 

# 例によって Obj o の位置を、大人の都合でちょっとズラしておいて

# 4ビットのカウンタを生成
# 4 Hzのクロックの出力を、カウンタのCLK端子に
# 4ビットのジョイント組を用意して(ランプもB_xとして有効に)
# 4ビットのジョイント組にカウンタの出力をつないで

# 4ビットのデコーダを生成
# 4ビットのジョイント組を、デコーダの入力につないで
# 2の4乗で16個のランプ組を生成
# デコーダの16個の出力を、ランプ組につなぎます

# 時刻1秒のときに、クロックのen端子とデコーダのen端子を'H'にセット
# 時刻20秒で終了です

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_tff_dff.txt | tee /tmp/aa |sed -e '/ESC/d'
0.000000000 o.O.O_0 L
0.000000000 o.O.O_1 L
0.000000000 o.O.O_2 L
0.000000000 o.O.O_3 L
0.000000000 o.O.O_4 L
0.000000000 o.O.O_5 L
0.000000000 o.O.O_6 L
0.000000000 o.O.O_7 L
0.000000000 o.O.O_8 L
0.000000000 o.O.O_9 L
0.000000000 o.O.O_10 L
0.000000000 o.O.O_11 L
0.000000000 o.O.O_12 L
0.000000000 o.O.O_13 L
0.000000000 o.O.O_14 L
0.000000000 o.O.O_15 L
0.000000000 o.jt_4.jt_0.B_0 L
0.000000000 o.jt_4.jt_1.B_1 L
0.000000000 o.jt_4.jt_2.B_2 L
0.000000000 o.jt_4.jt_3.B_3 L
  :
2.250000100 o.O.O_4 H
2.250000100 o.jt_4.jt_1.B_1 H
2.250000140 o.O.O_6 H
2.250000150 o.O.O_4 L
2.375000020 o.clk.CLK L
2.500000020 o.clk.CLK H
2.500000060 o.jt_4.jt_0.B_0 H
2.500000080 o.O.O_7 H
2.500000090 o.O.O_6 L
2.625000020 o.clk.CLK L
2.750000020 o.clk.CLK H
  :


ROMもどき

デコーダを手に入れたので「ROMもどき」を作ってみます。

生成時に初期値を指定する事にして、 その値を内部に密かに保持する端子(Pinクラス)にそっと 'H' or 'L' を設定しておきます。

その端子のビットの組を、アドレスの組み合わせ分だけ保持しておいて、 アドレス入力端子の値をデコーダに入れて、記録してる値を出力させてみます。


まずはデータ・ビット数「1」から

まず、1ビットのROMを作ってみて、うまくいけば、 それを4個なり、8個なり並べてビット数を増やせばええでしょう。

例によって効率無視、簡潔さ重視でいってみます。

まずは、データ幅1ビットで、アドレス空間も1ビットの場合。

ぱっと思いつきで、次の構成にします。

                                    en
         +-------+                   |
    inp_0|       |en                 |
A_0------|Decoder|-------------------@
         | 1 bit |                   |
         +-------+                   |
      out_1|   |out_0                |
           |   |                     |
           |   |    +---+            |  +---+
           |   .----|    \    +---+  .--|    \
           |        | AND |---|    \    | AND |--- D_0
   [p0]----|--------|    /     ) OR >---|    /
           |        +---+   .-|    /    +---+
           |        +---+   | +---+
           .--------|    \  |
                    | AND |-.
   [p1]-------------|    /
                    +---+


# 内部に保持する2つの端子[p0],[p1]に
# アドレス0ときと、アドレス1のときの値を初期値から記録

# en端子'L'ならデータD_0は必ず'L'
# アドレス1ビットを1ビットのデコーダにかけて
# p0側、p1側のどちらを採用するか振り分けます

# 不採用側のANDの出力は'L'なので
# ORして集団のANDを経て出力

これがまともに動作するならば、、、

これを底に持つようにし、 「再帰な考え」でアドレスのビット幅を、 次の構成で増やせるはずです。 (ひつこいですが、効率は無視です)

                                    en
         +-------+                   |
    inp_0|       |en                 |
A_N-1 ---|Decoder|-------------------@
         | 1 bit |                   |
         +-------+                   |
      out_1|   |out_0                |
           |   |                     |
           |   | en +-----+          |  +---+
           |   .----|ROM  |   +---+  .--|    \
A_0..N-2   |        | N-1 |---|    \    | AND |--- D_0
  ====@@===|========| bit |    ) OR >---|    /
      ||   |        +-----+ .-|    /    +---+
      ||   |     en +-----+ | +---+
      ||   .--------|ROM  | |
      ||            | N-1 |-.
      ..============| bit |
                    +-----+

# 「アドレス空間Nビットの」ROMの構成です

# 出力側の構成はアドレス空間1ビットのときと同じです

# 先の図の 2つの AND の位置が
# 「アドレス空間N-1ビットの」ROM に置き換わってます

# アドレスの最上位ビット(MSB)を1ビットのデコーダに入れて
# 下位の0からN-2までのアドレスのビットは、
# 2つの「アドレス空間N-1ビットの」ROM につなぎます

# 再帰で底のアドレス空間1ビットに辿りつくと先述の構成になります

nand.py-18-diff.txt

$ cat nand.py-18-diff.txt
--- nand.py-
+++ nand.py
@@ -388,6 +388,53 @@
 			new_pin_conn(self, self.dec_d, 0, n1, 'out', direc='from_in_obj')
 			new_pin_conn(self, self.dec_u, n1, n1, 'out', direc='from_in_obj')
 
+class ROM_D1(Obj):
+	def __init__(self, parent, abit_n, dlst=(), name='rom_d1', latency=10):
+		Obj.__init__(self, parent, name)
+		self.dbgout(dlst)
+		self.n = abit_n
+		for i in range(abit_n):
+			Pin( self, name_i('A', i), 'L' )
+		n1 = abit_n - 1
+		msb = getattr( self, name_i('A', n1) )
+		msb.conn = DECODER(self, 1, 'deco', latency).inp_0
+
+		if abit_n == 1:
+			dlst_v = lambda i: dlst[i] if i < len(dlst) else -1
+			dlst_b = lambda i: bool( dlst_v(i) & 1 )
+			Pin(self, 'p0', dlst_b(0) ).conn = AND(self, 'and_d', latency).inp_a
+			Pin(self, 'p1', dlst_b(1) ).conn = AND(self, 'and_u', latency).inp_a
+		
+			en_d = self.and_d.inp_b
+			en_u = self.and_u.inp_b
+			out_d = self.and_d.out
+			out_u = self.and_u.out
+		else:
+			half = 1 << n1
+			ROM_D1(self, n1, dlst[ :half ], 'rom_d', latency)
+			ROM_D1(self, n1, dlst[ half: ], 'rom_u', latency)
+			Joint_N(self, n1, 'jt_n')
+			self.jt_n.new_pin_conn(self.rom_d, 'A')
+			self.jt_n.new_pin_conn(self.rom_u, 'A')
+			self.jt_n.new_pin_conn(self, 'A', n1, direc='from_targ')
+
+			en_d = self.rom_d.en
+			en_u = self.rom_u.en
+			out_d = self.rom_d.D_0
+			out_u = self.rom_u.D_0
+
+		self.deco.out_0.conn = en_d
+		self.deco.out_1.conn = en_u
+
+		OR(self, 'or_', latency).out.conn = AND(self, 'and_out', latency).inp_a
+		out_d.conn = self.or_.inp_a
+		out_u.conn = self.or_.inp_b
+
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt').new_pin()
+		self.jt.new_pin().conn = self.deco.en
+		self.jt.new_pin().conn = self.and_out.inp_b
+		self.and_out.out.conn = Pin(self, 'D_0', 'L')
+
 class CLK(Obj):
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
 		Obj.__init__(self, parent, name, None, pos)
$ 

# 先の構成をコーディングしてみると、このようになりました

# アドレスの最上位ビット(ビット幅1のときも含めて)を
# デコーダに入れる共通の処理があって

# abit_n == 1 のときは
# 内部端子に初期値を記録して、2つのANDに入れる処理

# else 側は abit_n > 1 のときで
# アドレス空間 abit_n - 1 ビットの ROM を2つ用意して
# 重ねて繋ぐ処理

# 末尾に OR して AND して出力に繋ぐ共通の処理

続いて確認用のお試しコマンド。

データ1ビットなので「モールス信号」にしてみました。

cmd_rom_d1.txt

$ cat cmd_rom_d1.txt
o.pos = (4,4)

CLK(o, 8, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 5, 'cnt').CLK
Joint_N(o, 5, 'jt_5', None, (10,0), ('y',1), 'B')
o.jt_5.new_pin_conn(o.cnt, 'out', direc='from_targ')

o.dlst = [ 1,1,1,0, 1,0, 1,1,1,0, 1,0, 0,0,0, 1,1,1,0, 1,1,1,0, 1,0, 1,1,1,0, 0,0,0 ]
ROM_D1(o, 5, o.dlst, 'rom_d1').D_0.conn = Lamp(o, 'OUT', None, (20,0)).inp
o.jt_5.new_pin_conn(o.rom_d1, 'A', direc='to_targ')

sched.enque( (0,500000000), o.rom_d1.en.set, 'H' )
sched.enque( (1,0), o.clk.en.set, 'H' )
sched.enque( (20,0), sched.quit )

# EOF
$ 


# 8Hzのクロックを5ビットカウンタにつないで
# 5ビットのカウンタ出力を'B_x'ランプつきのジョイントに

# モールス信号データを o.dlst にちょっとメモして
# アドレス空間5ビット、データ1ビットのROMを生成して
# 5ビットカウンタ出力を経由するジョイントを、
# 5ビット幅のROMのアドレスにつなぎます

# ROMの1ビットデータ出力にランプをつなぎます

# 時刻0.5秒のとき、ROMのen端子を'H'に
# 時刻1秒のときに、クロックのen端子を'H'に
# 時刻20秒で終了

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_rom_d1.txt | tee /tmp/aa |sed -e '/ESC/d'
  :

モールス信号「CQ CQ ... 」


そしてデータ・ビット数「n」に

1ビットが出来たら、それをn個並べてnビット。

nand.py-19-diff.txt

$ cat nand.py-19-diff.txt
--- nand.py-
+++ nand.py
@@ -435,6 +435,22 @@
 		self.jt.new_pin().conn = self.and_out.inp_b
 		self.and_out.out.conn = Pin(self, 'D_0', 'L')
 
+class ROM(Obj):
+	def __init__(self, parent, dbit_n, abit_n, dlst=(), name='rom', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = dbit_n
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+		for i in range(abit_n):
+			Pin( self, name_i('A', i), 'L' )
+		Joint_N(self, abit_n, 'jt_n_A').new_pin_conn(self, 'A', abit_n, 'from_targ')
+
+		for i in range(dbit_n):
+			dlst_i = [ (d >> i) & 1 for d in dlst ]
+			rom_b = ROM_D1(self, abit_n, dlst_i, name_i('rom_b', i), latency)
+			self.jt_en.new_pin().conn = rom_b.en
+			self.jt_n_A.new_pin_conn(rom_b, 'A', abit_n, 'to_targ')
+			rom_b.D_0.conn = Pin( self, name_i('D', i), 'L' )
+
 class CLK(Obj):
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
 		Obj.__init__(self, parent, name, None, pos)
$ 


# 初期値の dlst がなかなか強引ですが、まぁ効率無視ということで

続いて確認用のお試しコマンド。

cmd_rom.txt

$ cat cmd_rom.txt
o.pos = (4,4)

CLK(o, 3, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 4, 'cnt').CLK
Joint_N(o, 4, 'jt_4', None, (10,0), ('y',1), 'A')
o.jt_4.new_pin_conn(o.cnt, 'out', direc='from_targ')

o.dlst = [ 1, 2, 3, 0, 4, 2, 5, 0, 3, 1, 4, 2, 4, 2, 5, 0 ]
ROM(o, 3, 4, o.dlst, 'rom')
o.jt_4.new_pin_conn(o.rom, 'A', direc='to_targ')

Joint_N(o, 3, 'jt_3', None, (20,0), ('y',1), 'D')
o.jt_3.new_pin_conn(o.rom, 'D', direc='from_targ')

DECODER(o, 3, 'dec')
o.jt_3.new_pin_conn(o.dec, 'inp', direc='to_targ')
Lamp_N(o, (1<<3), 'OUT', None, (30,0)).conn_targ(o.dec, 'out')

sched.enque( (1,0), o.rom.en.set, 'H' )
sched.enque( (1,0), o.dec.en.set, 'H' )
sched.enque( (2,0), o.clk.en.set, 'H' )
sched.enque( (20,0), sched.quit )

# EOF
$ 


# 適当にデータ幅3ビット, アドレス空間4ビットで試してます
# 0から7の値を、16個保持します

# 初期値のサンプルは
# 古い昭和の芸人さんの手遊びのネタ
# 「いちにのさーんの、しのにのご、さんいち、しのにの、しのにの、ごー」

# ついて来れない人は、おいていきますよ
# 義務教育ちゃいますからね

# 1ビットの時と同様にクロックでカウンタを動かして
# ROMのアドレスに入れるところは同じ

# ROMの3ビット出力を、今回はデコーダに入れて
# デコーダ出力に 8 個のランプをつないでます

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_rom.txt | tee /tmp/aa |sed -e '/ESC/d'
  :


日の字の7セグ

いわゆる電卓でよくある

 ---
|   |
 ---
|   |
 ---

こういうやつです。

「ROMもどき」を作ったので、これを使ってデコーダにしてみます。

4ビットの値を、0から9に続きAからFまでの16進数として表示します。

まずランプクラスを改造して、機能を追加しておいて、 「7セグメントなランプ」のクラスを作ります。

次に、その「7セグメントなランプ」に16進数を表示するための、 デコーダをROMを使ってつくります。

nand.py-20-diff.txt

$ cat nand.py-20-diff.txt
--- nand.py-
+++ nand.py
@@ -471,8 +471,9 @@
 			self.jt_out.new_pin().conn = Lamp(self, lamp_name).inp
 
 class Lamp(Obj):
-	def __init__(self, parent, name='lamp', latency=None, pos=(0,0)):
+	def __init__(self, parent, name='lamp', latency=None, pos=(0,0), show_inf={}):
 		Obj.__init__(self, parent, name, latency, pos)
+		self.show_inf = show_inf
 		Pin(self, 'inp')
 
 	def update(self):
@@ -483,11 +484,19 @@
 
 	def show(self, v):
 		(x, y) = self.get_pos()
+		label = self.show_inf.get('label', self.name)
+		rev = self.show_inf.get('rev', True)
+
+		if not rev and not v:
+			label = ' ' * len(label)
+		if rev and not v:
+			rev = False
+
 		lst = [
 			'ESC[{};{}H'.format(y+1, x+1), # move
-			'ESC[{}m'.format(7) if v else '', # reverse on if v
-			self.name,
-			'ESC[{}m'.format(27) if v else '', # reverse off if v
+			'ESC[{}m'.format(7) if rev else '', # reverse on if v
+			label,
+			'ESC[{}m'.format(27) if rev else '', # reverse off if v
 		]
 		print ''.join(lst)
 		sys.stdout.flush()


# ランプクラスの機能追加です
# show()メソッドの処理について、コンストラクタの引数に show_inf={} を追加して
# 互換性を保ちつつ、機能を追加しました

# show_info で指定する辞書で
# 'label': xxx を指定すると、ランプ表示の文字列をnameで指定したものから変更できます
# 'rev': False を指定すると、ランプ点灯でリバース表示としてた動作をやめて
# 消灯のときに空白を表示するようになります



@@ -505,6 +514,45 @@
 	def conn_targ(self, targ, targ_pin_name, n=-1):
 		n = self.n if n < 0 else n
 		conn_n( n, targ, 0, targ_pin_name, self, 0, 'inp' )
+
+class Lamp_7seg(Obj):
+	def __init__(self, parent, name='lamp_7seg', latency=None, pos=(0,0)):
+		Obj.__init__(self, parent, name, None, pos)
+		lst = [
+			(1,0, '---'),
+			(0,1, '|'),
+			(4,1, '|'),
+			(1,2, '---'),
+			(0,3, '|'),
+			(4,3, '|'),
+			(1,4, '---'),
+		]
+		for (i, (x, y, label)) in enumerate(lst):
+			show_info = {'label': label, 'rev':False}
+			lamp = Lamp( self, name_i('lamp', i), latency, (x,y), show_info )
+			Pin( self, name_i('inp', i), 'L' ).conn = lamp.inp
+
+	def conn_targ(self, targ, targ_pin_name):
+		conn_n( 7, targ, 0, targ_pin_name, self, 0, 'inp' )
+

# 「日の字形の7セグメント」のランプクラスです
# 先ほどランプクラスに追加したshow()メソッドの機能を利用して
# リバース表示にせずに、指定した文字列で7つのランプを表示します

# 入力端子の番号と7つのランプの位置の関係は
# 上から順に割り振って、次の通りです

#  --- 0
# |1  |2
#  --- 3
# |4  |5
#  --- 6


+class DECODER_7seg(Obj):
+	def __init__(self, parent, name='dec_7seg', latency=10):
+		Obj.__init__(self, parent, name)
+		dlst = [
+			0x77, 0x24, 0x5d, 0x6d, 0x2e, 0x6b, 0x7b, 0x25,
+			0x7f, 0x6f, 0x3f, 0x7a, 0x53, 0x7c, 0x5b, 0x1b,
+		]
+		ROM(self, 7, 4, dlst, 'rom', latency)
+		Pin(self, 'en', 'L').conn = self.rom.en
+		for i in range(4):
+			Pin( self, name_i('inp', i), 'L' )
+		conn_n(4, self, 0, 'inp', self.rom, 0, 'A')
+		for i in range(7):
+			Pin( self, name_i('out', i), 'L' )
+		conn_n(7, self.rom, 0, 'D', self, 0, 'out')
+
+	def conn_targ(self, targ, targ_pin_name):
+		conn_n( 7, self, 0, 'out', targ, 0, targ_pin_name )
 
 class Sched:
 	def __init__(self):
$ 

# ROMを使った「日の字形の7セグメント」のランプ用のデコーダです
# ここの本質は dlst = [ ... ] のデータにあります
# あとはROMの端子を自分の端子に繋いでるだけです

続いて確認用のお試しコマンド。

cmd_7seg.txt

$ cat cmd_7seg.txt
o.pos = (4,4)

CLK(o, 2, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 4, 'cnt').CLK
Joint_N(o, 4, 'jt_4', None, (10,0), ('y',1), 'B')
o.jt_4.new_pin_conn(o.cnt, 'out', direc='from_targ')

DECODER_7seg(o, 'dec_7seg')
o.jt_4.new_pin_conn(o.dec_7seg, 'inp', direc='to_targ')

Lamp_7seg(o, 'lamp_7seg', None, (20,0))
o.dec_7seg.conn_targ( o.lamp_7seg, 'inp' )

sched.enque( (0,0), o.dec_7seg.en.set, 'H' )
sched.enque( (4,0), o.clk.en.set, 'H' )
sched.enque( (20,0), sched.quit )

# EOF
$ 


# 2 Hzのクロックを4ビットのカウンタに入れて
# カウンタの出力をランプつきのジョイントで表示しつつ

# お試しする一方の「主役」のデコーダにつなぎ

# デコーダの出力を
# もう一方の「主役」日の字形のランプにつないでます

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_7seg.txt | tee /tmp/aa |sed -e '/ESC/d'
  :

クロックが'H'表示のままですが、表示だけの問題の様子で、 後段の表示は変化してます。

おそらく、クロックが'H'になる立ち上がりで、全ての更新処理がなされるので、 処理が重すぎてクロック'L'の表示がかなり遅れてしまい、 すぐに次の'H'の表示のタイミングが来てしまう現象のようです。


実装の構成を少し修正

データ幅1ビットのROM_D1クラスの実装が「再帰すぎて」少々混乱気味です。 シンプルな構成に変更してみます。

まず、再帰の果ての奥の底に、状態を保持するPinを抱えこませてる実装をやめます。

複数のPinがあるだけのPinSetクラスを追加して、これを使うように変更します。

+----------+
|          |---- p_0
| PinSet   |---- p_1
|          |  :
|          |---- p_n-1
+----------+

次に、便利部品として2入力のANDを複数個並べて、 端子名を変えただけのANDSetクラスを追加。

     sel_0, sel_1, ..., sel_n-1
         ||
         ||
         ||   +---+
         . ---|    \
         ||   |     |--- out_0
inp_0 --------|    /
         ||   +---+
         ||   +---+
         . ---|    \
         ||   |     |--- out_1
inp_1 --------|    /
         ||   +---+

         :      :

         ||   +---+
         . ---|    \
              |     |--- out_n-1
inp_n-1 ------|    /
              +---+

     ANDSetクラスの構成

sel_x'L'ならout_xは'L'に固定。 sel_x が'H'になったものだけ、inp_xの値がout_x に反映されます。

次に、SELECTORクラスを追加します。

デコーダの出力out_xを、ANDSetクラスのsel_xにつないで、 ANDSetの出力out_xのORをとって出力outへ。

           +---------------------------+
en --------|en                         |
           |          DECODER (an bit) |
A_0 -------|inp_0                      |
A_1 -------|inp_1                      |
  :        |                           |
A_an-1 ----|inp_an-1                   |
           |           out_0..out_dn-1 |
           +---------------------------+
                            ||
                            ||
           +---------------------------+
           |           sel_0..sel_dn-1 |
           |                           |     +---+
D_0 -------|inp_0    ANDSet    out_0   |-----|    \
D_1 -------|inp_1    (dn)      out_1   |-----|     \
  :        |                           |  :  ) OR   >--- out
D_dn-1 ----|inp_dn-1           out_dn-1|-----|     /
           +---------------------------+     |    /
                                             +---+

           SELECTORクラスの構成

ROM_D1クラスは、PinSetとSELECTORで構成します。

                   +------------+
en ----------------|en          |
                   |    SELECTOR|
A_0 ---------------|A_0         |
A_1 ---------------|A_1         |
  :                | :          |
A_an-1 ------------|A_an-1      |
                   |         out|--- D_0
+--------------+   |            |
|           p_0|---|D_0         |
| PinSet    p_1|---|D_1         |
|              | : | :          |
|        p_dn-1|---|D_dn-1      |
+--------------+   +------------+

          ROM_D1クラスの構成

例によって互換性を保ちつつ、この方針で実装を変更してみます。

nand.py-21-diff.txt

$ cat nand.py-21-diff.txt
--- nand.py-
+++ nand.py
@@ -340,6 +340,7 @@
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 
 class COUNTER(Obj):
+	# CLK, out_x
 	def __init__(self, parent, bit_n, name='counter', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = bit_n
@@ -351,6 +352,7 @@
 			clk = tff.nQ
 			
 class DECODER(Obj):
+	# en, inp_x, out_x
 	def __init__(self, parent, bit_n, name='decoder', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = bit_n


# 主要な上位のクラスに、端子名のコメントを追加しました
# その場その場で、適当に命名してるもので...


@@ -388,60 +390,70 @@
 			new_pin_conn(self, self.dec_d, 0, n1, 'out', direc='from_in_obj')
 			new_pin_conn(self, self.dec_u, n1, n1, 'out', direc='from_in_obj')
 
+class PinSet(Obj):
+	# p_x
+	def __init__(self, parent, n, dlst=(), name='pin_set'):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		new_pin_n(n, self, 'p', 0, dlst)
+

# PinSetクラス追加です
# 今回、n個のPinを生成するだけの関数 new_pin_n() を追加したので
# これを呼び出すだけです


+class ANDSet(Obj):
+	# inp_x, sel_x, out_x
+	def __init__(self, parent, n, name='and_set', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		for i in range(n):
+			and_i = AND( self, name_i('and', i), latency )
+			Pin( self, name_i('inp', i) ).conn = and_i.inp_a
+			Pin( self, name_i('sel', i) ).conn = and_i.inp_b
+			and_i.out.conn = Pin( self, name_i('out', i) )
+

# ANDSetクラス追加です
# n個の「2入力のAND」を生成して
# 端子 inp_x, sel_x, out_x につなぐだけです


+class SELECTOR(Obj):
+	# en, A_x, D_x, out
+	def __init__(self, parent, bit_n, name='selector', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = bit_n
+		new_pin_n(bit_n, self, 'A', 0, 'L')
+		DECODER(self, bit_n, 'deco', latency)
+		conn2_n( bit_n, (self, 'A', 0), (self.deco, 'inp', 0) )
+		Pin(self, 'en', 'L').conn = self.deco.en
+
+		dn = 1 << bit_n
+		new_pin_n(dn, self, 'D', 0, 'L')
+		ANDSet(self, dn, 'and_set', latency)
+		conn2_n( dn, (self, 'D', 0), (self.and_set, 'inp', 0) )
+		conn2_n( dn, (self.deco, 'out', 0), (self.and_set, 'sel', 0) )
+
+		OR_N(self, dn, 'or_n', latency)
+		conn2_n( dn, (self.and_set, 'out', 0), (self.or_n, 'inp', 0) )
+		self.or_n.out.conn = Pin(self, 'out', 'L')
+

# SELECTORクラス追加です

# en端子が'L'なら出力は'L'
# D_x 入力から
# A_x のアドレスで指定されたものを選び
# out に出力します

# DECORDER 出力を ANDSet の sel_x 端子に入れて
# ANDSet出力の OR をとったものを
# SELECTOR の出力としてます


 class ROM_D1(Obj):
+	# en, A_x, D_0
 	def __init__(self, parent, abit_n, dlst=(), name='rom_d1', latency=10):
 		Obj.__init__(self, parent, name)
 		self.dbgout(dlst)
 		self.n = abit_n
-		for i in range(abit_n):
-			Pin( self, name_i('A', i), 'L' )
-		n1 = abit_n - 1
-		msb = getattr( self, name_i('A', n1) )
-		msb.conn = DECODER(self, 1, 'deco', latency).inp_0
-
-		if abit_n == 1:
-			dlst_v = lambda i: dlst[i] if i < len(dlst) else -1
-			dlst_b = lambda i: bool( dlst_v(i) & 1 )
-			Pin(self, 'p0', dlst_b(0) ).conn = AND(self, 'and_d', latency).inp_a
-			Pin(self, 'p1', dlst_b(1) ).conn = AND(self, 'and_u', latency).inp_a
-		
-			en_d = self.and_d.inp_b
-			en_u = self.and_u.inp_b
-			out_d = self.and_d.out
-			out_u = self.and_u.out
-		else:
-			half = 1 << n1
-			ROM_D1(self, n1, dlst[ :half ], 'rom_d', latency)
-			ROM_D1(self, n1, dlst[ half: ], 'rom_u', latency)
-			Joint_N(self, n1, 'jt_n')
-			self.jt_n.new_pin_conn(self.rom_d, 'A')
-			self.jt_n.new_pin_conn(self.rom_u, 'A')
-			self.jt_n.new_pin_conn(self, 'A', n1, direc='from_targ')
-
-			en_d = self.rom_d.en
-			en_u = self.rom_u.en
-			out_d = self.rom_d.D_0
-			out_u = self.rom_u.D_0
-
-		self.deco.out_0.conn = en_d
-		self.deco.out_1.conn = en_u
-
-		OR(self, 'or_', latency).out.conn = AND(self, 'and_out', latency).inp_a
-		out_d.conn = self.or_.inp_a
-		out_u.conn = self.or_.inp_b
-
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt').new_pin()
-		self.jt.new_pin().conn = self.deco.en
-		self.jt.new_pin().conn = self.and_out.inp_b
-		self.and_out.out.conn = Pin(self, 'D_0', 'L')
+		dn = 1 << abit_n
+
+		SELECTOR(self, abit_n, 'select', latency)
+
+		Pin(self, 'en', 'L').conn = self.select.en
+		new_pin_n(abit_n, self, 'A', 0, 'L')
+		conn2_n( abit_n, (self, 'A', 0), (self.select, 'A', 0) )
+		self.select.out.conn = Pin(self, 'D_0', 'L')
+
+		# as late as possible, for data enque
+		PinSet(self, dn, dlst, 'pin_set')
+		conn2_n( dn, (self.pin_set, 'p', 0), (self.select, 'D', 0) )
 
# ROM_D1クラスの実装の変更です

# 端子のコメントを追加して

# 以前の再帰のコードはごっそり削除

# 変更後の実行では
# SELECTOR の入力に PinSet からの出力をつないで、おしまい

# PinSet の生成は、大人の都合でなるべく後方で行ないます

# PinSet 出力は SELECTOR の入力へとつないでますが、
# SELECTOR は入力端子を一旦'L'で初期化します
# PinSet の生成と初期化が先にスケジューラにenqueされてしまうと、
# SELECTOR の入力'L'初期化が後から実行されてしまい、
# ROMとしてのデータがSELECTORに伝わらないからです


 class ROM(Obj):
+	# en, A_x, D_x
 	def __init__(self, parent, dbit_n, abit_n, dlst=(), name='rom', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = dbit_n
 		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
-		for i in range(abit_n):
-			Pin( self, name_i('A', i), 'L' )
+		new_pin_n(abit_n, self, 'A', 0, 'L')
 		Joint_N(self, abit_n, 'jt_n_A').new_pin_conn(self, 'A', abit_n, 'from_targ')
 
 		for i in range(dbit_n):

# 端子のコメント追加と
# 今回追加した new_pin_n() 関数で、複数の端子を生成するようにしました

# nビットの方のROMクラスの構成は、今回は変更ありません


@@ -452,6 +464,7 @@
 			rom_b.D_0.conn = Pin( self, name_i('D', i), 'L' )
 
 class CLK(Obj):
+	# en, out
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
 		Obj.__init__(self, parent, name, None, pos)
 		latency = tm_from_fsec( 1.0 / ( 2 * hz ) )
@@ -471,6 +484,7 @@
 			self.jt_out.new_pin().conn = Lamp(self, lamp_name).inp
 
 class Lamp(Obj):
+	# inp
 	def __init__(self, parent, name='lamp', latency=None, pos=(0,0), show_inf={}):
 		Obj.__init__(self, parent, name, latency, pos)
 		self.show_inf = show_inf
@@ -502,6 +516,7 @@
 		sys.stdout.flush()
 
 class Lamp_N(Obj):
+	# inp_x
 	def __init__(self, parent, n, name='lamp_n', latency=None, pos=(0,0), slide=('y',1)):
 		Obj.__init__(self, parent, name, latency, pos)
 		self.n = n

# 端子のコメント追加です


@@ -513,9 +528,10 @@
 
 	def conn_targ(self, targ, targ_pin_name, n=-1):
 		n = self.n if n < 0 else n
-		conn_n( n, targ, 0, targ_pin_name, self, 0, 'inp' )
+		conn2_n( n, (targ, targ_pin_name, 0), (self, 'inp', 0) )
 

# 複数の端子を接続する conn_n() 関数がありますが、
# 今から思えば、引数の順が気に入りません

# conn_n() は、cmd_xxx.txt からも使ってるかもしれないので、
# とりあえず残しておいて、、、

# 新たに conn2_n() を追加してます
# 引数の順番を変えつつ、可読性向上のためタプルにまとめてます
# (conn2_n() からは、conn_n() を呼び出してるだけ...)


 class Lamp_7seg(Obj):
+	# inp_x
 	def __init__(self, parent, name='lamp_7seg', latency=None, pos=(0,0)):
 		Obj.__init__(self, parent, name, None, pos)
 		lst = [
@@ -533,9 +549,10 @@
 			Pin( self, name_i('inp', i), 'L' ).conn = lamp.inp
 
 	def conn_targ(self, targ, targ_pin_name):
-		conn_n( 7, targ, 0, targ_pin_name, self, 0, 'inp' )
+		conn2_n( 7, (targ, targ_pin_name, 0), (self, 'inp', 0) )
 
 class DECODER_7seg(Obj):
+	# en, inp_x, out_x
 	def __init__(self, parent, name='dec_7seg', latency=10):
 		Obj.__init__(self, parent, name)
 		dlst = [
@@ -544,15 +561,13 @@
 		]
 		ROM(self, 7, 4, dlst, 'rom', latency)
 		Pin(self, 'en', 'L').conn = self.rom.en
-		for i in range(4):
-			Pin( self, name_i('inp', i), 'L' )
-		conn_n(4, self, 0, 'inp', self.rom, 0, 'A')
-		for i in range(7):
-			Pin( self, name_i('out', i), 'L' )
-		conn_n(7, self.rom, 0, 'D', self, 0, 'out')
+		new_pin_n(4, self, 'inp', 0, 'L')
+		conn2_n( 4, (self, 'inp', 0), (self.rom, 'A', 0) )
+		new_pin_n(7, self, 'out', 0, 'L')
+		conn2_n( 7, (self.rom, 'D', 0), (self, 'out', 0) )
 
 	def conn_targ(self, targ, targ_pin_name):
-		conn_n( 7, self, 0, 'out', targ, 0, targ_pin_name )
+		conn2_n( 7, (self, 'out', 0), (targ, targ_pin_name, 0) )
 
 class Sched:
 	def __init__(self):
@@ -620,10 +635,13 @@
 def new_pin_conn(obj, in_obj, sta_i, n=-1, name='inp', direc='to_in_obj'):
 	if n < 0:
 		n = in_obj.n
-	for i in range(n):
-		Pin( obj, name_i( name, sta_i + i ) )
+	new_pin_n(n, obj, name, sta_i)
 	direc_rev = (direc != 'to_in_obj')
-	conn_n(n, obj, sta_i, name, in_obj, 0, name, direc_rev)
+	conn2_n(n, (obj, name, sta_i), (in_obj, name, 0), direc_rev)
+

# 端子のコメント追加
# 今回追加の new_pin_n() 関数を使うように変更
# 今回追加の conn2_n() 関数を使うように変更
# ... いづれかです


+def conn2_n( n, (from_obj, from_name, from_sta_i), (to_obj, to_name, to_sta_i), direc_rev=False ):
+	# replaced args, only ...
+	conn_n(n, from_obj, from_sta_i, from_name, to_obj, to_sta_i, to_name, direc_rev)
 
 def conn_n(n, from_obj, from_sta_i, from_name, to_obj, to_sta_i, to_name, direc_rev=False):
 	for i in range(n):


# conn2_n() を追加してますが、引数の順番を変えて、タプルにしてるだけです
# そのまま conn_n() を呼び出してます


@@ -634,6 +652,16 @@
 				to_pin.conn = from_pin
 			else:
 				from_pin.conn = to_pin
+
+def new_pin_n(n, targ, name, name_sta_i=0, dlst=None):
+	for i in range(n):
+		Pin( targ, name_i(name, name_sta_i + i), get_dlst_val(i, dlst) )
+
+def get_dlst_val(i, dlst):
+	t = type(dlst)
+	if t not in [ tuple, list ]:
+		return dlst # same v
+	return dlst[i] if i < len(dlst) else -1
 
 def name_i(name, i):
 	return '{}_{}'.format(name, i)
$

# 最後は new_pin_n() 関数の追加です
# 複数の端子を生成します

# 端子の初期値の指定は、get_dlst_val() 関数におまかせ

# dlstの型がタプル (x,x,..) でもリスト[x,x,..] でもなければ
# dlst自体を返します

# get_dlst_val() は new_pin_n() でしか使われてないので、
# 返した値は Pin() の 初期値指定に渡ります

# None ならPin()では、初期値による初期化はしません
# 'L' とかなら、全部の端子が 'L' で初期化

# ('L', 'H', 'H', 'L', ...) なら
# 複数の端子は、順番にその値で初期化されます

従来の再帰方式のROM_D1クラスだと、 終段で en 端子と AND をとってから出力してましたが、 今回の構成では、en 端子はセレクタにだけ入れて、セレクタ任せです。

もっと言うと、セレクタのenはデコーダのenに入るだけで、デコーダ任せです。

とりあえず、、、

のお試し実行をしてみましたが、特におかわりなく大丈夫そうです。


足し算できるかな

足し算をしてみたいなと。

例によって素人なのでWebで検索してみると、基本はXORゲートとのこと。

繰り上がりはANDゲートで出力。なるほど。

つまり、こういうことかな?

           |+----+
A -----@---||     \
       |   )) XOR  >--- Y
B --@------||     /
    |  |   |+----+
    |  |
    |  |   +-----+
    |  .---|      \
    |      |  AND  |--- C
    .------|      /
           +-----+

    ADDクラスの構成

これを元にして、小さい方の桁からの繰り上がり Cin があって、 それを足しこんで、さらに大きい方の桁への繰り上がらせるには、、、

                     +-------+
Cin------------------|A     Y|--------------- Y
       +-------+     |  ADD  |   +---+
A -----|A     Y|-----|B     C|---|    \
       |  ADD  |     +-------+   ) OR  >----- C
B -----|B     C|-----------------|    /
       +-------+                 +---+

                ADD_Cクラスの構成

こうしておいて、ビット数分だけつなぎますと、、、

       +--------+
Cin ---|Cin    Y|----- Y0
       |        |
A0 ----|A ACC_C |
       |        |
B0 ----|B      C|--.
       +--------+  |
   .---------------.
   |   +--------+
   .---|Cin    Y|----- Y1
       |        |
A1 ----|A ACC_C |
       |        |
B1 ----|B      C|--.
       +--------+  |
   .---------------.
   |   +--------+
   .---|Cin    Y|----- Y2
       |        |
A2 ----|A ACC_C |
       |        |
B2 ----|B      C|--.
       +--------+  |
   .---------------.
   |
   :       :
   |   +--------+
   .---|Cin    Y|----- Yn-1
       |        |
An-1 --|A ACC_C |
       |        |
Bn-1 --|B      C|----- C
       +--------+

    ADD_Nクラスの構成

なーるほど。

Cin を 'L' にしておいて、A + B が Y に出力されて、繰越しが C に出ると。

さてさて、どうやって試してたものか?

ふと思いつくのは、、、

ROMのデータを順番に読み出して、データビットの上位側と下位側で足し算して表示してみるとか。

4ビットの足し算、やってみます。

nand.py-22-diff.txt

$ cat nand.py-22-diff.txt
--- nand.py-
+++ nand.py
@@ -52,6 +52,7 @@
 			self.enque(self.parent.update)
 
 class NAND(Obj):
+	# inp_a, inp_b, out
 	def __init__(self, parent, name='nand', latency=10):
 		Obj.__init__(self, parent, name, latency)
 		Pin(self, 'out')

# 端子のコメント追加


@@ -85,6 +86,10 @@
 			if pin.conn:
 				pin.set(v)
 
+	def new_pin_conn_n(self, n, targ, name, sta_i):
+		for i in range(n):
+			self.new_pin().conn = getattr( targ, name_i(name, sta_i + i) )
+
 class Joint_N(Obj):
 	def __init__(self, parent, n, name='jt_n', latency=None, pos=(0,0), slide=('y',1), lamp_name=None):
 		Obj.__init__(self, parent, name, latency, pos)

# Jointクラスに new_pin_conn_n() メソッドとりあえず用意しておきます
# 1つのジョイントから各端子に向けて、まとめてつなぎます


@@ -95,11 +100,11 @@
 			Joint( self, name_i('jt', i), latency, pos, lamp_nm )
 			pos = step_pos_slide(pos, slide)
 
-	def new_pin_conn(self, targ, targ_pin_name, n=-1, direc='to_targ'):
+	def new_pin_conn(self, targ, targ_pin_name, sta_i=0, n=-1, direc='to_targ'):
 		if n < 0:
 			n = self.n
 		for i in range(n):
-			targ_pin = getattr( targ, name_i(targ_pin_name, i), None )
+			targ_pin = getattr( targ, name_i(targ_pin_name, sta_i + i), None )
 			jt = getattr( self, name_i('jt', i), None )
 			if targ_pin and jt:
 				if direc == 'to_targ':

# Joint_Nクラスの new_pin_conn() メソッドに引数追加です
# つなぐターゲットの端子番号にゲタをはかせれるよう
# 開始番号 sta_i の指定を追加してます


@@ -108,6 +113,7 @@
 					targ_pin.conn = jt.new_pin()
 
 class NOT(Obj):
+	# inp, out
 	def __init__(self, parent, name='not_', latency=10):
 		Obj.__init__(self, parent, name)
 		NAND(self, 'nand', latency)
@@ -118,6 +124,7 @@
 		self.nand.out.conn = Pin(self, 'out')
 
 class AND(Obj):
+	# inp_a, inp_b, out
 	def __init__(self, parent, name='and_', latency=10):
 		Obj.__init__(self, parent, name)
 		NAND(self, 'nand', latency)
@@ -128,6 +135,7 @@
 		self.not_.out.conn = Pin(self, 'out')
 
 class OR(Obj):
+	# inp_a, inp_b, out
 	def __init__(self, parent, name='or_', latency=10):
 		Obj.__init__(self, parent, name)
 		NOT(self, 'not_a', latency)
@@ -140,6 +148,7 @@
 		self.nand.out.conn = Pin(self, 'out')
 
 class NOR(Obj):
+	# inp_a, inp_b, out
 	def __init__(self, parent, name='nor', latency=10):
 		Obj.__init__(self, parent, name)
 		OR(self, 'or_', latency)
@@ -150,6 +159,7 @@
 		self.not_.out.conn = Pin(self, 'out')
 
 class XOR(Obj):
+	# inp_a, inp_b, out
 	def __init__(self, parent, name='xor', latency=10):
 		Obj.__init__(self, parent, name)
 		NAND(self, 'nand_i', latency)
@@ -177,6 +187,7 @@
 		self.nand_o.out.conn = Pin(self, 'out')
 
 class AND_N(Obj):
+	# inp_x, out
 	def __init__(self, parent, n, name='and_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
@@ -206,6 +217,7 @@
 		self.and_.out.conn = Pin(self, 'out')
 
 class OR_N(Obj):
+	# inp_x, out
 	def __init__(self, parent, n, name='or_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
@@ -235,6 +247,7 @@
 		self.or_.out.conn = Pin(self, 'out')
 
 class NAND_N(Obj):
+	# inp_x, out
 	def __init__(self, parent, n, name='nand_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
@@ -245,6 +258,7 @@
 		self.not_.out.conn = Pin(self, 'out')
 
 class NOR_N(Obj):
+	# inp_x, out
 	def __init__(self, parent, n, name='nor_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
@@ -255,6 +269,7 @@
 		self.not_.out.conn = Pin(self, 'out')
 
 class RSFF(Obj):
+	# R, S, Q, nQ
 	def __init__(self, parent, name='rsff', latency=10):
 		Obj.__init__(self, parent, name)
 		NAND(self, 'nand_r', latency)
@@ -275,6 +290,7 @@
 		self.nand_s.out.enque_set('H')
 
 class JKFF(Obj):
+	# J, K, CLK, Q, nQ
 	def __init__(self, parent, name='jkff', latency=10):
 		Obj.__init__(self, parent, name)
 		RSFF(self, 'rsff_o', latency)
@@ -318,6 +334,7 @@
 		self.jt_k_o.new_pin().conn = Pin(self, 'nQ')
 
 class TFF(Obj):
+	# CLK, Q, nQ
 	def __init__(self, parent, name='tff', latency=10):
 		Obj.__init__(self, parent, name)
 		JKFF(self, 'jkff', latency)
@@ -328,6 +345,7 @@
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 
 class DFF(Obj):
+	# D, CLK, Q, nQ
 	def __init__(self, parent, name='dff', latency=10):
 		Obj.__init__(self, parent, name)
 		JKFF(self, 'jkff', latency)

# 端子のコメント追加


@@ -375,7 +393,7 @@
 			DECODER(self, bn1, 'dec_u', latency)
 			DECODER(self, bn1, 'dec_d', latency)
 			Joint_N(self, bn1, 'jt_n')
-			self.jt_n.new_pin_conn(self, 'inp', bn1, direc='from_targ')
+			self.jt_n.new_pin_conn(self, 'inp', 0, bn1, direc='from_targ')
 			self.jt_n.new_pin_conn(self.dec_u, 'inp')
 			self.jt_n.new_pin_conn(self.dec_d, 'inp')
 
@@ -454,13 +472,13 @@
 		self.n = dbit_n
 		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
 		new_pin_n(abit_n, self, 'A', 0, 'L')
-		Joint_N(self, abit_n, 'jt_n_A').new_pin_conn(self, 'A', abit_n, 'from_targ')
+		Joint_N(self, abit_n, 'jt_n_A').new_pin_conn(self, 'A', 0, abit_n, 'from_targ')
 
 		for i in range(dbit_n):
 			dlst_i = [ (d >> i) & 1 for d in dlst ]
 			rom_b = ROM_D1(self, abit_n, dlst_i, name_i('rom_b', i), latency)
 			self.jt_en.new_pin().conn = rom_b.en
-			self.jt_n_A.new_pin_conn(rom_b, 'A', abit_n, 'to_targ')
+			self.jt_n_A.new_pin_conn(rom_b, 'A', 0, abit_n, 'to_targ')
 			rom_b.D_0.conn = Pin( self, name_i('D', i), 'L' )
 
 class CLK(Obj):

# Joint_Nクラスの new_pin_conn()メソッドの引数 sta_i 追加による変更です
# (この変更は、やむなしか...)


@@ -470,7 +488,7 @@
 		latency = tm_from_fsec( 1.0 / ( 2 * hz ) )
 		NAND(self, 'nand', latency)
 		Joint(self)
-		AND(self)
+		AND(self, 'and_')
 
 		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
 		self.jt_en.new_pin().conn = self.nand.inp_a

# NANDじゃなくて、AND側にlatency を追加すべきかも、ですが、、、
# 先おくり
# 名前だけでも、明示的に指定しておきましょうか


@@ -568,6 +586,67 @@
 
 	def conn_targ(self, targ, targ_pin_name):
 		conn2_n( 7, (self, 'out', 0), (targ, targ_pin_name, 0) )
+
+class DECODER_Lamp_7seg(Obj):
+	# en, inp_x
+	def __init__(self, parent, name='deco_lamp_7seg', latency=10, pos=(0,0)):
+		Obj.__init__(self, parent, name)
+		DECODER_7seg(self, 'deco', latency)
+		Pin(self, 'en', 'L').conn = self.deco.en
+		new_pin_n(4, self, 'inp', 0, 'L')
+		conn2_n(4, (self, 'inp', 0), (self.deco, 'inp', 0) )
+
+		Lamp_7seg(self, 'lamp', latency, pos)
+		conn2_n(7, (self.deco, 'out', 0), (self.lamp, 'inp', 0) )
+

# 7セグ用のデコーダとランプのセットです


+class ADD(Obj):
+	# A, B, Y, C
+	def __init__(self, parent, name='add', latency=10):
+		Obj.__init__(self, parent, name)
+
+		Pin(self, 'A', 'L').conn = Joint(self, 'jt_a').new_pin()
+		Pin(self, 'B', 'L').conn = Joint(self, 'jt_b').new_pin()
+
+		XOR(self, 'xor', latency).out.conn = Pin(self, 'Y', 'L')
+		self.jt_a.new_pin().conn = self.xor.inp_a
+		self.jt_b.new_pin().conn = self.xor.inp_b
+
+		AND(self, 'and_', latency).out.conn = Pin(self, 'C', 'L')
+		self.jt_a.new_pin().conn = self.and_.inp_a
+		self.jt_b.new_pin().conn = self.and_.inp_b
+

# 単純な1ビットの足し算
# 結果の桁あがりはCへ


+class ADD_C(Obj):
+	# Cin, A, B, Y, C
+	def __init__(self, parent, name='add_c', latency=10):
+		Obj.__init__(self, parent, name)
+
+		ADD(self, 'add_ab', latency)
+		Pin(self, 'A', 'L').conn = self.add_ab.A
+		Pin(self, 'B', 'L').conn = self.add_ab.B
+
+		ADD(self, 'add_cy', latency)
+		Pin(self, 'Cin', 'L').conn = self.add_cy.A
+		self.add_ab.Y.conn = self.add_cy.B
+		self.add_cy.Y.conn = Pin(self, 'Y', 'L')
+
+		OR(self, 'or_', latency).out.conn = Pin(self, 'C', 'L')
+		self.add_cy.C.conn = self.or_.inp_a
+		self.add_ab.C.conn = self.or_.inp_b
+

# 同じく1ビットの足し算ですが
# Cinからの桁あがりを考慮して計算して
# 結果の桁あがりはCへ


+class ADD_N(Obj):
+	# Cin, A_x, B_x, Y_x, C
+	def __init__(self, parent, n, name='add_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		c = Pin(self, 'Cin', 'L')
+		for i in range(n):
+			add_c = ADD_C( self, name_i('add_c', i), latency )
+			c.conn = add_c.Cin
+			Pin( self, name_i('A', i), 'L' ).conn = add_c.A
+			Pin( self, name_i('B', i), 'L' ).conn = add_c.B
+			add_c.Y.conn = Pin( self, name_i('Y', i), 'L' )
+			c = add_c.C
+		c.conn = Pin(self, 'C', 'L')
 
 class Sched:
 	def __init__(self):
$ 

# Nビットの足し算
# Cinからの桁あがりを考慮して計算して
# 結果の桁あがりはCへ

# Cin端子のデフォルトの初期値は 'L' で桁あがり無しです

続いて確認用のお試しコマンド。

cmd_add.txt

$ cat cmd_add.txt
o.pos = (4,4)

CLK(o, 0.2, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 3, 'cnt').CLK

# 0.2 Hz(周期5秒)クロックの出力を3ビットのカウンタに入れ


o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]
ROM(o, 8, 3, o.dlst, 'rom')
conn2_n( 3, (o.cnt, 'out', 0), (o.rom, 'A', 0) )

# データ幅8ビットアドレス空間3ビットのROMを用意
# カウンタからの3ビット出力をROMのアドレスにつなぎ


Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.rom, 'D', 4, direc='from_targ')
Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.rom, 'D', direc='from_targ')

# 4ビットのJoint群をjt_a, jt_bの2組分用意
# A側をROMのデータ出力の上位4ビットにつなぎ
# B側をROMのデータ出力の下位4ビットにつなぎ


DECODER_Lamp_7seg(o, 'deco_lamp_a', pos=(10,6))
DECODER_Lamp_7seg(o, 'deco_lamp_b', pos=(20,6))
o.jt_a.new_pin_conn(o.deco_lamp_a, 'inp', direc='to_targ')
o.jt_b.new_pin_conn(o.deco_lamp_b, 'inp', direc='to_targ')

# 7セグのデコーダとランプのセットを2組分用意
# それぞれ
# ROM出力上位のA側ジョイント
# ROM出力下位のB側ジョイント
# からつなぎ


ADD_N(o, 4, 'add_4')
o.jt_a.new_pin_conn(o.add_4, 'A', direc='to_targ')
o.jt_b.new_pin_conn(o.add_4, 'B', direc='to_targ')

# Nビットの足し算 ADD_N を用意して
# ROM出力上位のA側ジョイントから、ADD_Nの A_x 端子につなぎ
# ROM出力下位のB側ジョイントから、ADD_Nの B_x 端子につなぎ


Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.add_4, 'Y', direc='from_targ')
DECODER_Lamp_7seg(o, 'deco_lamp_y', pos=(30,6))
o.jt_y.new_pin_conn(o.deco_lamp_y, 'inp', direc='to_targ')

# 4ビットのJoint群 jt_y を用意して、ADD_N の出力 y_x 端子につなぎ
# 7セグのデコーダとランプのセットを用意
# ADD_N 結果の出力のジョイント jt_y からつなぎ


o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp

# ADD_N の繰り上がり結果の端子Cに、ランプをつなぐ


sched.enque( (60,0), o.clk.en.set, 'H' )
sched.enque( (60,0), o.rom.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_a.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_b.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_y.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 

# どうにも初期化が、えらい時間がかかってます
# とりあえず1分まって、初期化が終ってから
# クロックのスタートをかけてます

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_add.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

あまりに初期化の待ちが長いので、動画は一部省略してます。

ROMからの出力がバタついてると、その結果も律義に足し算されてるようですが、 足し算そのものは、なんとなく大丈夫そうです。


初期化が長いので

それにしても、7セグメントのデコーダの初期化というかROMの初期化がなが〜いです。

どこに時間がかかってるのでしょうか? 状況を把握してみます。

$ ./nand.py -v < cmd_add.txt | less
0.000000000 cmd_exec "o.pos = (4,4)"
0.000000000 cmd_exec "CLK(o, 0.2, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 3, 'cnt').CLK"
0.000000000 cmd_exec "o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]"
0.000000000 cmd_exec "ROM(o, 8, 3, o.dlst, 'rom')"
0.000000000 o.rom.rom_b_0 [0, 1, 1, 1, 1, 1, 0, 1]
0.000000000 o.rom.rom_b_1 [0, 0, 1, 0, 1, 0, 1, 1]
0.000000000 o.rom.rom_b_2 [0, 0, 0, 1, 1, 0, 0, 1]
0.000000000 o.rom.rom_b_3 [0, 0, 0, 0, 0, 0, 0, 1]
0.000000000 o.rom.rom_b_4 [0, 1, 0, 0, 0, 1, 1, 1]
0.000000000 o.rom.rom_b_5 [0, 0, 1, 0, 1, 1, 1, 1]
0.000000000 o.rom.rom_b_6 [0, 0, 0, 1, 1, 1, 1, 1]
0.000000000 o.rom.rom_b_7 [0, 0, 0, 0, 0, 1, 1, 1]
0.000000000 cmd_exec "conn2_n( 3, (o.cnt, 'out', 0), (o.rom, 'A', 0) )"
0.000000000 cmd_exec "Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.rom, 'D', 4, direc='from_targ')"
0.000000000 cmd_exec "Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.rom, 'D', direc='from_targ')"
0.000000000 cmd_exec "DECODER_Lamp_7seg(o, 'deco_lamp_a', pos=(10,6))"
0.000000000 o.deco_lamp_a.deco.rom.rom_b_0 [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_1 [1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_2 [1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_3 [0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_4 [1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_5 [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0]
0.000000000 o.deco_lamp_a.deco.rom.rom_b_6 [1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0]
  :
0.000000100 o.deco_lamp_y.deco.rom.rom_b_6.select.or_n.or_.out L
0.000000100 o.deco_lamp_y.deco.rom.rom_b_6.select.or_n.out L
2.500000000 o.clk.nand.out H
2.500000000 o.clk.jt.pin0 H
2.500000000 o.clk.jt.pin1 H
2.500000000 o.clk.nand.inp_b H
2.500000000 o.clk.jt.pin2 H
2.500000000 o.clk.and_.inp_b H
2.500000000 o.clk.and_.nand.inp_b H
2.500000000 o.clk.nand update
2.500000000 o.clk.and_.nand update
60.000000000 o.clk.en H
60.000000000 o.clk.jt_en.pin0 H
60.000000000 o.rom.en H
60.000000000 o.rom.jt_en.pin0 H
  :

クロックをスタートさせる時刻1分になる前の段階で、 だれの出力が多いか調べてみます。

出力の量だけで一概に処理の多さは語れませんが、ひとつの目安にはなるでしょう。

$ ./nand.py -v < cmd_add.txt | sed -n -e '1,/60\.0/p' > cmd_add.out

# 出力の先頭から '60.0' の行までを cmd_add.out におとしてみて


$ tail cmd_add.out
2.500000000 o.clk.nand.out H
2.500000000 o.clk.jt.pin0 H
2.500000000 o.clk.jt.pin1 H
2.500000000 o.clk.nand.inp_b H
2.500000000 o.clk.jt.pin2 H
2.500000000 o.clk.and_.inp_b H
2.500000000 o.clk.and_.nand.inp_b H
2.500000000 o.clk.nand update
2.500000000 o.clk.and_.nand update
60.000000000 o.clk.en H
$ 

# 末尾確認OK


$ wc -l cmd_add.out
44807 cmd_add.out
$ 

# 全体で 4万5千行くらい!


$ cat cmd_add.out | sed -n -e 's/.* o\.\([^\.]*\)\..*$/\1/p' > cmd_add2.out

# x.xxxxxxxxx o.yyy.zzzz...
# な行に絞って yyy の部分だけにして cmd_add2.out に

$ wc -l cmd_add2.out
44751 cmd_add2.out

# ちょっと減りましたが、中身はこんな感じ

$ head cmd_add2.out
rom
rom
rom
rom
rom
rom
rom
rom
deco_lamp_a
deco_lamp_a

$ tail cmd_add2.out
clk
clk
clk
clk
clk
clk
clk
clk
clk
clk


# ソートして重複分をとりのぞくと

$ cat cmd_add2.out | sort | uniq | tee lst
C
add_4
clk
cnt
deco_lamp_a
deco_lamp_b
deco_lamp_y
jt_a
jt_b
jt_y
rom
$ 


# lst について cmd_add2.out の行数を集計すると

$ for s in $(cat lst) ; do echo -n $s: ; grep $s cmd_add2.out | wc -l ; done
C:1
add_4:582
clk:37
cnt:516
deco_lamp_a:12338
deco_lamp_b:12338
deco_lamp_y:12338
jt_a:24
jt_b:24
jt_y:20
rom:6533


# deco_lamp_a,b,y は、それぞれDECODERとLampをもってて
# Lamp側の処理がそんなにあるとは思えないですよね

# となるとDECODER側、そしてその中のROM

$ grep 'o\.deco_lamp_a\.deco' cmd_add.out | wc -l       
12311

$ grep 'o\.deco_lamp_a\.lamp' cmd_add.out | wc -l
21

$ grep 'o\.deco_lamp_a\.deco\.rom' cmd_add.out | wc -l
12299


# ROM は 7セグ用でデータ幅7ビットなので
# 7個の ROM_D1 に割り振られてて

o.deco_lamp_a.deco.rom_rom_b_0
  :
o.deco_lamp_a.deco.rom_rom_b_6

$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0' cmd_add.out | wc -l
1753

$ expr 1753 '*' 7
12271

$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_1' cmd_add.out | wc -l
1749
$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_2' cmd_add.out | wc -l
1745
$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_3' cmd_add.out | wc -l
1753


# バラツキはあるけど、だいたい均等


rom:6533
# こちらは足し算データ用で
# データ8ビット幅でアドレス3bit

# ということは
# データ7ビット幅でアドレス4bitに換算してみると

$ expr 6533 '*' 7 / 8 '*' 2
11432


# だいたい7セグ用のROMの場合と同じオーダー

# ROMの初期化が圧倒的に処理が多いです


# ROM_D1クラスで保持してる主要なクラスは
# SELECTORクラス 'select' と PinSetクラス 'pin_set'

$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0\.select' cmd_add.out | wc -l
1730
$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0\.pin_set' cmd_add.out | wc -l
16

# 圧倒的にSELECTOR 


# SELECTORクラスで保持してる主要なクラスは
# DECODERクラスの 'deco'
# ANDSetクラスの 'and_set'
# OR_Nクラスの 'or_n'

$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0\.select.\deco' cmd_add.out | wc -l
886
$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0\.select.\and_set' cmd_add.out | wc -l
356
$ grep 'o\.deco_lamp_a\.deco\.rom\.rom_b_0\.select.\or_n' cmd_add.out | wc -l
454

セレクタの中でもデコーダの行が半分くらい占めてます。

そもそも、、、ROMのデータ幅が8ビットなら、 8個のセレクタを保持してて、その中でそれぞれのデコーダが、同じ動作をしてます。

ROMの実装を改善して、各ビットで共通のデコーダを1つだけ持てば、 ちったぁ「まし」になるかもしれません。

SELECTORクラスのNビット版を「マルチプレクサ」MUXクラスとして用意して、 ここでデコーダの共通化をしてみます。

ROMクラスからは、ROM_D1クラスを使うのをやめて、 MUXを使うように変更してみます。

nand.py-23-diff.txt

$ cat nand.py-23-diff.txt
--- nand.py-
+++ nand.py
@@ -357,6 +357,18 @@
 		self.jkff.Q.conn = Pin(self, 'Q')
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 
+class DFF_N(Obj):
+	# D_x, CLK, Q_x
+	def __init__(self, parent, n, name='dff_n', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+		Pin(self, 'CLK', 'L').conn = Joint(self, 'jt_clk').new_pin()
+		for i in range(n):
+			dff_i = DFF( self, name_i('dff', i), latency )
+			self.jt_clk.new_pin().conn = dff_i.CLK
+			Pin( self, name_i('D', i), 'L' ).conn = dff_i.D
+			dff_i.Q.conn = Pin( self, name_i('Q', i), 'L' )
+
 class COUNTER(Obj):
 	# CLK, out_x
 	def __init__(self, parent, bit_n, name='counter', latency=10):

# 次の一手にそなえて追加してます
# D-FFをN個並べてNビットの「ラッチ」です
# 今回は使いません


@@ -426,6 +438,35 @@
 			Pin( self, name_i('sel', i) ).conn = and_i.inp_b
 			and_i.out.conn = Pin( self, name_i('out', i) )
 
+class ANDSetOr(Obj):
+	# inp_x, sel_x, out
+	def __init__(self, parent, n, name='and_set_or', latency=10):
+		Obj.__init__(self, parent, name)
+		ANDSet(self, n, 'and_set', latency)
+		new_pin_n(n, self, 'inp', 0, 'L')
+		conn2_n( n, (self, 'inp', 0), (self.and_set, 'inp', 0) )
+		new_pin_n(n, self, 'sel', 0, 'L')
+		conn2_n( n, (self, 'sel', 0), (self.and_set, 'sel', 0) )
+
+		OR_N(self, n, 'or_n', latency).out.conn = Pin(self, 'out', 'L')
+		conn2_n( n, (self.and_set, 'out', 0), (self.or_n, 'inp', 0) )
+

# SELECTORクラスで使ってる ANDSet の出力に OR_N をつないぐ組み合わせを
# ANDSetOrクラスとしてみました
# SELECTORクラス からは、このAndSetOrを保持するようにします


+class GATE(Obj):
+	# inp_x, en, out_x
+	def __init__(self, parent, n, name='gate', latency=10):
+		Obj.__init__(self, parent, name)
+		self.n = n
+
+		ANDSet(self, n, 'and_set', latency)
+
+		new_pin_n( n, self, 'inp', 0, 'L' )
+		conn2_n( n, (self, 'inp', 0), (self.and_set, 'inp', 0) )
+		new_pin_n( n, self, 'out', 0, 'L' )
+		conn2_n( n, (self.and_set, 'out', 0), (self, 'out', 0) )
+
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt').new_pin()
+		self.jt.new_pin_conn_n( n, self.and_set, 'sel', 0 )
+
 class SELECTOR(Obj):
 	# en, A_x, D_x, out
 	def __init__(self, parent, bit_n, name='selector', latency=10):

# これまた次の一手にそなえて追加です
# en端子'L'の間はout_xは'L'に固定
# en端子'H'にすると、inp_xがout_xに反映されます


@@ -438,13 +479,30 @@
 
 		dn = 1 << bit_n
 		new_pin_n(dn, self, 'D', 0, 'L')
-		ANDSet(self, dn, 'and_set', latency)
-		conn2_n( dn, (self, 'D', 0), (self.and_set, 'inp', 0) )
-		conn2_n( dn, (self.deco, 'out', 0), (self.and_set, 'sel', 0) )
-
-		OR_N(self, dn, 'or_n', latency)
-		conn2_n( dn, (self.and_set, 'out', 0), (self.or_n, 'inp', 0) )
-		self.or_n.out.conn = Pin(self, 'out', 'L')
+		ANDSetOr(self, dn, 'and_set_or', latency)
+		conn2_n( dn, (self, 'D', 0), (self.and_set_or, 'inp', 0) )
+		conn2_n( dn, (self.deco, 'out', 0), (self.and_set_or, 'sel', 0) )
+		self.and_set_or.out.conn = Pin(self, 'out', 'L')
+

# SELECTORクラスでは
# ANDSetOrクラスを保持させるよう変更してます
# (やってる事は同じです)


+class MUX(Obj):
+	# en, A_x, inp_ax_dx, out_dx
+	def __init__(self, parent, dbit_n, abit_n, name='mux', latency=10):
+		Obj.__init__(self, parent, name)
+		new_pin_n(abit_n, self, 'A', 0, 'L')
+		DECODER(self, abit_n, 'deco', latency)
+		conn2_n( abit_n, (self, 'A', 0), (self.deco, 'inp', 0) )
+		Pin(self, 'en', 'L').conn = self.deco.en
+
+		n = 1 << abit_n
+		Joint_N(self, n, 'jt_deco_out').new_pin_conn( self.deco, 'out', direc='from_targ' )
+
+		for di in range(dbit_n):
+			aso = ANDSetOr( self, n, name_i('and_set_or', di), latency )
+			self.jt_deco_out.new_pin_conn( aso, 'sel', direc='to_targ' )
+			for i in range(n):
+				nm = name_i( name_i('inp', i), di )
+				Pin(self, nm, 'L').conn = getattr( aso, name_i('inp', i) )
+			aso.out.conn = Pin( self, name_i('out', di), 'L' )
 
 class ROM_D1(Obj):
 	# en, A_x, D_0


# SELECTORのNビット版として「マルチプレクサ」MUXクラスの追加です
# inp_ax_dx 端子は
# ax はアドレスの値
# dx はデータビット番号です


@@ -469,17 +527,23 @@
 	# en, A_x, D_x
 	def __init__(self, parent, dbit_n, abit_n, dlst=(), name='rom', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = dbit_n
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+
+		MUX(self, dbit_n, abit_n, 'mux', latency)
+		Pin(self, 'en', 'L').conn = self.mux.en
 		new_pin_n(abit_n, self, 'A', 0, 'L')
-		Joint_N(self, abit_n, 'jt_n_A').new_pin_conn(self, 'A', 0, abit_n, 'from_targ')
+		conn2_n( abit_n, (self, 'A', 0), (self.mux, 'A', 0) )
+		new_pin_n(dbit_n, self, 'D', 0, 'L')
+		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'D', 0) )
+
+		f = lambda s, v: s + [ (v >> di) & 1 for di in range(dbit_n) ]
+		lst = reduce(f, dlst, [])
+		dbgout(lst)
 
-		for i in range(dbit_n):
-			dlst_i = [ (d >> i) & 1 for d in dlst ]
-			rom_b = ROM_D1(self, abit_n, dlst_i, name_i('rom_b', i), latency)
-			self.jt_en.new_pin().conn = rom_b.en
-			self.jt_n_A.new_pin_conn(rom_b, 'A', 0, abit_n, 'to_targ')
-			rom_b.D_0.conn = Pin( self, name_i('D', i), 'L' )
+		# as late as possible, for data enque
+		n = 1 << abit_n
+		PinSet(self, n * dbit_n, lst, 'pin_set')
+		for i in range(n):
+			conn2_n( dbit_n, (self.pin_set, 'p', i * dbit_n), ( self.mux, name_i('inp', i), 0 ) )
 
 class CLK(Obj):
 	# en, out
$ 

# ROMクラスの実装の変更です
# ROM_D1を使わずにMUXを使うようにしてます
# PinSetには アドレス数 x データビット数 の Pin を連番で用意させて
# データビット数づつ区切って
# MUXの inp_{アドレス}_{データビット} につないでます

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って
# まずは cmd_add.txt そのままで

$ ./nand.py < cmd_add.txt | tee /tmp/aa |sed -e '/ESC/d'
  :

一分待ちしてからクロックスタートなので、 開始が遅いのはまぁ、そうだとして。

足し算の動作自体は正しい結果が表示され。

動き始めると前に増して、なんとなくキビキビ動いてる様子。

初期化にかかる時間をちゃんと見てみます。

$ sed -e '/enque( (60/d' cmd_add.txt | tail
Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.add_4, 'Y', direc='from_targ')
DECODER_Lamp_7seg(o, 'deco_lamp_y', pos=(30,6))
o.jt_y.new_pin_conn(o.deco_lamp_y, 'inp', direc='to_targ')

o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp

sched.enque( (120,0), sched.quit )

# EOF
$ 


# 末尾の方の時刻60秒の enque 行を削除してます
# 今回のパッチを当てる前のソースコード patch -R コマンドで用意しておいて
# -f オプションで「待ち」なしで実行してみます

$ cp nand.py nand.py-23
$ cat nand.py-23-diff.txt | patch -R
patching file nand.py
$ 

$ time sed -e '/enque( (60/d' cmd_add.txt | ./nand.py -f

  :

ESC[14;39H 
0.000000010 o.deco_lamp_y.lamp.lamp_6 L
ESC[15;36H   
0.000000020 o.clk.CLK L
ESC[5;5HCLK

real	1m10.556s
user	1m10.276s
sys	0m0.224s
$ 

# 1分超えてました


# 今回のパッチを当てた後のソースコード nand.py で試すと
$ mv nand.py nand.py-22
$ mv nand.py-23 nand.py
$ time sed -e '/enque( (60/d' cmd_add.txt | ./nand.py -f

  :

ESC[14;39H 
0.000000010 o.deco_lamp_y.lamp.lamp_6 L
ESC[15;36H   
0.000000020 o.clk.CLK L
ESC[5;5HCLK

real	0m22.516s
user	0m22.359s
sys	0m0.120s
$ 

# 1/3程度にv(^_^)


# ということで、初期化待ちしてクロックスタートなどの時刻を変更して実行してみます

$ sed -e 's/enque( (60/enque( (23/' cmd_add.txt | tail                                         
o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp

sched.enque( (23,0), o.clk.en.set, 'H' )
sched.enque( (23,0), o.rom.en.set, 'H' )
sched.enque( (23,0), o.deco_lamp_a.en.set, 'H' )
sched.enque( (23,0), o.deco_lamp_b.en.set, 'H' )
sched.enque( (23,0), o.deco_lamp_y.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 


# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ sed -e 's/enque( (60/enque( (10/' cmd_add.txt | ./nand.py | tee /tmp/aa | sed -e '/ESC/d'
  :

それでも初期化の待ちが長いので、動画は一部省略してます。


表示がバラつくので

どうにも表示がバラついてしまいます。

「ラッチ」を入れてカウンタ出力の変化するタイミングを、 「なるべく」揃えてみます。

nand.py-24-diff.txt

$ cat nand.py-24-diff.txt
--- nand.py-
+++ nand.py
@@ -357,17 +357,17 @@
 		self.jkff.Q.conn = Pin(self, 'Q')
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 
-class DFF_N(Obj):
-	# D_x, CLK, Q_x
-	def __init__(self, parent, n, name='dff_n', latency=10):
+class LATCH(Obj):
+	# inp_x, CLK, out_x
+	def __init__(self, parent, n, name='latch', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
 		Pin(self, 'CLK', 'L').conn = Joint(self, 'jt_clk').new_pin()
 		for i in range(n):
 			dff_i = DFF( self, name_i('dff', i), latency )
 			self.jt_clk.new_pin().conn = dff_i.CLK
-			Pin( self, name_i('D', i), 'L' ).conn = dff_i.D
-			dff_i.Q.conn = Pin( self, name_i('Q', i), 'L' )
+			Pin( self, name_i('inp', i), 'L' ).conn = dff_i.D
+			dff_i.Q.conn = Pin( self, name_i('out', i), 'L' )
 
 class COUNTER(Obj):
 	# CLK, out_x


# 前回、用意だけして未だ使ってないDFFのNビット版
# 名前を DFF_N から LATCH に変更します
# 端子名も D_x, Q_x を inp_x, out_x に変更します

        CLK
         |   +----------+
inp_0 ---|---|D         |
         |   |   D-FF  Q|---- out_0
         @---|CLK       |
         |   +----------+
         |   +----------+
inp_0 ---|---|D         |
         |   |   D-FF  Q|---- out_0
         @---|CLK       |
         |   +----------+
         :        :
         |   +----------+
inp_n-1 -|---|D         |
         |   |   D-FF  Q|---- out_n-1
         .---|CLK       |
             +----------+

         LATCHクラスの構成



# ついでに
# 前回、用意だけして未だ使ってないGATEクラス


en ----------------------@--------.
                         |        |
           +---------------------------+
           |           sel_0..sel_d-1  |
           |                           |
inp_0 -----|inp_0    ANDSet       out_0|---- out_0
inp_1 -----|inp_1    n bit        out_1|---- out_1
  :        |                           |      :
inp_n-1 ---|inp_n-1             out_n-1|---- out_n-1
           +---------------------------+

                 GATEクラスの構成



@@ -546,22 +546,29 @@
 			conn2_n( dbit_n, (self.pin_set, 'p', i * dbit_n), ( self.mux, name_i('inp', i), 0 ) )
 
 class CLK(Obj):
-	# en, out
-	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None):
+	# en, out, Q, nQ
+	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None, latency=10):
 		Obj.__init__(self, parent, name, None, pos)
-		latency = tm_from_fsec( 1.0 / ( 2 * hz ) )
-		NAND(self, 'nand', latency)
+		late = tm_from_fsec( 1.0 / ( 2 * hz ) )
+		NAND(self, 'nand', late)
 		Joint(self)
-		AND(self, 'and_')
+		NOT(self, 'not_', latency)
 
 		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
 		self.jt_en.new_pin().conn = self.nand.inp_a
 		self.nand.out.conn = self.jt.new_pin()
 		self.jt.new_pin().conn = self.nand.inp_b
-		self.jt.new_pin().conn = self.and_.inp_b
-		self.jt_en.new_pin().conn = self.and_.inp_a
-		self.and_.out.conn = Joint(self, 'jt_out').new_pin()
+		self.jt.new_pin().conn = self.not_.inp
+
+		GATE(self, 2, 'gate', latency)
+		self.jt_en.new_pin().conn = self.gate.en
+		self.jt.new_pin().conn = self.gate.inp_0
+		self.not_.out.conn = self.gate.inp_1
+
+		self.gate.out_1.conn = Pin(self, 'nQ')
+		self.gate.out_0.conn = Joint(self, 'jt_out').new_pin()
 		self.jt_out.new_pin().conn = Pin(self, 'out')
+		self.jt_out.new_pin().conn = Pin(self, 'Q')
 		if lamp_name:
 			self.jt_out.new_pin().conn = Lamp(self, lamp_name).inp
 
 

# 少し整理して、
# クロックの反転出力というか1/2周期遅らせた出力を追加です

#	 .----------------.
#	 |    +----+      |            [ Lamp ]
#	 .----|     \     |    +----+     |
#	      |      |O---@----|     \    |
#  en ---@----|     /          |      |---@--- out
#	 |    +----+      .----|     /
#	 |                |    +----+
#	 .----------------.
#
#              以前の構成


#                                                 [ Lamp ]
#        .----------------.        +------------+    |
#        |    +----+      |        |  GATE 2bit |    | .--- out
#        .----|     \     |        |            |    |/
#             |      |O---@--------|inp_0  out_0|----@----- Q
#  en ---@----|     /     |        |            |
#        |    +----+      .---|>o--|inp_1  out_1|---------- nQ
#        |                         |            |
#        .-------------------------|en          |
#                                  +------------+
#
#              新しい構成


@@ -717,6 +724,11 @@
 		self.que = []
 		self.now = tm_tup(0)
 	
+	def enque_just(self, f, *args):
+		d = time.time() - self.start - tm_fsec( self.now )
+		tm = tm_from_fsec( d if d > 0 else 0 )
+		self.enque(tm, f, *args)
+
 	def enque(self, latency, f, *args):
 		if len(args) == 1 and type( args[0] ) == tuple:
 			args = args[0]


# スケジューラに enque_just() メソッド追加
# 呼び出しがあった時点の現実世界の時刻で、enque() します
# (スケジューラスタートしてからの現実世界の経過時刻)


@@ -727,6 +739,8 @@
 
 		if self.exist_same(t):
 			return
+		for a in self.get_same_tm_f(t):
+			self.que.remove(a)
 
 		n = len(self.que)
 		while n > 0:

# これまでスケジューラの enque() では
# 同一時刻に同一メソッドを同一の引数で呼び出す登録は
# 意味が無いのではねてました

# 今回の追加では
# 同一時刻に同一メソッドで引数が異なるものを登録しようとした場合、
# 先にキューに登録された方を削除して、
# 後から登録しようとした引数が異なる方が、
# 上書きするよう登録します

# この追加の「ねらい」とは

# 底のNANDクラスの更新処理では、
# 出力端子に値をセットするための登録がなされます

# 2つの入力端子が同時に変化したとしても
# シングルスレッドだし、呼び出しは片方づつ処理されます
# ここで出力端子を'H'にする登録と、'L'にする登録が
# 同じ時刻になされるかも知れません

# 一瞬'H'になってすぐ'L'になると
# 'H'の処理も律義に実行された後、
# 本来の'L'の処理

# こういう無駄な処理を削減します
# 本当に必要な'H','L'のパルスの変化は、同じ時刻には登録されないはずです


@@ -739,9 +753,13 @@
 	def exist_same(self, t):
 		return next( ( True for a in self.que if a == t ), False )
 
+	def get_same_tm_f(self, t):
+		same_tm_f = lambda (a_tm, a_f, a_a), (t_tm, t_f, t_a): a_tm == t_tm and a_f == t_f
+		return [ a for a in self.que if same_tm_f(a, t) ]
+

# 先の
# 同一時刻に同一メソッドで引数が異なるものを登録しようとした場合
# の処理から呼び出すメソッドです
# キューに登録されている中から、
# 引数指定と、同一時刻で同一メソッドの一覧を返します


 	def main_loop(self):
 		self.run = True
-		start = time.time()
+		self.start = time.time()
 		while self.run:
 			cmd_exec( sys.stdin )
 			if self.que:
@@ -749,7 +767,7 @@
 				(tm, f, args) = self.que.pop(0)
 				self.now = tm
 				if '-f' not in sys.argv:
-					tm_sleep(tm, start)
+					tm_sleep(tm, self.start)
 				f(*args)
 			else:
 				time.sleep(0.5)
$ 

# 追加した enque_just()メソッドのための変更です
# 開始時刻 start を、インスタンス変数として保持するように変更
# enque_just()メソッドから参照できるようにしてます

続いて確認用のお試しコマンド。

cmd_add_latch.txt

$ cat cmd_add_latch.txt
o.pos = (4,4)

CLK(o, 0.2, 'clk', (0,0), 'CLK').Q.conn = COUNTER(o, 3, 'cnt').CLK

LATCH(o, 3, 'latch')
conn2_n( 3, (o.cnt, 'out', 0), (o.latch, 'inp', 0) )
o.clk.nQ.conn = o.latch.CLK

o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]
ROM(o, 8, 3, o.dlst, 'rom')
conn2_n( 3, (o.latch, 'out', 0), (o.rom, 'A', 0) )

Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.rom, 'D', 4, direc='from_targ')
Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.rom, 'D', direc='from_targ')

DECODER_Lamp_7seg(o, 'deco_lamp_a', pos=(10,6))
DECODER_Lamp_7seg(o, 'deco_lamp_b', pos=(20,6))
o.jt_a.new_pin_conn(o.deco_lamp_a, 'inp', direc='to_targ')
o.jt_b.new_pin_conn(o.deco_lamp_b, 'inp', direc='to_targ')

ADD_N(o, 4, 'add_4')
o.jt_a.new_pin_conn(o.add_4, 'A', direc='to_targ')
o.jt_b.new_pin_conn(o.add_4, 'B', direc='to_targ')

Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.add_4, 'Y', direc='from_targ')
DECODER_Lamp_7seg(o, 'deco_lamp_y', pos=(30,6))
o.jt_y.new_pin_conn(o.deco_lamp_y, 'inp', direc='to_targ')

o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp

sched.enque( (60,0), o.clk.en.set, 'H' )
sched.enque( (60,0), o.rom.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_a.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_b.en.set, 'H' )
sched.enque( (60,0), o.deco_lamp_y.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 

$ diff -u -Lold -Lnew cmd_add.txt cmd_add_latch.txt 
--- old
+++ new
@@ -1,10 +1,14 @@
 o.pos = (4,4)
 
-CLK(o, 0.2, 'clk', (0,0), 'CLK').out.conn = COUNTER(o, 3, 'cnt').CLK
+CLK(o, 0.2, 'clk', (0,0), 'CLK').Q.conn = COUNTER(o, 3, 'cnt').CLK
+
+LATCH(o, 3, 'latch')
+conn2_n( 3, (o.cnt, 'out', 0), (o.latch, 'inp', 0) )
+o.clk.nQ.conn = o.latch.CLK
 
 o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]
 ROM(o, 8, 3, o.dlst, 'rom')
-conn2_n( 3, (o.cnt, 'out', 0), (o.rom, 'A', 0) )
+conn2_n( 3, (o.latch, 'out', 0), (o.rom, 'A', 0) )
 
 Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.rom, 'D', 4, direc='from_targ')
 Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.rom, 'D', direc='from_targ')
$ 


# CLKの出力端子名を追加した方の'Q'に変えてみつつ

# カウンタの出力をラッチに入れて
# ラッチのCLKにはクロックの反転出力'nQ'をつなぎます

# ROMのアドレス端子には、カウンタの出力ではなく
# ラッチの出力をつなぎます

それでは実行してみます。


# まずラッチを使わない方で
# カウンタの出力のタイミングのバラつき具合を確認

$ ./nand.py -f -v < cmd_add.txt | grep cnt.out  
0.000000000 o.cnt.out_0 L
0.000000000 o.cnt.out_1 L
0.000000000 o.cnt.out_2 L
60.000000060 o.cnt.out_0 H
65.000000070 o.cnt.out_0 L
65.000000100 o.cnt.out_1 H
70.000000060 o.cnt.out_0 H
75.000000070 o.cnt.out_0 L
75.000000110 o.cnt.out_1 L
75.000000140 o.cnt.out_2 H
80.000000060 o.cnt.out_0 H
85.000000070 o.cnt.out_0 L
85.000000100 o.cnt.out_1 H
90.000000060 o.cnt.out_0 H
95.000000070 o.cnt.out_0 L
95.000000110 o.cnt.out_1 L
95.000000150 o.cnt.out_2 L
100.000000060 o.cnt.out_0 H
105.000000070 o.cnt.out_0 L
105.000000100 o.cnt.out_1 H
110.000000060 o.cnt.out_0 H
115.000000070 o.cnt.out_0 L
115.000000110 o.cnt.out_1 L
115.000000140 o.cnt.out_2 H
$ 

# とくに
# 95.000000070 o.cnt.out_0 L
# 95.000000110 o.cnt.out_1 L
# 95.000000150 o.cnt.out_2 L
# このあたり


# そしてラッチありの場合で
# ラッチの出力が変化するタイミングやいかに?

$ ./nand.py -f -v < cmd_add_latch.txt | grep latch.out
0.000000000 o.latch.out_0 L
0.000000000 o.latch.out_1 L
0.000000000 o.latch.out_2 L
62.500000070 o.latch.out_0 H
67.500000070 o.latch.out_1 H
67.500000080 o.latch.out_0 L
72.500000070 o.latch.out_0 H
77.500000070 o.latch.out_2 H
77.500000080 o.latch.out_0 L
77.500000080 o.latch.out_1 L
82.500000070 o.latch.out_0 H
87.500000070 o.latch.out_1 H
87.500000080 o.latch.out_0 L
92.500000070 o.latch.out_0 H
97.500000080 o.latch.out_0 L
97.500000080 o.latch.out_1 L
97.500000080 o.latch.out_2 L
102.500000070 o.latch.out_0 H
107.500000070 o.latch.out_1 H
107.500000080 o.latch.out_0 L
112.500000070 o.latch.out_0 H
117.500000070 o.latch.out_2 H
117.500000080 o.latch.out_0 L
117.500000080 o.latch.out_1 L
$ 


# カウンタにはクロック出力'Q'を、
# クロック出力'nQ'をラッチに入れてるので

# クロック'Q'の立上りで、
# カウンタ出力が更新されはじめ
# やがてバタついておさまり

# クロック'Q'の立ち下がりで
# カウンタ出力をラッチで取り込み
# ROMのアドレスとして出力します

# 0.2Hz で周期5秒なので、
# ROMへのアドレスが変化するタイミングは
# ラッチありの場合2.5秒ずれます

# しかしながら、例えば先の「このあたり」
# の該当箇所では

# 97.500000080 o.latch.out_0 L
# 97.500000080 o.latch.out_1 L
# 97.500000080 o.latch.out_2 L

# 時刻が揃ってくれました

しかしながら、時刻の末尾は70ナノと80ナノの2種類あります。

「ラッチ」といえど中身は「D-FF」。そしてその中身は「JKFF」。

そして「JKFF」の出力側は「RSFF」。


# 例えば

        +----+
R --H---|     \
        |      |O---@--L--- Q
   .----|     /     |
   |    +----+      |
   .-H--    --------.
         \ /
          X
         / \
   .----    --------.
   |    +----+      |
   .-L--|     \     |
        |      |O---@--H--- nQ
S --L---|     /
        +----+

# この状態で安定してるところから...


# 入力のR,Sの値が、同時に反転したとすると
# (変化した箇所に'!'をつけてます)

    !   +----+
R --L---|     \
        |      |O---@--L--- Q
   .----|     /     |
   |    +----+      |
   .-H--    --------.
         \ /
          X
         / \
   .----    --------.
   |    +----+      |
   .-L--|     \     |
        |      |O---@--H--- nQ
S --H---|     /
    !   +----+


# latencyを10ナノ秒とすると
# この入力の変化を受けて
# 10ナノ秒後の状態は

        +----+
R --L---|     \        !
        |      |O---@--H--- Q
   .----|     /     |
   |    +----+      |
   .-H--    --------.
         \ /
          X
         / \
   .----    --------.
   | !  +----+      |
   .-H--|     \     |
        |      |O---@--H--- nQ
S --H---|     /
        +----+

# まず Q 側だけが変化します
# この時点で nQ 側の入力がやっと'H','H'で揃い
# さらに10ナノ秒後に

        +----+
R --L---|     \
        |      |O---@--H--- Q
   .----|     /     |
   |    +----+      |
   .-L--    --------.
     !   \ /
          X
         / \
   .----    --------.
   |    +----+      |
   .-H--|     \     |  !
        |      |O---@--L--- nQ
S --H---|     /
        +----+

# nQ 側が変化して
# 安定した状態に落ち着きます

# RSFFを遅延ありのNANDで構成してるので、
# このQとnQのタイミングの差は、
# しょうがないということで...

初期化に1分待ちのままでありますが、実際に走らせてみると、、、

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_add_latch.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

若干「まし」ではあります。

足し算への入力の「ジョイント」の時点の'A_x'の変化から、 7セグ・デコーダを経ての7セグ・ランプの変化までが、 バタつきで処理がおっつか無い感がにじんでます。

せっかくなので随所に「ラッチ」をかましてみましょう。

cmd_add_latch2.txt

$ cat cmd_add_latch2.txt
o.pos = (4,4)

CLK(o, 0.5, 'clk', (0,0), 'CLK').Q.conn = Joint(o, 'jt_clk_Q').new_pin()
o.clk.nQ.conn = Joint(o, 'jt_clk_nQ').new_pin()

o.jt_clk_Q.new_pin().conn = COUNTER(o, 3, 'cnt').CLK

LATCH(o, 3, 'latch')
conn2_n( 3, (o.cnt, 'out', 0), (o.latch, 'inp', 0) )
o.jt_clk_nQ.new_pin().conn = o.latch.CLK

o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]
ROM(o, 8, 3, o.dlst, 'rom')
conn2_n( 3, (o.latch, 'out', 0), (o.rom, 'A', 0) )

LATCH(o, 8, 'latch_rom_out')
conn2_n( 8, (o.rom, 'D', 0), (o.latch_rom_out, 'inp', 0) )
o.jt_clk_Q.new_pin().conn = o.latch_rom_out.CLK

Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.latch_rom_out, 'out', 4, direc='from_targ')
Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.latch_rom_out, 'out', direc='from_targ')

DECODER_Lamp_7seg(o, 'deco_lamp_a', pos=(10,6))
DECODER_Lamp_7seg(o, 'deco_lamp_b', pos=(20,6))
o.jt_a.new_pin_conn(o.deco_lamp_a, 'inp', direc='to_targ')
o.jt_b.new_pin_conn(o.deco_lamp_b, 'inp', direc='to_targ')

ADD_N(o, 4, 'add_4')
o.jt_a.new_pin_conn(o.add_4, 'A', direc='to_targ')
o.jt_b.new_pin_conn(o.add_4, 'B', direc='to_targ')

LATCH(o, 4, 'latch_add_out')
conn2_n( 4, (o.add_4, 'Y', 0), (o.latch_add_out, 'inp', 0) )
o.jt_clk_nQ.new_pin().conn = o.latch_add_out.CLK

Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.latch_add_out, 'out', direc='from_targ')
DECODER_Lamp_7seg(o, 'deco_lamp_y', pos=(30,6))
o.jt_y.new_pin_conn(o.deco_lamp_y, 'inp', direc='to_targ')

o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp

sched.enque( (1,1000), sched.enque_just, o.clk.en.set, 'H' )
sched.enque( (1,1001), sched.enque_just, o.rom.en.set, 'H' )
sched.enque( (1,1002), sched.enque_just, o.deco_lamp_a.en.set, 'H' )
sched.enque( (1,1003), sched.enque_just, o.deco_lamp_b.en.set, 'H' )
sched.enque( (1,1004), sched.enque_just, o.deco_lamp_y.en.set, 'H' )

sched.enque( (120,0), sched.quit )

# EOF
$ 



$ diff -u -Lold -Lnew cmd_add_latch.txt cmd_add_latch2.txt
--- old
+++ new
@@ -1,17 +1,24 @@
 o.pos = (4,4)
 
-CLK(o, 0.2, 'clk', (0,0), 'CLK').Q.conn = COUNTER(o, 3, 'cnt').CLK
+CLK(o, 0.5, 'clk', (0,0), 'CLK').Q.conn = Joint(o, 'jt_clk_Q').new_pin()
+o.clk.nQ.conn = Joint(o, 'jt_clk_nQ').new_pin()
+
+o.jt_clk_Q.new_pin().conn = COUNTER(o, 3, 'cnt').CLK
 
 LATCH(o, 3, 'latch')
 conn2_n( 3, (o.cnt, 'out', 0), (o.latch, 'inp', 0) )
-o.clk.nQ.conn = o.latch.CLK
+o.jt_clk_nQ.new_pin().conn = o.latch.CLK
 
 o.dlst = [ 0x00, 0x11, 0x23, 0x45, 0x67, 0xF1, 0xF2, 0xFF ]
 ROM(o, 8, 3, o.dlst, 'rom')
 conn2_n( 3, (o.latch, 'out', 0), (o.rom, 'A', 0) )
 

# クロックを 0.2Hzから0.5Hzに変更です
# クロックの'Q'はカウンタ以外にも入れたいので
# ジョイント'jt_clk_Q'を経由してから、カウンタへ


-Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.rom, 'D', 4, direc='from_targ')
-Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.rom, 'D', direc='from_targ')
+LATCH(o, 8, 'latch_rom_out')
+conn2_n( 8, (o.rom, 'D', 0), (o.latch_rom_out, 'inp', 0) )
+o.jt_clk_Q.new_pin().conn = o.latch_rom_out.CLK
+
+Joint_N(o, 4, 'jt_a', None, (10,0), ('y',1), 'A').new_pin_conn(o.latch_rom_out, 'out', 4, direc='from_targ')
+Joint_N(o, 4, 'jt_b', None, (20,0), ('y',1), 'B').new_pin_conn(o.latch_rom_out, 'out', direc='from_targ')
 
 DECODER_Lamp_7seg(o, 'deco_lamp_a', pos=(10,6))
 DECODER_Lamp_7seg(o, 'deco_lamp_b', pos=(20,6))

# ROM出力をジョイント jt_a, jt_b に入れてましたが
# ラッチ'latch_rom_out'へ入れるように変更してます
# ラッチ'latch_rom_out'の出力をjt_a, jt_bに入れるように変更します
# ラッチ'latch_rom_out'のクロックにはクロックの'Q'のジョイントからつなぎます


@@ -22,17 +29,22 @@
 o.jt_a.new_pin_conn(o.add_4, 'A', direc='to_targ')
 o.jt_b.new_pin_conn(o.add_4, 'B', direc='to_targ')
 
-Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.add_4, 'Y', direc='from_targ')
+LATCH(o, 4, 'latch_add_out')
+conn2_n( 4, (o.add_4, 'Y', 0), (o.latch_add_out, 'inp', 0) )
+o.jt_clk_nQ.new_pin().conn = o.latch_add_out.CLK
+
+Joint_N(o, 4, 'jt_y', None, (30,0), ('y',1), 'Y').new_pin_conn(o.latch_add_out, 'out', direc='from_targ')
 DECODER_Lamp_7seg(o, 'deco_lamp_y', pos=(30,6))
 o.jt_y.new_pin_conn(o.deco_lamp_y, 'inp', direc='to_targ')
 
 o.add_4.C.conn = Lamp(o, 'C', None, (40,0)).inp
 
# 足し算の出力をジョイント jt_y に入れてましたが
# ラッチ'latch_add_out'へ入れるように変更してます
# ラッチ'latch_add_out'の出力をjt_yに入れるように変更します
# ラッチ'latch_add_out'のクロックにはクロックの'nQ'のジョイントからつなぎます


-sched.enque( (60,0), o.clk.en.set, 'H' )
-sched.enque( (60,0), o.rom.en.set, 'H' )
-sched.enque( (60,0), o.deco_lamp_a.en.set, 'H' )
-sched.enque( (60,0), o.deco_lamp_b.en.set, 'H' )
-sched.enque( (60,0), o.deco_lamp_y.en.set, 'H' )
+sched.enque( (1,1000), sched.enque_just, o.clk.en.set, 'H' )
+sched.enque( (1,1001), sched.enque_just, o.rom.en.set, 'H' )
+sched.enque( (1,1002), sched.enque_just, o.deco_lamp_a.en.set, 'H' )
+sched.enque( (1,1003), sched.enque_just, o.deco_lamp_b.en.set, 'H' )
+sched.enque( (1,1004), sched.enque_just, o.deco_lamp_y.en.set, 'H' )
+
 sched.enque( (120,0), sched.quit )
 
 # EOF
$ 

# 初期化が終ってから、クロックなどのen端子を'H'にしたいところですが
# 登録する時刻が長過ぎると、表示が出るまでに無駄な「待ち」になります

# そこで今回スケジューラに追加した enque_just() を使って変更してます

# まず、この箇所が標準入力から読み込まれて実行されるのは、
# スケジューラのメインループの実行が開始された直後で、
# スケジューラの保持してるself.nowの時刻は(0,0)の段階です

# ここに至る前に、部品の生成や、端子の初期化などがなされるので
# スケジューラには時刻(0,0)や、(0,10)などの登録がなされた直後です

# latency 10ナノ秒で初期化処理が進行していって
# 時刻1秒にはクロックのスタート待ちに落ち着くでしょう

# ということで、時刻1秒に(正確には時刻0秒の時点から1秒後に)
# スケジューラの enque_just() メソッドを呼び出すように登録してます
# enque_just() に引数には、
# 端子のセット用のメソッドと、設定する値'H'

# これで時刻1秒になると
# スケジューラに残ってる登録は
# 1秒付近の enque_just と 120秒後の quit だけになってるはずです

# やがて enque_just() の呼び出しが実行されて

# その時点の現実世界の時刻から換算したスケジューラの時刻を指定して
# sched.enque( ({換算した時刻}), 端子のセットメソッド, 設定する値'H' )
# が実行されて

# 端子メソッドの呼び出しがスケジューラに登録されます

# これで現実世界の時刻と同期して、
# 端子のセットメソッドが、ほぼ「待ち」無く実行されるはずです

# 1秒の付近で
# (1, 1000)
# (1, 1001)
# (1, 1002)
# (1, 1003)
# (1, 1004)
# とバラけさせてるのは、
# 今回スケジューラに入れた「もう一つの追加」の事情からです

# 「もう一つの追加」とは、端子への値のセットで無駄を省くために追加した

#     同一時刻に同一メソッドで引数が異なるものを登録しようとした場合、
#     先にキューに登録された方を削除して、
#     後から登録しようとした引数が異なる方が、
#     上書きするよう登録します

# てなヤツです
# 同じ時刻にしてしまうと
# 指定のメソッドは全て sched.enque_just で同一なので
# 最後の登録以外は、上書きで消されてしまいます

それでは実行してみます。

# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_add_latch2.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

クロックの立ち上がりで入力A,Bが更新され、 クロックの立ち下がりで足し算の結果が更新されてます。(^_^v

これまで作ってきた部品の概略を部品一覧にまとめてみました。

なるべく随時更新していきます。


ROMとくればRAM

ROMがそこそこ動いたので次なる一手は、やはりRAMでしょう。

ROMの構成は

                   +-------------------+
en ----------------|en                 |
                   |        MUX        |
A_0 ---------------|A_0           out_0|---- D_0
A_1 ---------------|A_1           out_1|---- D_1
  :                | :              :  |
A_an-1 ------------|A_an-1     out_dn-1|---- D_dn-1
                   |                   |
+--------------+   |                   |
|           p_0|---|inp_0_0            |
| PinSet    p_1|---|inp_0_1            |
|              | : | :                 |
|        p_dn-1|---|inp_0_dn-1         |
|          p_dn|---|inp_1_0            |
|        p_dn+1|---|inp_1_1            |
|              | : | :                 |
|      p_dn*N-1|---|inp_N-1_dn-1       |
+--------------+   +-------------------+

こんなのでした。

端子の集合「PinSet」のところを、書き込める仕組みに付け替えれば、 出力側はこのままで良さげです。

書き換えるというか、状態を保たせつつ変更も可能。 つまりDフリップフロップ。もっと言うとラッチ。

アドレスの分だけラッチをつないでゴニョゴニョすれば何とかなりそうです。


まずは素直に作ってみたところ

D-FFをデータビット個分集めて、ラッチクラスにしてました。

となると、まずはラッチがアドレスの数だけ必要です。

LATCHクラスをN個まとめたLATCH_Nクラスを用意しておきます。

各ラッチの入力側は並列につないでおいて、 CLKをアドレスで指定されてる1つのラッチにだけ与えれば、 クロックの立ち上がりで、アドレス指定のラッチだけが更新されるはず。

アドレスの数だけあるCLK端子をどうしたものか?

そもそもアドレスの指定は?

ROMの場合、アドレス指定ビットはMUXクラスに入れて、MUXの中のデコーダを使ってました。

リードもライトも同じアドレス指定の仕組みを共用して問題ないでしょう。 MUXにデコード結果の出力をこっそり追加して、それを使うことにします。;-p)

出力側のMUXクラスから引っ張ってきたデコード結果を、 ANDSetクラスのsel_x端子に入れて、、、

ANDSetクラスの入力側は全部ジョイントでまとめて外部からの書き込み用の信号を与えておきます。

ANDSetクラスの出力側を、各ラッチのCLK端子につなげば、 アドレス指定されてるラッチにだけ、書き込み信号がCLK端子に入ります。

アドレス個分 x ラッチのデータビット数の出力は、そのままMUXの入力へ。

この方針で行ってみましょう。

nand.py-25-diff.txt

$ cat nand.py-25-diff.txt
--- nand.py-
+++ nand.py
@@ -369,6 +369,28 @@
 			Pin( self, name_i('inp', i), 'L' ).conn = dff_i.D
 			dff_i.Q.conn = Pin( self, name_i('out', i), 'L' )
 
+class LATCH_N(Obj):
+	# inp_dx, CLK, sel_x, out_x_dx
+	def __init__(self, parent, n, dbit_n, name='latch_n', latency=10):
+		Obj.__init__(self, parent, name)
+
+		JointANDSet(self, n, 'jas', latency)
+		Pin(self, 'CLK', 'L').conn = self.jas.inp
+		new_pin_n(n, self, 'sel', 0, 'L')
+		conn2_n( n, (self, 'sel', 0), (self.jas, 'sel', 0) )
+
+		new_pin_n(dbit_n, self, 'inp', 0, 'L')
+		Joint_N(self, dbit_n, 'jt_inp', latency)
+		self.jt_inp.new_pin_conn(self, 'inp', direc='from_targ')
+
+		for i in range(n):
+			lt = LATCH(self, dbit_n, name_i('latch', i), latency)
+			getattr( self.jas, name_i('out', i) ).conn = lt.CLK
+			self.jt_inp.new_pin_conn(lt, 'inp', direc='to_targ')
+
+			new_pin_n( dbit_n, self, name_i('out', i), 0, 'L' )
+			conn2_n( dbit_n, (lt, 'out', 0), (self, name_i('out', i), 0) )
+
 class COUNTER(Obj):
 	# CLK, out_x
 	def __init__(self, parent, bit_n, name='counter', latency=10):


# まずはLATCH_Nクラスを追加
# ANDSetクラスの入力側をJointで1つにまとめただけのJointANDSetクラスを使ってます
# 他は、先の説明のまま、素直につないでるだけです


@@ -451,6 +473,20 @@
 		OR_N(self, n, 'or_n', latency).out.conn = Pin(self, 'out', 'L')
 		conn2_n( n, (self.and_set, 'out', 0), (self.or_n, 'inp', 0) )
 
+class JointANDSet(Obj):
+	# inp, sel_x, out_x
+	def __init__(self, parent, n, name='joint_and_set', latency=10):
+		Obj.__init__(self, parent, name)
+		Pin(self, 'inp', 'L').conn = Joint(self, 'jt').new_pin()
+
+		ANDSet(self, n, 'and_set', latency)
+		self.jt.new_pin_conn_n( n, self.and_set, 'inp', 0 )
+
+		new_pin_n(n, self, 'sel', 0, 'L')
+		conn2_n( n, (self, 'sel', 0), (self.and_set, 'sel', 0) )
+		new_pin_n(n, self, 'out', 0, 'L')
+		conn2_n( n, (self.and_set, 'out', 0), (self, 'out', 0) )
+
 class GATE(Obj):
 	# inp_x, en, out_x
 	def __init__(self, parent, n, name='gate', latency=10):


# ANDSetの入力側をJointで1つにまとめただけのJoinANDSetです
# じつは端子名が違うだけで、やってる事はGATEクラスと同じです


@@ -485,7 +521,7 @@
 		self.and_set_or.out.conn = Pin(self, 'out', 'L')
 
 class MUX(Obj):
-	# en, A_x, inp_ax_dx, out_dx
+	# en, A_x, inp_ax_dx, out_dx, deco_out_ax
 	def __init__(self, parent, dbit_n, abit_n, name='mux', latency=10):
 		Obj.__init__(self, parent, name)
 		new_pin_n(abit_n, self, 'A', 0, 'L')
@@ -496,6 +532,9 @@
 		n = 1 << abit_n
 		Joint_N(self, n, 'jt_deco_out').new_pin_conn( self.deco, 'out', direc='from_targ' )
 
+		new_pin_n(n, self, 'deco_out', 0, 'L')
+		self.jt_deco_out.new_pin_conn( self, 'deco_out', direc='to_targ' )
+
 		for di in range(dbit_n):
 			aso = ANDSetOr( self, n, name_i('and_set_or', di), latency )
 			self.jt_deco_out.new_pin_conn( aso, 'sel', direc='to_targ' )


# MUXクラスの内部のデコーダの出力を、
# 端子 deco_out_0, deco_out_1, .. , deco_out_n-1 に出力するように端子を追加してます


@@ -545,6 +584,28 @@
 		for i in range(n):
 			conn2_n( dbit_n, (self.pin_set, 'p', i * dbit_n), ( self.mux, name_i('inp', i), 0 ) )
 
+class RAM(Obj):
+	# en, CLK, A_x, D_x
+	def __init__(self, parent, dbit_n, abit_n, name='ram', latency=10):
+		Obj.__init__(self, parent, name)
+
+		n = 1 << abit_n
+		LATCH_N(self, n, dbit_n, 'latch_n', latency)
+		Pin(self, 'CLK', 'L').conn = self.latch_n.CLK
+		new_pin_n( dbit_n, self, 'inp', 0, 'L' )
+		conn2_n( dbit_n, (self, 'inp', 0), (self.latch_n, 'inp', 0) )
+
+		MUX(self, dbit_n, abit_n, 'mux', latency)
+		Pin(self, 'en', 'L').conn = self.mux.en
+		new_pin_n( abit_n, self, 'A', 0, 'L' )
+		conn2_n( abit_n, (self, 'A', 0), (self.mux, 'A', 0) )
+		new_pin_n( dbit_n, self, 'out', 0, 'L' )
+		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'out', 0) )
+
+		conn2_n( n, (self.mux, 'deco_out', 0), (self.latch_n, 'sel', 0) )
+		for i in range(n):
+			conn2_n( dbit_n, (self.latch_n, name_i('out', i), 0), (self.mux, name_i('inp', i), 0) )
+
 class CLK(Obj):
 	# en, out, Q, nQ
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None, latency=10):
$ 


# 最後にRAM御本尊です
# LATCH_N と MUX をつないで、端子を取り出してできあがり

さてコーディングしたものの、どうやって動作確認しようか、、、


どやって試すべしか

'en'端子に'H'を入れて内部のデコーダを有効にしてから、 アドレスを指定してデータを指定して、CLK端子を'L'から'H'に立ち上げると、書き込めるはずです。

出力側は'en'端子が'H'の間は、アドレスで指定された「ラッチ」の値が、常に出力されてるはず。

ならばROMを使って、RAMの各端子にそのような出力を与えてやればいいのかな?

まずは、クロックとカウンタとROMをつないで、ROMの内容を順に出力するだけの部品をまとめておいて、 ROM_SEQクラスとします。

            Q
            |
            |
            @---------------------------------------------------------------------.
            |                                                                     |
+--------+  |   +----------+   +------------------+   +--------------+   +------------------+
|       Q|--@---|CLK  out_0|---|inp_0        out_0|---|A_0        D_0|---|inp_0  CLK   out_0|--- out_0
|        |      |     out_1|---|inp_1        out_1|---|A_1        D_1|---|inp_1        out_1|--- out_1
|  CLK   |      |          | : |                  | : |              | : |                  | :
|        |      | COUNTER  |   |      LATCH       |   |     ROM      |   |      LATCH       |
|        |      | an bit   |   |      an bit      |   |              |   |      dn bit      |
|        |      |          | : |                  | : |              | : |                  | :
|        |      |  out_an-1|---|inp_an-1  out_an-1|---|A_an-1  D_dn-1|---|inp_dn_1  out_dn-1|--- out_dn-1
|        |      +----------+   |        CLK       |   |      en      |   |                  |
|        |                     +------------------+   +--------------+   +------------------+
|        |                               |                   |
|      nQ|-------------------------------@                   |
|  en    |                               |                   |
+--------+                               |                   |
   |                                     |                   |
   @---------------------------------------------------------.
   |                                     |
   |                                     |
   en                                    nQ

これにRAMをつないでみます。

  en
  |
  @---------------------@-----------------.
  |                     |                 |
+------------+       +-------------+   +-------+
| en         |       |  en         |   |  en   |
|            |       |             |   |       |
|            |       |             |   |  ---  |
|            |       |             |   | |   | |
|            |       |             |   |  ---  |
| ROM_SEQ    |       | RAM         |   | |   | |
| 8 bit      |       | 4 bit       |   |  ---  |
|            |       |             |   |       |
|       out_0|-------|inp_0   out_0|---|inp_0  |
|       out_1|-------|inp_1   out_1|---|inp_1  |
|       out_2|-------|inp_2   out_2|---|inp_2  |
|       out_3|-------|inp_3   out_3|---|inp_3  |
|            |       |             |   |       |
|       out_4|-------|A_0          |   |       |
|       out_5|-------|A_1          |   |       |
|       out_6|-------|A_2          |   |       |
|            |       |        CLK  |   |       |
|            |       +-------------+   +-------+
|            |                 |
|            |       +---+     |
|       out_7|-------|    \    |
|            |       |     |---.
|          nQ|-------|    /
|            |       +---+
|            |
+------------+

RAMはデータ幅4ビット、アドレス空間3ビットで、出力側には7セグのデコーダとランプのセットを。

ROMのデータビット幅は8ビット。 出力の下位4ビットはRAMのデータ入力へ。ビット4,5,6はRAMのアドレスビットへ。

最上位ビットはROMを読み出すクロックを反転したものとANDをとって、RAMのCLKに入れます。 つまり、ROM出力データの最上位ビットが書き込み制御用の指定になります。

クロックの立ち上がりのタイミングで、ROMからの出力が更新されるので、 ROM出力が更新されてから、次の更新までの中間地点のタイミングで、 データ出力が安定してる「スキ」に書き込みます。

テスト用のモジュールはコマンドファイルで用意してきましたが、 今回のはそれなりに使いでがありそうなので、 RAM_TEST_4クラスとしてクラス定義しておきます。

nand.py-26-diff.txt

$ cat nand.py-26-diff.txt
--- nand.py-
+++ nand.py
@@ -584,6 +584,37 @@
 		for i in range(n):
 			conn2_n( dbit_n, (self.pin_set, 'p', i * dbit_n), ( self.mux, name_i('inp', i), 0 ) )
 
+class ROM_SEQ(Obj):
+	# en, out_x, Q, nQ
+	def __init__(self, parent, dbit_n, abit_n, dlst=(), name='rom_seq', hz=1.0, pos=(0,0), lamp_name=None, latency=10):
+		Obj.__init__(self, parent, name, None, pos)
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+
+		CLK(self, hz, 'clk', (0,0), lamp_name, latency)
+		self.jt_en.new_pin().conn = self.clk.en
+		self.clk.Q.conn = Joint(self, 'jt_Q').new_pin()
+		self.jt_Q.new_pin().conn = Pin(self, 'Q', 'L')
+		self.clk.nQ.conn = Joint(self, 'jt_nQ').new_pin()
+		self.jt_nQ.new_pin().conn = Pin(self, 'nQ', 'L')
+
+		COUNTER(self, abit_n, 'counter', latency)
+		self.jt_Q.new_pin().conn = self.counter.CLK
+
+		LATCH(self, abit_n, 'latch_a', latency)
+		conn2_n( abit_n, (self.counter, 'out', 0), (self.latch_a, 'inp', 0) )
+		self.jt_nQ.new_pin().conn = self.latch_a.CLK
+
+		ROM(self, dbit_n, abit_n, dlst, 'rom', latency)
+		conn2_n( abit_n, (self.latch_a, 'out', 0), (self.rom, 'A', 0) )
+		self.jt_en.new_pin().conn = self.rom.en
+
+		LATCH(self, dbit_n, 'latch_d', latency)
+		conn2_n( dbit_n, (self.rom, 'D', 0), (self.latch_d, 'inp', 0) )
+		self.jt_Q.new_pin().conn = self.latch_d.CLK
+
+		new_pin_n(dbit_n, self, 'out', 0, 'L')
+		conn2_n( dbit_n, (self.latch_d, 'out', 0), (self, 'out', 0) )
+
 class RAM(Obj):
 	# en, CLK, A_x, D_x
 	def __init__(self, parent, dbit_n, abit_n, name='ram', latency=10):

# クロックとカウンタとROMをまとめたクラスです

# カウンタはクロックの立ち上がりで更新
# カウンタ出力をクロックの立ち下がりでラッチしてROMのアドレス端子へ
# ROMのデータ出力をクロックの立ち上がりでラッチしてから出力してます


@@ -606,6 +637,50 @@
 		for i in range(n):
 			conn2_n( dbit_n, (self.latch_n, name_i('out', i), 0), (self.mux, name_i('inp', i), 0) )
 
+class RAM_TEST_4(Obj):
+	# en
+	def __init__(self, parent, abit_n, dlst=(), name='ram_test_4', hz=1.0, pos=(0,0), latency=10):
+		Obj.__init__(self, parent, name, None, pos)
+
+		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+
+		(x,y) = (0,0)
+		ROM_SEQ(self, 8, abit_n, dlst, 'rom_seq', hz, (x,y), 'CLK', latency)
+		self.jt_en.new_pin().conn = self.rom_seq.en
+		x += 10
+		Joint_N(self, 4, 'jt_d', None, (x,y), ('y',1), 'D')
+		self.jt_d.new_pin_conn(self.rom_seq, 'out', direc='from_targ')
+		y += 4+1
+		Joint_N(self, 3, 'jt_a', None, (x,y), ('y',1), 'A')
+		self.jt_a.new_pin_conn(self.rom_seq, 'out', 4, direc='from_targ')
+		y += 3+1
+		self.rom_seq.out_7.conn = Joint(self, 'jt_ctl', None, (x,y), 'CTL').new_pin()
+		self.rom_seq.nQ.conn = Joint(self, 'jt_nQ').new_pin()
+		y_bak = y
+		
+		RAM(self, 4, 3, 'ram', latency)
+		self.jt_en.new_pin().conn = self.ram.en
+		self.jt_d.new_pin_conn(self.ram, 'inp', direc='to_targ')
+		self.jt_a.new_pin_conn(self.ram, 'A', direc='to_targ')
+
+		x += 10
+		y = 0
+		x_bak = x
+		Joint_N(self, 4, 'jt_ram_out', None, (x,y), ('y',1), 'O')
+		self.jt_ram_out.new_pin_conn(self.ram, 'out', direc='from_targ')
+
+		DECODER_Lamp_7seg(self, 'deco_lamp_7seg', latency, (40,0))
+		self.jt_en.new_pin().conn = self.deco_lamp_7seg.en
+		self.jt_ram_out.new_pin_conn(self.deco_lamp_7seg, 'inp', direc='to_targ')
+
+		x = x_bak
+		y = y_bak
+		AND(self, 'and_w', latency)
+		self.jt_ctl.new_pin().conn = self.and_w.inp_a
+		self.jt_nQ.new_pin().conn = self.and_w.inp_b
+		self.and_w.out.conn = Joint(self, 'jt_wrt', None, (x,y), 'W').new_pin()
+		self.jt_wrt.new_pin().conn = self.ram.CLK
+
 class CLK(Obj):
 	# en, out, Q, nQ
 	def __init__(self, parent, hz=1.0, name='clk', pos=(0,0), lamp_name=None, latency=10):
$ 


# データビット幅4ビット、アドレス空間3ビットのRAMのテスト用のモジュールです
# テスト用のデータビットはばROMは8ビット
# 下位4ビットはデータ
# 次の3ビットはアドレス
# 最上位ビットは書き込み制御用です

続いて確認用のお試しコマンド。

cmd_ram.txt

$ cat cmd_ram.txt
o.pos = (4,4)

o.data1 = [ (0,1),(1,2),(2,3),(3,4) ]
o.dlst = [ 0x80|(a<<4)|d for (a,d) in o.data1 ]

o.data2 = [ 3, 2, 1, 0 ]
o.dlst += [ a<<4 for a in o.data2 ]

RAM_TEST_4( o, 3, o.dlst, 'ram_test_4' )

sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 


# o.dlst のデータが全てです
# o.data1 に (アドレス, データ) の組みを用意して
# 最上位ビットを'1'にしつつ、アドレスとデータををシフトして所定の位置に配置

# 次に読み出し用として、o.data2 にアドレスだけのデータを用意して
# 最上位ビットを'0'にしつつ、アドレスをシフトして所定の位置に配置

# 初期化が終わるであろうタイミングでen端子を'H'にしてスタート

それでは実行してみます。

$ ./nand.py < cmd_ram.txt
    :

ポクポクチーン。固まりました。

デバッグのお時間です。


デバッグのお時間です

まずは詳細な記録をとりつつ眺めてみまするに、、、

# -v は verbose で詳細表示オプション
# -f は「寝ないでがんばる」オプション

$ ./nand.py -v -f < cmd_ram.txt | tee log-1.txt
0.000000000 cmd_exec "o.pos = (4,4)"
0.000000000 cmd_exec "o.data1 = [ (0,1),(1,2),(2,3),(3,4) ]"
0.000000000 cmd_exec "o.dlst = [ 0x80|(a<<4)|d for (a,d) in o.data1 ]"
0.000000000 cmd_exec "o.data2 = [ 3, 2, 1, 0 ]"
0.000000000 cmd_exec "o.dlst += [ a<<4 for a in o.data2 ]"
0.000000000 cmd_exec "RAM_TEST_4( o, 3, o.dlst, 'ram_test_4' )"
0.000000000 [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

  :

17.307676485 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a L
17.307676485 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.jt_k_o.pin2 L
17.307676485 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nQ L
17.307676485 o.ram_test_4.ram.latch_n.latch_0.dff_1.nQ L
17.307676485 o.ram_test_4^C

なんだか同じパターンの表示がスクロールされて流れてるようなので、 ^C で止めます。

$ wc -l log-1.txt
  199314 log-1.txt
$ 

$ less log-1.txt
  :
0.500000010 o.ram_test_4.rom_seq.clk.not_.nand.out L
0.500000010 o.ram_test_4.rom_seq.clk.not_.out L
16.307640075 o.ram_test_4.en H
16.307640075 o.ram_test_4.jt_en.pin0 H
16.307640075 o.ram_test_4.jt_en.pin1 H
16.307640075 o.ram_test_4.rom_seq.en H
16.307640075 o.ram_test_4.rom_seq.jt_en.pin0 H
16.307640075 o.ram_test_4.jt_en.pin2 H
16.307640075 o.ram_test_4.ram.en H
  :

# ここから en 'H' にしてスタートして

  :
17.307676445 o.ram_test_4.ram.mux.inp_0_1 L
17.307676445 o.ram_test_4.ram.mux.and_set_or_1.inp_0 L
17.307676445 o.ram_test_4.ram.mux.and_set_or_1.and_set.inp_0 L
17.307676445 o.ram_test_4.ram.mux.and_set_or_1.and_set.and_0.inp_a L
17.307676445 o.ram_test_4.ram.mux.and_set_or_1.and_set.and_0.nand.inp_a L
17.307676445 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.nand_s update
17.307676445 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j update
17.307676445 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_k update
17.307676445 o.ram_test_4.ram.mux.and_set_or_1.and_set.and_0.nand update
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nand_r.out H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.jt_r.pin0 H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.nand_r.out H
  :
  :
17.307676455 o.ram_test_4.ram.mux.inp_0_1 H
17.307676455 o.ram_test_4.ram.mux.and_set_or_1.inp_0 H
17.307676455 o.ram_test_4.ram.mux.and_set_or_1.and_set.inp_0 H
17.307676455 o.ram_test_4.ram.mux.and_set_or_1.and_set.and_0.inp_a H
17.307676455 o.ram_test_4.ram.mux.and_set_or_1.and_set.and_0.nand.inp_a H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.nand_s update
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.jt_k_o.pin1 H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.jt_k_o.pin2 H
17.307676455 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nQ H
  :
  :
17.307676465 o.ram_test_4.ram.mux.inp_0_1 L
17.307676465 o.ram_test_4.ram.mux.and_set_or_1.inp_0 L
17.307676465 o.ram_test_4.ram.mux.and_set_or_1.and_set.inp_0 L
  :

クロックは1.0Hzなので、10ナノ秒ごとにRAMのMUXへのある入力端子が'L','H'を繰り返し、 発振してます。

MUXの、ある入力端子とは inp_0_1 なので、 アドレス0のデータビットのビット1。

この処理が延々続いて、先に進んでません。

原因にさかのぼっていきます。

MUXの入力ということはLATCH_Nの出力。

$ grep latch_n.out_0_1 log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.out_0_1 L
17.307640175 o.ram_test_4.ram.latch_n.out_0_1 H
17.307640185 o.ram_test_4.ram.latch_n.out_0_1 L
17.307640195 o.ram_test_4.ram.latch_n.out_0_1 H
17.307640205 o.ram_test_4.ram.latch_n.out_0_1 L
17.307640215 o.ram_test_4.ram.latch_n.out_0_1 H
17.307640225 o.ram_test_4.ram.latch_n.out_0_1 L
17.307640235 o.ram_test_4.ram.latch_n.out_0_1 H
17.307640245 o.ram_test_4.ram.latch_n.out_0_1 L
17.307640255 o.ram_test_4.ram.latch_n.out_0_1 H
$ 

# 発振

# latch_n の latch_0 の出力のビット1

$ grep latch_n.latch_0.out_1 log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.out_1 L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.out_1 H
17.307640185 o.ram_test_4.ram.latch_n.latch_0.out_1 L
17.307640195 o.ram_test_4.ram.latch_n.latch_0.out_1 H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.out_1 L
17.307640215 o.ram_test_4.ram.latch_n.latch_0.out_1 H
17.307640225 o.ram_test_4.ram.latch_n.latch_0.out_1 L
17.307640235 o.ram_test_4.ram.latch_n.latch_0.out_1 H
17.307640245 o.ram_test_4.ram.latch_n.latch_0.out_1 L
17.307640255 o.ram_test_4.ram.latch_n.latch_0.out_1 H
$ 

# 発振

# latch_n の latch_0 の インデックス 1 の D-FF の出力

$ grep latch_n.latch_0.dff_1.Q log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q H
17.307640185 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q L
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q L
17.307640215 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q H
17.307640225 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q L
17.307640235 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q H
17.307640245 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q L
17.307640255 o.ram_test_4.ram.latch_n.latch_0.dff_1.Q H
$ 

# 発振

# そのD-FFの入力DとCLKは?

$ grep latch_n.latch_0.dff_1.[DC] log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.CLK L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.D L
16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.CLK H
17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.D H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.CLK L
$ 

# 最初に D 'L', CLK 'L'
# 時刻16.8秒 くらいに CLK 'H' で D 'L' をとりこんだはず
# その0.5秒後くらいに D が 'H' に変化して
# すぐ10ナノ秒後に CLK が 'L' に落ちて
# それ以降は変化なし

D-FF的には、CLKの立上りでDの値を取り込んで、 あとは、次のCLKの立上りまでは、Dがどう変化しようが、 CLKをどこで'L'に落とそうが、問題なさそうなのでですが、、、

たぶん、Dの変化とCLKの変化が10ナノ秒という近さで起こってる事が からんでる?

D-FFの中身はJKFF。その中は3つのRSFF。

出力側のRSFFの端子をみてみると。

$ grep latch_n.latch_0.dff_1.jkff.rsff_o.Q log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q H
17.307640185 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q L
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q L
17.307640215 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q H
17.307640225 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q L
17.307640235 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q H
17.307640245 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q L
17.307640255 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.Q H
$ 

# 確かに発振してて

# その入力は

$ grep latch_n.latch_0.dff_1.jkff.rsff_o.[RS] log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.S L
0.000000030 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.S H
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.R L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_o.R H
$ 

# 最初 R 'H', S 'L' なので Q 'L' なはず
# 30 ナノ秒で 'H', 'H' で固定したまま 17秒くらい経過

# ここで R 'H' --> 'L' に変化しつつ
# 10ナノ秒差で R 'L' --> 'H'

結線の状態の変化を再現してみると


    H   +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |L
   | H  +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | L  +----+      |
   .----|     \     |H
        |      |O---@------ nQ
S ------|     /
    L   +----+

この安定状態から


    H   +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |L
   | H  +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | L  +----+      |
   .----|     \     |H
        |      |O---@------ nQ
S ------|     /
    H!  +----+

S が L --> H で状態キープ


    L!  +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |L
   | H  +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | L  +----+      |
   .----|     \     |H
        |      |O---@------ nQ
S ------|     /
    H   +----+


R が H --> L
「10ナノ秒後に Q 側 L --> H」がスケジューラに登録されつつ

その10ナノ秒後の時点にきてみると、
ちょうどそのタイミングで R が L --> H に戻ります

    H!  +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |H!
   | H  +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | H! +----+      |
   .----|     \     |H
        |      |O---@------ nQ
S ------|     /
    H   +----+


この入力に対して
「10ナノ秒後に Q H --> L」
「10ナノ秒後に nQ H --> L」
がスケジューラに登録されて

    
    H   +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |L!
   | L! +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | L! +----+      |
   .----|     \     |L!
        |      |O---@------ nQ
S ------|     /
    H   +----+


ああ、Q, nQ が同じ値に同期した

この入力に対して
「10ナノ秒後に Q L --> H」
「10ナノ秒後に nQ L --> H」
がスケジューラに登録されて

    H   +----+
R ------|     \
        |      |O---@------ Q
   .----|     /     |H!
   | H! +----+      |
   .----    --------.
         \ /
          X
         / \
   .----    --------.
   | H! +----+      |
   .----|     \     |H!
        |      |O---@------ nQ
S ------|     /
    H   +----+


魔のスパイラル
もう抜けられません

JKFFの中の出力側の RSFF の R に H --> L --> H で 10ナノ秒だけ L に落ちるパルスが入ると、発狂してます。

なぜ R に10ナノ秒のパルスがくるか?

         .----------------------------------------.
         |   +---+      +------+                  |
         .---|    \     |      |                  |
             |     |O---| R  Q |                  |
J -----------|    /     |      |                  |
             +---+      |      |                  |
                        |      |                  |
             +---+      |      |       +------+   |
       .-----|    \     |      |       |      |   |
       |     |     |----| S nQ |---@---| R  Q |---|--@--- Q
       | .---|    /     |      |   |   |      |   |  |
       | |   +---+      +------+   |   |      |   |  |
       | .----------\ /------------.   |      |   |  |
CLK ---@             X                 |      |   |  |
       | .----------/ \------------.   |      |   |  |
       | |   +---+      +------+   |   |      |   |  |
       | .---|    \     |      |   |   |      |   |  |
       |     |     |----| R  Q |---@---| S nQ |---@--|--- nQ
       .-----|    /     |      |       |      |      |
             +---+      |      |       +------+      |
                        |      |                     |
             +---+      |      |                     |
K -----------|    \     |      |                     |
             |     |O---| S nQ |                     |
         .---|    /     |      |                     |
         |   +---+      +------+                     |
         .-------------------------------------------.

J側の RSFF の nQ が出してるからに違いありません。

$ grep latch_n.latch_0.dff_1.jkff.rsff_j.nQ log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
$

# 確かに出てます

# J側のRSFFの入力は?

$ grep latch_n.latch_0.dff_1.jkff.rsff_j.[RS] log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
17.307640215 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
17.307640225 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
17.307640235 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
$ 

# nQ の変化を織りまぜると

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H

16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S H

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L


まず最初は

    H   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |L
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | L  +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    L   +---+

から
16.8秒くらいに S L --> H

    H   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |L
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | L  +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    H!  +---+

でキープ

17.3 秒くらいに R H --> L


    L!  +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |L
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | L  +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    H   +---+

「10ナノ秒後に Q H」登録

10 ナノ経過して

    L   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |H!
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | H! +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    H   +---+


「10ナノ秒後に nQ L」登録
10ナノ秒経過してみると、そのタイミングで S H --> L

    L   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |H
   | L! +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | H  +---+      |
   .----|    \     |L!
        |     |O---@------ nQ
S ------|    /
    L!  +---+

「10ナノ秒後に nQ H」登録

10ナノ秒経過して

    L   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |H
   | H! +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | H  +---+      |
   .----|    \     |H!
        |     |O---@------ nQ
S ------|    /
    L   +---+

以上で nQ が 10ナノ秒だけ 'L' になる詳細です。

いったいどこが悪かったのでしょうか?

RS:HL安定状態から、RS:HHキープになって、RS:LHへの変化は問題ないはず。

その状態で入力を変化させなければ、10ナノ秒後にQが変化し、 さらに10ナノ秒後にnQが変化して安定状態になるはず。

ところが、この安定までの20ナノ必要なうちの、ぎりぎり20ナノのところで 入力が変化してる事が問題です。

RS:HH --> RS:LH --> RS:LL
              20ナノ

# これでもうnQが10ナノのパルスがでてしまうのですが
# つづきをみると

RS:HH --> RS:LH --> RS:LL --> RS:HL --> RS:LL
              20ナノ    30ナノ     10ナノ

# などと...

J,K,CLKの入力と、このJ側のRSの入力のタイミングの相関をみてみまするに

$ grep latch_n.latch_0.dff_1.jkff.[JKC] log-1.txt 
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
0.000000010 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K H
16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK H
17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
$ 


# なので先の結果におりまぜて


0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# J,K,CLK : L

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
# RS:HL で 安定

0.000000010 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K H
# D-FFなので、KはJの判定で 10ナノ秒おくれて H に

# 16秒以上も変化なく...

16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK H
# ここでクロック立上り

16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S H
# RS:HH に変化してキープ状態

# 0.5秒変化なく...

17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# D-FFのDが変化したから J,Kがそれぞれ反転
# ただしK側は NOT かんでるので10ナノ秒の遅れ

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
# Kの変換と同時に CLK も落ちる

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
# と同時に、先のJ側の変化が反映されて RS:HH --> LH

17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H

# ここで、問題の nQ が10ナノ秒だけ L に落ちる現象..

17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
  :

# むー

         .----------------------------------------.
         |   +---+      +------+                  |
         .---|    \     |      |                  |
             |     |O---| R  Q |                  |
J -----------|    /     |      |                  |
             +---+      |      |                  |
                        |      |                  |
             +---+      |      |       +------+   |
       .-----|    \     |      |       |      |   |
       |     |     |----| S nQ |---@---| R  Q |---|--@--- Q
       | .---|    /     |      |   |   |      |   |  |
       | |   +---+      +------+   |   |      |   |  |
       | .----------\ /------------.   |      |   |  |
CLK ---@             X                 |      |   |  |


# この R, S の入力側の NAND の変化は

$ grep latch_n.latch_0.dff_1.jkff.nand_j.[io] log-1.txt | head
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_b L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a H
0.000000010 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out H
17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_b H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out L
17.307640185 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a L
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out H
17.307640195 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a H
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out L
17.307640205 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_a L


# 一部、先の結果におりまぜると

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# J,K,CLK : L

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
# RS:HL で 安定

0.000000010 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K H
# D-FFなので、KはJの判定で 10ナノ秒おくれて H に

# 16秒以上も変化なく...

16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK H
# ここでクロック立上り

16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S H
# RS:HH に変化してキープ状態

# 0.5秒変化なく...

17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J H
17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_b H
# J端子接続で inp_b は H 固定 --> 以降 nand_j は NOT として動作

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# D-FFのDが変化したから J,Kがそれぞれ反転
# ただしK側は NOT かんでるので10ナノ秒の遅れ

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out L
# Jの変化でNAND出力L
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
# と同時に、先のJ側の変化が反映されて RS:HH --> LH

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
# Kの変換と同時に CLK も落ちる

17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H

# ここで、問題の nQ が10ナノ秒だけ L に落ちる現象..



# この R, S の入力側の AND の変化は

$ grep latch_n.latch_0.dff_1.jkff.and_j.[io] log-1.txt       
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_a L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_b L
0.000000020 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.out L
0.000000030 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_b H
16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_a H
16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.out H
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_a L
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.out L
$ 

# AND側はこれだけで終了


# 一部、先の結果におりまぜると

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# J,K,CLK : L

0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R H
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
0.000000000 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H
# RS:HL で 安定

0.000000010 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K H
# D-FFなので、KはJの判定で 10ナノ秒おくれて H に

# 16秒以上も変化なく...

16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK H
# ここでクロック立上り
16.807640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_a H
# CLKの接続で変化

16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.out H
# ANDは NAND+NOTで20ナノ秒に出力に反映
16.807640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S H
# RS:HH に変化してキープ状態

# 0.5秒変化なく...

17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.J H
17.307640135 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.inp_b H
# J端子接続で inp_b は H 固定 --> 以降 nand_j は NOT として動作

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.K L
# D-FFのDが変化したから J,Kがそれぞれ反転
# ただしK側は NOT かんでるので10ナノ秒の遅れ

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.nand_j.out L
# Jの変化でNAND出力L
17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.R L
# と同時に、先のJ側の変化が反映されて RS:HH --> LH

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.CLK L
# Kの変化と同時に CLK も落ちる

17.307640145 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.inp_a L
# CLKの接続で変化

17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.and_j.out L
# ANDなので20ナノ秒で出力に反映
17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.S L
# AND出力の結果 RS:LL

17.307640165 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ L
17.307640175 o.ram_test_4.ram.latch_n.latch_0.dff_1.jkff.rsff_j.nQ H

# ここで、問題の nQ が10ナノ秒だけ L に落ちる現象..

むー。複雑。

でも何となくつかめてきたかもです。

J,K,CLKの入力について、 Jが変化したあと10ナノ秒遅れてKとCLKが同時に変化。

CLKはH --> Lの変化。 JKFFからして、本来CLKはどこで落しても影響ないはずだけど、、、

Jの変化で後段のRSFFのRが先に変化。 CLKの変化で後段のRSFFのSが遅れて変化。 この差20ナノ。そしてnQに10ナノのパルスが生まれる。

たぶん、Jの変化とCLKの変化がもっと離れてると問題なく。 CLKの立ち下がりなので、あるいは、JとCLKが同時でも問題ない?

J, CLK同時だとNANDとANDを経由してR, Sに入るので、 NAND 10ナノ AND 20 なので、R, Sの変化の差は 10ナノになります。


# 短いパルスやいかに?
# Sの変化を10ナノ早めてみます

17.3 秒くらいに R H --> L


    L!  +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |L
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | L  +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    H   +---+

「10ナノ秒後に Q H」登録

10 ナノ経過して Q H 反映とともに
S も L に

    L   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |H!
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | H! +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    L!  +---+

これで固定するので nQ に短い L は出ません
よさそうです。


ちなみに RS:HH --> RS:LL同時ならば?


17.3 秒くらいに R H --> L しつつ S H --> L


    L!  +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |L
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | L  +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    L!  +---+

「10ナノ秒後に Q H」登録
10 ナノ経過して

    L   +---+
R ------|    \
        |     |O---@------ Q
   .----|    /     |H!
   | H  +---+      |
   .----    -------.
         \ /
          X
         / \
   .----    -------.
   | H! +---+      |
   .----|    \     |H
        |     |O---@------ nQ
S ------|    /
    L   +---+

これで固定
これでもnQに短いパルスはでません


何となく対策

さて、どう対策すべしか。

CLKの立ち下がりとDの変化を遠ざけるのは、今の構成のままでは、少々大がかりかも。

何となく導きだされる対策は、、、

NOTと同様に10ナノ秒遅れて出力が変化するけど、反転はしない部品、 BUFクラスを追加してみます。

さらに入力に対してBUFとNOTをつないで正誤が対になった2つの出力を出す部品、 PosiNegaクラスを追加してみます。

nand.py-27-diff.txt

$ cat nand.py-27-diff.txt
--- nand.py-
+++ nand.py
@@ -84,7 +84,10 @@
 		v = reduce(f, ivs, None)
 		for pin in self.pins:
 			if pin.conn:
-				pin.set(v)
+				if self.latency:
+					self.enque(pin.set, v)
+				else:
+					pin.set(v)
 
 	def new_pin_conn_n(self, n, targ, name, sta_i):
 		for i in range(n):


# BUFの内部実装としてJointの遅延を使おうとしたら
# Joint自身はlatencyを使ってませんでした
# latencyが設定されてるときは、使うように修正です


@@ -112,6 +115,14 @@
 				else:
 					targ_pin.conn = jt.new_pin()
 
+class BUF(Obj):
+	# inp, out
+	def __init__(self, parent, name='buf', latency=10):
+		# for timing
+		Obj.__init__(self, parent, name)
+		Pin(self, 'inp').conn = Joint(self, 'jt', latency).new_pin()
+		self.jt.new_pin().conn = Pin(self, 'out')
+
 class NOT(Obj):
 	# inp, out
 	def __init__(self, parent, name='not_', latency=10):


# そして遅延の目的だけのためのBUFクラスの追加です


@@ -268,6 +279,16 @@
 		self.or_n.out.conn = self.not_.inp
 		self.not_.out.conn = Pin(self, 'out')
 
+class PosiNega(Obj):
+	# inp, Q, nQ
+	def __init__(self, parent, name='posi_nega', latency=10):
+		Obj.__init__(self, parent, name)
+		Pin(self, 'inp').conn = Joint(self, 'jt').new_pin()
+		BUF(self, 'buf', latency).out.conn = Pin(self, 'Q')
+		NOT(self, 'not_', latency).out.conn = Pin(self, 'nQ')
+		self.jt.new_pin().conn = self.buf.inp
+		self.jt.new_pin().conn = self.not_.inp
+
 class RSFF(Obj):
 	# R, S, Q, nQ
 	def __init__(self, parent, name='rsff', latency=10):


# BUFクラスを使って、inpに対してQと反転したnQを、
# 同じタイミングで出力する PosiNegaクラスの追加です


@@ -298,6 +319,8 @@
 		RSFF(self, 'rsff_k', latency)
 		NAND(self, 'nand_j', latency)
 		NAND(self, 'nand_k', latency)
+		BUF(self, 'buf_j', latency)
+		BUF(self, 'buf_k', latency)
 		AND(self, 'and_j', latency)
 		AND(self, 'and_k', latency)
 		Joint(self, 'jt_clk')
@@ -313,10 +336,12 @@
 		Pin(self, 'J', 'L').conn = self.nand_j.inp_b
 		Pin(self, 'K', 'L').conn = self.nand_k.inp_a
 
-		self.nand_j.out.conn = self.rsff_j.R
+		self.nand_j.out.conn = self.buf_j.inp
+		self.buf_j.out.conn = self.rsff_j.R
 		self.and_j.out.conn = self.rsff_j.S
 		self.and_k.out.conn = self.rsff_k.R
-		self.nand_k.out.conn = self.rsff_k.S
+		self.nand_k.out.conn = self.buf_k.inp
+		self.buf_k.out.conn = self.rsff_k.S
 
 		self.rsff_j.nQ.conn = self.jt_j_i.new_pin()
 		self.jt_j_i.new_pin().conn = self.rsff_o.R


# JKFFクラスで AND と NAND の出力タイミングを揃えるために
# NANDの出力側に BUF を追加してます

# latency の指定は揃ってるのに
# (NANDとANDで2倍遅延が違うとは... 見ため判りにくいです >_<)


@@ -350,10 +375,9 @@
 		Obj.__init__(self, parent, name)
 		JKFF(self, 'jkff', latency)
 		Pin(self, 'CLK', 'L').conn = self.jkff.CLK
-		Pin(self, 'D', 'L').conn = Joint(self, 'jt').new_pin()
-		self.jt.new_pin().conn = self.jkff.J
-		self.jt.new_pin().conn = NOT(self, 'not_', latency).inp
-		self.not_.out.conn = self.jkff.K
+		Pin(self, 'D', 'L').conn = PosiNega(self, 'posi_nega', latency).inp
+		self.posi_nega.Q.conn = self.jkff.J
+		self.posi_nega.nQ.conn = self.jkff.K
 		self.jkff.Q.conn = Pin(self, 'Q')
 		self.jkff.nQ.conn = Pin(self, 'nQ')
 

# DFFクラスについて、D端子を内部JKFFのJ,Kに割り振るのに
# PosiNega を使うように変更して、タイミングを揃えてます


@@ -688,18 +712,18 @@
 		late = tm_from_fsec( 1.0 / ( 2 * hz ) )
 		NAND(self, 'nand', late)
 		Joint(self)
-		NOT(self, 'not_', latency)
+		PosiNega(self, 'posi_nega', latency)
 
 		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
 		self.jt_en.new_pin().conn = self.nand.inp_a
 		self.nand.out.conn = self.jt.new_pin()
 		self.jt.new_pin().conn = self.nand.inp_b
-		self.jt.new_pin().conn = self.not_.inp
+		self.jt.new_pin().conn = self.posi_nega.inp
 
 		GATE(self, 2, 'gate', latency)
 		self.jt_en.new_pin().conn = self.gate.en
-		self.jt.new_pin().conn = self.gate.inp_0
-		self.not_.out.conn = self.gate.inp_1
+		self.posi_nega.Q.conn = self.gate.inp_0
+		self.posi_nega.nQ.conn = self.gate.inp_1
 
 		self.gate.out_1.conn = Pin(self, 'nQ')
 		self.gate.out_0.conn = Joint(self, 'jt_out').new_pin()
$ 

# CLKクラスです
# PosiNegaクラスを使う以下の構成にして
# Q, nQのタイミングを揃えてみました

#                                                           [ Lamp ]
#         .---------------.  +-----------+   +------------+    |
#         |    +---+      |  | PosiNega  |   |  GATE 2bit |    | .--- out
#         .----|    \     |  |           |   |            |    |/
#              |     |O---@--|inp       Q|---|inp_0  out_0|----@----- Q
#   en ---@----|    /        |           |   |            |
#         |    +---+         |         nQ|---|inp_1  out_1|---------- nQ
#         |                  +-----------+   |            |
#         .----------------------------------|en          |
#                                            +------------+

それでは実行してみます。

$ ./nand.py < cmd_ram.txt
0.000000000 o.ram_test_4.rom_seq.clk.CLK L
ESC[5;5HCLK
0.000000000 o.ram_test_4.jt_d.jt_0.D_0 L
ESC[5;15HD_0
0.000000000 o.ram_test_4.jt_d.jt_1.D_1 L
ESC[6;15HD_1
0.000000000 o.ram_test_4.jt_d.jt_2.D_2 L
ESC[7;15HD_2
0.000000000 o.ram_test_4.jt_d.jt_3.D_3 L
ESC[8;15HD_3
0.000000000 o.ram_test_4.jt_a.jt_0.A_0 L
  :

# よすよす



# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_ram.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

もうちょっと、色々読み書きしてみます。

cmd_ram2.txt

$ cat cmd_ram2.txt
o.pos = (4,4)

o.data1 = [ (0,1),(1,2),(2,3),(3,4) ]
o.dlst = [ 0x80|(a<<4)|d for (a,d) in o.data1 ]

o.data2 = [ 3, 2, 1, 0 ]
o.dlst += [ a<<4 for a in o.data2 ]

o.data1 = [ (7,5),(5,2),(3,5),(1,2) ]
o.dlst += [ 0x80|(a<<4)|d for (a,d) in o.data1 ]

o.data2 = [ 1, 3, 5, 7 ]
o.dlst += [ a<<4 for a in o.data2 ]

RAM_TEST_4( o, 4, o.dlst, 'ram_test_4' )

sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 


# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します


# 元の端末側に戻って

$ ./nand.py < cmd_ram2.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

指定した通りの順で書き込み、指定した順で読み出せてるようです。 さすがわランダム・アクセス・メモリ。


ちったあ無駄を省いて効率化

「効率は無視。とりあえず動くもの」としてきたものの、 さすがに実行すると初期化待ちで1分もかかるので、 ちょっとスピードアップを図ってみます。


端子の初期化の削除

あちこちでPin()やnew_pin_n()で端子を生成してるときに、 いちいち初期値'L'を指定してます。

Pinクラスのコンストラクタから enque_set(v) で、 スケジューラに登録されるので、スケジューラをスタートさせたときに、 これらの処理にかかる時間が元凶かと。

端子の初期値は一応「None」ということにしてたので、 Boolとして判定されるような箇所では False つまり 'L' 扱いになるので、 心配して 'L' で初期化しなくとも大丈夫でしょう。

'H'指定とか、必要そうなところは残しておくとして、 'L'指定箇所を削除してみます。

nand.py-28-diff.txt

$ cat nand.py-28-diff.txt
--- nand.py-
+++ nand.py
@@ -329,12 +329,12 @@
 		Joint(self, 'jt_j_o')
 		Joint(self, 'jt_k_o')
 
-		Pin(self, 'CLK', 'L').conn = self.jt_clk.new_pin()
+		Pin(self, 'CLK').conn = self.jt_clk.new_pin()
 		self.jt_clk.new_pin().conn = self.and_j.inp_a
 		self.jt_clk.new_pin().conn = self.and_k.inp_b
 
-		Pin(self, 'J', 'L').conn = self.nand_j.inp_b
-		Pin(self, 'K', 'L').conn = self.nand_k.inp_a
+		Pin(self, 'J').conn = self.nand_j.inp_b
+		Pin(self, 'K').conn = self.nand_k.inp_a
 
 		self.nand_j.out.conn = self.buf_j.inp
 		self.buf_j.out.conn = self.rsff_j.R
@@ -363,7 +363,7 @@
 	def __init__(self, parent, name='tff', latency=10):
 		Obj.__init__(self, parent, name)
 		JKFF(self, 'jkff', latency)
-		Pin(self, 'CLK', 'L').conn = self.jkff.CLK
+		Pin(self, 'CLK').conn = self.jkff.CLK
 		self.jkff.J.enque_set('H')
 		self.jkff.K.enque_set('H')
 		self.jkff.Q.conn = Pin(self, 'Q')
@@ -374,8 +374,8 @@
 	def __init__(self, parent, name='dff', latency=10):
 		Obj.__init__(self, parent, name)
 		JKFF(self, 'jkff', latency)
-		Pin(self, 'CLK', 'L').conn = self.jkff.CLK
-		Pin(self, 'D', 'L').conn = PosiNega(self, 'posi_nega', latency).inp
+		Pin(self, 'CLK').conn = self.jkff.CLK
+		Pin(self, 'D').conn = PosiNega(self, 'posi_nega', latency).inp
 		self.posi_nega.Q.conn = self.jkff.J
 		self.posi_nega.nQ.conn = self.jkff.K
 		self.jkff.Q.conn = Pin(self, 'Q')
@@ -386,12 +386,12 @@
 	def __init__(self, parent, n, name='latch', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
-		Pin(self, 'CLK', 'L').conn = Joint(self, 'jt_clk').new_pin()
+		Pin(self, 'CLK').conn = Joint(self, 'jt_clk').new_pin()
 		for i in range(n):
 			dff_i = DFF( self, name_i('dff', i), latency )
 			self.jt_clk.new_pin().conn = dff_i.CLK
-			Pin( self, name_i('inp', i), 'L' ).conn = dff_i.D
-			dff_i.Q.conn = Pin( self, name_i('out', i), 'L' )
+			Pin( self, name_i('inp', i) ).conn = dff_i.D
+			dff_i.Q.conn = Pin( self, name_i('out', i) )
 
 class LATCH_N(Obj):
 	# inp_dx, CLK, sel_x, out_x_dx
@@ -399,11 +399,11 @@
 		Obj.__init__(self, parent, name)
 
 		JointANDSet(self, n, 'jas', latency)
-		Pin(self, 'CLK', 'L').conn = self.jas.inp
-		new_pin_n(n, self, 'sel', 0, 'L')
+		Pin(self, 'CLK').conn = self.jas.inp
+		new_pin_n(n, self, 'sel')
 		conn2_n( n, (self, 'sel', 0), (self.jas, 'sel', 0) )
 
-		new_pin_n(dbit_n, self, 'inp', 0, 'L')
+		new_pin_n(dbit_n, self, 'inp')
 		Joint_N(self, dbit_n, 'jt_inp', latency)
 		self.jt_inp.new_pin_conn(self, 'inp', direc='from_targ')
 
@@ -412,7 +412,7 @@
 			getattr( self.jas, name_i('out', i) ).conn = lt.CLK
 			self.jt_inp.new_pin_conn(lt, 'inp', direc='to_targ')
 
-			new_pin_n( dbit_n, self, name_i('out', i), 0, 'L' )
+			new_pin_n( dbit_n, self, name_i('out', i) )
 			conn2_n( dbit_n, (lt, 'out', 0), (self, name_i('out', i), 0) )
 
 class COUNTER(Obj):
@@ -420,11 +420,11 @@
 	def __init__(self, parent, bit_n, name='counter', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = bit_n
-		clk = Pin(self, 'CLK', 'L')
+		clk = Pin(self, 'CLK')
 		for i in range(bit_n):
 			tff = TFF( self, name_i('tff', i), latency )
 			clk.conn = tff.CLK
-			tff.Q.conn = Pin( self, name_i('out', i), 'L' )
+			tff.Q.conn = Pin( self, name_i('out', i) )
 			clk = tff.nQ
 			
 class DECODER(Obj):
@@ -433,12 +433,12 @@
 		Obj.__init__(self, parent, name)
 		self.n = bit_n
 		if bit_n == 1:
-			Pin(self, 'inp_0', 'L').conn = Joint(self, 'jt_inp').new_pin()
+			Pin(self, 'inp_0').conn = Joint(self, 'jt_inp').new_pin()
 			self.jt_inp.new_pin().conn = NOT(self, 'not_', latency).inp
 			self.not_.out.conn = AND(self, 'and_0', latency).inp_a
 			self.jt_inp.new_pin().conn = AND(self, 'and_1', latency).inp_a
 
-			Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+			Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
 			self.jt_en.new_pin().conn = self.and_0.inp_b
 			self.jt_en.new_pin().conn = self.and_1.inp_b
 
@@ -446,7 +446,7 @@
 			self.and_1.out.conn = Pin(self, 'out_1')
 		else:
 			for i in range(bit_n):
-				Pin( self, name_i('inp', i), 'L' )
+				Pin( self, name_i('inp', i) )
 			bn1 = bit_n - 1
 			DECODER(self, bn1, 'dec_u', latency)
 			DECODER(self, bn1, 'dec_d', latency)
@@ -458,7 +458,7 @@
 			DECODER(self, 1, 'dec_1', latency)
 			msb = getattr( self, name_i('inp', bn1) )
 			msb.conn = self.dec_1.inp_0
-			Pin(self, 'en', 'L').conn = self.dec_1.en
+			Pin(self, 'en').conn = self.dec_1.en
 			self.dec_1.out_0.conn = self.dec_d.en
 			self.dec_1.out_1.conn = self.dec_u.en
 
@@ -489,26 +489,26 @@
 	def __init__(self, parent, n, name='and_set_or', latency=10):
 		Obj.__init__(self, parent, name)
 		ANDSet(self, n, 'and_set', latency)
-		new_pin_n(n, self, 'inp', 0, 'L')
+		new_pin_n(n, self, 'inp')
 		conn2_n( n, (self, 'inp', 0), (self.and_set, 'inp', 0) )
-		new_pin_n(n, self, 'sel', 0, 'L')
+		new_pin_n(n, self, 'sel')
 		conn2_n( n, (self, 'sel', 0), (self.and_set, 'sel', 0) )
 
-		OR_N(self, n, 'or_n', latency).out.conn = Pin(self, 'out', 'L')
+		OR_N(self, n, 'or_n', latency).out.conn = Pin(self, 'out')
 		conn2_n( n, (self.and_set, 'out', 0), (self.or_n, 'inp', 0) )
 
 class JointANDSet(Obj):
 	# inp, sel_x, out_x
 	def __init__(self, parent, n, name='joint_and_set', latency=10):
 		Obj.__init__(self, parent, name)
-		Pin(self, 'inp', 'L').conn = Joint(self, 'jt').new_pin()
+		Pin(self, 'inp').conn = Joint(self, 'jt').new_pin()
 
 		ANDSet(self, n, 'and_set', latency)
 		self.jt.new_pin_conn_n( n, self.and_set, 'inp', 0 )
 
-		new_pin_n(n, self, 'sel', 0, 'L')
+		new_pin_n(n, self, 'sel')
 		conn2_n( n, (self, 'sel', 0), (self.and_set, 'sel', 0) )
-		new_pin_n(n, self, 'out', 0, 'L')
+		new_pin_n(n, self, 'out')
 		conn2_n( n, (self.and_set, 'out', 0), (self, 'out', 0) )
 
 class GATE(Obj):
@@ -519,12 +519,12 @@
 
 		ANDSet(self, n, 'and_set', latency)
 
-		new_pin_n( n, self, 'inp', 0, 'L' )
+		new_pin_n( n, self, 'inp')
 		conn2_n( n, (self, 'inp', 0), (self.and_set, 'inp', 0) )
-		new_pin_n( n, self, 'out', 0, 'L' )
+		new_pin_n( n, self, 'out')
 		conn2_n( n, (self.and_set, 'out', 0), (self, 'out', 0) )
 
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt').new_pin()
+		Pin(self, 'en').conn = Joint(self, 'jt').new_pin()
 		self.jt.new_pin_conn_n( n, self.and_set, 'sel', 0 )
 
 class SELECTOR(Obj):
@@ -532,31 +532,31 @@
 	def __init__(self, parent, bit_n, name='selector', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = bit_n
-		new_pin_n(bit_n, self, 'A', 0, 'L')
+		new_pin_n(bit_n, self, 'A')
 		DECODER(self, bit_n, 'deco', latency)
 		conn2_n( bit_n, (self, 'A', 0), (self.deco, 'inp', 0) )
-		Pin(self, 'en', 'L').conn = self.deco.en
+		Pin(self, 'en').conn = self.deco.en
 
 		dn = 1 << bit_n
-		new_pin_n(dn, self, 'D', 0, 'L')
+		new_pin_n(dn, self, 'D')
 		ANDSetOr(self, dn, 'and_set_or', latency)
 		conn2_n( dn, (self, 'D', 0), (self.and_set_or, 'inp', 0) )
 		conn2_n( dn, (self.deco, 'out', 0), (self.and_set_or, 'sel', 0) )
-		self.and_set_or.out.conn = Pin(self, 'out', 'L')
+		self.and_set_or.out.conn = Pin(self, 'out')
 
 class MUX(Obj):
 	# en, A_x, inp_ax_dx, out_dx, deco_out_ax
 	def __init__(self, parent, dbit_n, abit_n, name='mux', latency=10):
 		Obj.__init__(self, parent, name)
-		new_pin_n(abit_n, self, 'A', 0, 'L')
+		new_pin_n(abit_n, self, 'A')
 		DECODER(self, abit_n, 'deco', latency)
 		conn2_n( abit_n, (self, 'A', 0), (self.deco, 'inp', 0) )
-		Pin(self, 'en', 'L').conn = self.deco.en
+		Pin(self, 'en').conn = self.deco.en
 
 		n = 1 << abit_n
 		Joint_N(self, n, 'jt_deco_out').new_pin_conn( self.deco, 'out', direc='from_targ' )
 
-		new_pin_n(n, self, 'deco_out', 0, 'L')
+		new_pin_n(n, self, 'deco_out')
 		self.jt_deco_out.new_pin_conn( self, 'deco_out', direc='to_targ' )
 
 		for di in range(dbit_n):
@@ -564,8 +564,8 @@
 			self.jt_deco_out.new_pin_conn( aso, 'sel', direc='to_targ' )
 			for i in range(n):
 				nm = name_i( name_i('inp', i), di )
-				Pin(self, nm, 'L').conn = getattr( aso, name_i('inp', i) )
-			aso.out.conn = Pin( self, name_i('out', di), 'L' )
+				Pin(self, nm).conn = getattr( aso, name_i('inp', i) )
+			aso.out.conn = Pin( self, name_i('out', di) )
 
 class ROM_D1(Obj):
 	# en, A_x, D_0
@@ -577,10 +577,10 @@
 
 		SELECTOR(self, abit_n, 'select', latency)
 
-		Pin(self, 'en', 'L').conn = self.select.en
-		new_pin_n(abit_n, self, 'A', 0, 'L')
+		Pin(self, 'en').conn = self.select.en
+		new_pin_n(abit_n, self, 'A')
 		conn2_n( abit_n, (self, 'A', 0), (self.select, 'A', 0) )
-		self.select.out.conn = Pin(self, 'D_0', 'L')
+		self.select.out.conn = Pin(self, 'D_0')
 
 		# as late as possible, for data enque
 		PinSet(self, dn, dlst, 'pin_set')
@@ -592,10 +592,10 @@
 		Obj.__init__(self, parent, name)
 
 		MUX(self, dbit_n, abit_n, 'mux', latency)
-		Pin(self, 'en', 'L').conn = self.mux.en
-		new_pin_n(abit_n, self, 'A', 0, 'L')
+		Pin(self, 'en').conn = self.mux.en
+		new_pin_n(abit_n, self, 'A')
 		conn2_n( abit_n, (self, 'A', 0), (self.mux, 'A', 0) )
-		new_pin_n(dbit_n, self, 'D', 0, 'L')
+		new_pin_n(dbit_n, self, 'D')
 		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'D', 0) )
 
 		f = lambda s, v: s + [ (v >> di) & 1 for di in range(dbit_n) ]
@@ -612,14 +612,14 @@
 	# en, out_x, Q, nQ
 	def __init__(self, parent, dbit_n, abit_n, dlst=(), name='rom_seq', hz=1.0, pos=(0,0), lamp_name=None, latency=10):
 		Obj.__init__(self, parent, name, None, pos)
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
 
 		CLK(self, hz, 'clk', (0,0), lamp_name, latency)
 		self.jt_en.new_pin().conn = self.clk.en
 		self.clk.Q.conn = Joint(self, 'jt_Q').new_pin()
-		self.jt_Q.new_pin().conn = Pin(self, 'Q', 'L')
+		self.jt_Q.new_pin().conn = Pin(self, 'Q')
 		self.clk.nQ.conn = Joint(self, 'jt_nQ').new_pin()
-		self.jt_nQ.new_pin().conn = Pin(self, 'nQ', 'L')
+		self.jt_nQ.new_pin().conn = Pin(self, 'nQ')
 
 		COUNTER(self, abit_n, 'counter', latency)
 		self.jt_Q.new_pin().conn = self.counter.CLK
@@ -636,7 +636,7 @@
 		conn2_n( dbit_n, (self.rom, 'D', 0), (self.latch_d, 'inp', 0) )
 		self.jt_Q.new_pin().conn = self.latch_d.CLK
 
-		new_pin_n(dbit_n, self, 'out', 0, 'L')
+		new_pin_n(dbit_n, self, 'out')
 		conn2_n( dbit_n, (self.latch_d, 'out', 0), (self, 'out', 0) )
 
 class RAM(Obj):
@@ -646,15 +646,15 @@
 
 		n = 1 << abit_n
 		LATCH_N(self, n, dbit_n, 'latch_n', latency)
-		Pin(self, 'CLK', 'L').conn = self.latch_n.CLK
-		new_pin_n( dbit_n, self, 'inp', 0, 'L' )
+		Pin(self, 'CLK').conn = self.latch_n.CLK
+		new_pin_n( dbit_n, self, 'inp' )
 		conn2_n( dbit_n, (self, 'inp', 0), (self.latch_n, 'inp', 0) )
 
 		MUX(self, dbit_n, abit_n, 'mux', latency)
-		Pin(self, 'en', 'L').conn = self.mux.en
-		new_pin_n( abit_n, self, 'A', 0, 'L' )
+		Pin(self, 'en').conn = self.mux.en
+		new_pin_n( abit_n, self, 'A' )
 		conn2_n( abit_n, (self, 'A', 0), (self.mux, 'A', 0) )
-		new_pin_n( dbit_n, self, 'out', 0, 'L' )
+		new_pin_n( dbit_n, self, 'out' )
 		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'out', 0) )
 
 		conn2_n( n, (self.mux, 'deco_out', 0), (self.latch_n, 'sel', 0) )
@@ -666,7 +666,7 @@
 	def __init__(self, parent, abit_n, dlst=(), name='ram_test_4', hz=1.0, pos=(0,0), latency=10):
 		Obj.__init__(self, parent, name, None, pos)
 
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
 
 		(x,y) = (0,0)
 		ROM_SEQ(self, 8, abit_n, dlst, 'rom_seq', hz, (x,y), 'CLK', latency)
@@ -714,7 +714,7 @@
 		Joint(self)
 		PosiNega(self, 'posi_nega', latency)
 
-		Pin(self, 'en', 'L').conn = Joint(self, 'jt_en').new_pin()
+		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
 		self.jt_en.new_pin().conn = self.nand.inp_a
 		self.nand.out.conn = self.jt.new_pin()
 		self.jt.new_pin().conn = self.nand.inp_b
@@ -773,7 +773,7 @@
 		for i in range(n):
 			lamp = Lamp( self, name_i(name, i), latency, pos )
 			pos = step_pos_slide(pos, slide)
-			Pin( self, name_i('inp', i), 'L' ).conn = lamp.inp
+			Pin( self, name_i('inp', i) ).conn = lamp.inp
 
 	def conn_targ(self, targ, targ_pin_name, n=-1):
 		n = self.n if n < 0 else n
@@ -795,7 +795,7 @@
 		for (i, (x, y, label)) in enumerate(lst):
 			show_info = {'label': label, 'rev':False}
 			lamp = Lamp( self, name_i('lamp', i), latency, (x,y), show_info )
-			Pin( self, name_i('inp', i), 'L' ).conn = lamp.inp
+			Pin( self, name_i('inp', i) ).conn = lamp.inp
 
 	def conn_targ(self, targ, targ_pin_name):
 		conn2_n( 7, (targ, targ_pin_name, 0), (self, 'inp', 0) )
@@ -809,10 +809,10 @@
 			0x7f, 0x6f, 0x3f, 0x7a, 0x53, 0x7c, 0x5b, 0x1b,
 		]
 		ROM(self, 7, 4, dlst, 'rom', latency)
-		Pin(self, 'en', 'L').conn = self.rom.en
-		new_pin_n(4, self, 'inp', 0, 'L')
+		Pin(self, 'en').conn = self.rom.en
+		new_pin_n(4, self, 'inp')
 		conn2_n( 4, (self, 'inp', 0), (self.rom, 'A', 0) )
-		new_pin_n(7, self, 'out', 0, 'L')
+		new_pin_n(7, self, 'out')
 		conn2_n( 7, (self.rom, 'D', 0), (self, 'out', 0) )
 
 	def conn_targ(self, targ, targ_pin_name):
@@ -823,8 +823,8 @@
 	def __init__(self, parent, name='deco_lamp_7seg', latency=10, pos=(0,0)):
 		Obj.__init__(self, parent, name)
 		DECODER_7seg(self, 'deco', latency)
-		Pin(self, 'en', 'L').conn = self.deco.en
-		new_pin_n(4, self, 'inp', 0, 'L')
+		Pin(self, 'en').conn = self.deco.en
+		new_pin_n(4, self, 'inp')
 		conn2_n(4, (self, 'inp', 0), (self.deco, 'inp', 0) )
 
 		Lamp_7seg(self, 'lamp', latency, pos)
@@ -835,14 +835,14 @@
 	def __init__(self, parent, name='add', latency=10):
 		Obj.__init__(self, parent, name)
 
-		Pin(self, 'A', 'L').conn = Joint(self, 'jt_a').new_pin()
-		Pin(self, 'B', 'L').conn = Joint(self, 'jt_b').new_pin()
+		Pin(self, 'A').conn = Joint(self, 'jt_a').new_pin()
+		Pin(self, 'B').conn = Joint(self, 'jt_b').new_pin()
 
-		XOR(self, 'xor', latency).out.conn = Pin(self, 'Y', 'L')
+		XOR(self, 'xor', latency).out.conn = Pin(self, 'Y')
 		self.jt_a.new_pin().conn = self.xor.inp_a
 		self.jt_b.new_pin().conn = self.xor.inp_b
 
-		AND(self, 'and_', latency).out.conn = Pin(self, 'C', 'L')
+		AND(self, 'and_', latency).out.conn = Pin(self, 'C')
 		self.jt_a.new_pin().conn = self.and_.inp_a
 		self.jt_b.new_pin().conn = self.and_.inp_b
 
@@ -852,15 +852,15 @@
 		Obj.__init__(self, parent, name)
 
 		ADD(self, 'add_ab', latency)
-		Pin(self, 'A', 'L').conn = self.add_ab.A
-		Pin(self, 'B', 'L').conn = self.add_ab.B
+		Pin(self, 'A').conn = self.add_ab.A
+		Pin(self, 'B').conn = self.add_ab.B
 
 		ADD(self, 'add_cy', latency)
-		Pin(self, 'Cin', 'L').conn = self.add_cy.A
+		Pin(self, 'Cin').conn = self.add_cy.A
 		self.add_ab.Y.conn = self.add_cy.B
-		self.add_cy.Y.conn = Pin(self, 'Y', 'L')
+		self.add_cy.Y.conn = Pin(self, 'Y')
 
-		OR(self, 'or_', latency).out.conn = Pin(self, 'C', 'L')
+		OR(self, 'or_', latency).out.conn = Pin(self, 'C')
 		self.add_cy.C.conn = self.or_.inp_a
 		self.add_ab.C.conn = self.or_.inp_b
 
@@ -873,11 +873,11 @@
 		for i in range(n):
 			add_c = ADD_C( self, name_i('add_c', i), latency )
 			c.conn = add_c.Cin
-			Pin( self, name_i('A', i), 'L' ).conn = add_c.A
-			Pin( self, name_i('B', i), 'L' ).conn = add_c.B
-			add_c.Y.conn = Pin( self, name_i('Y', i), 'L' )
+			Pin( self, name_i('A', i) ).conn = add_c.A
+			Pin( self, name_i('B', i) ).conn = add_c.B
+			add_c.Y.conn = Pin( self, name_i('Y', i) )
 			c = add_c.C
-		c.conn = Pin(self, 'C', 'L')
+		c.conn = Pin(self, 'C')
 
 class Sched:
 	def __init__(self):
$ 

それでは実行してみます。

が、その前に、、、

  :
# 例によって別の端末から

$ mkfifo /tmp/aa
# 既に作ってあればそのままでOKです

$ sed -n -e 's/ESC/^[/gp' /tmp/aa

# として待ち受けます
# '^[' は ctrl + v を入力してからエスケープキーを入力します
  :

このくだり、コピペするのも飽きてきたのでスクリプトを用意しておきます。

show

$ cat show
#!/bin/bash

if [ $# -ne 0 ]; then
  F=$1
  [ ! -e $F ] && mkfifo $F
fi

sed -n -e 's/ESC/^[/gp' $F

# EOF
$ 

ファイル中の '^[' のコードはバイナリ 0x1B です。

$ hd show
00000000  23 21 2f 62 69 6e 2f 62  61 73 68 0a 0a 69 66 20  |#!/bin/bash..if |
00000010  5b 20 24 23 20 2d 6e 65  20 30 20 5d 3b 20 74 68  |[ $# -ne 0 ]; th|
00000020  65 6e 0a 20 20 46 3d 24  31 0a 20 20 5b 20 21 20  |en.  F=$1.  [ ! |
00000030  2d 65 20 24 46 20 5d 20  26 26 20 6d 6b 66 69 66  |-e $F ] && mkfif|
00000040  6f 20 24 46 0a 66 69 0a  0a 73 65 64 20 2d 6e 20  |o $F.fi..sed -n |
00000050  2d 65 20 27 73 2f 45 53  43 2f 1b 2f 67 70 27 20  |-e 's/ESC/./gp' |
00000060  24 46 0a 0a 23 20 45 4f  46 0a                    |$F..# EOF.|
0000006a
$ 

仕切り直して、それでは実行してみます。

# 別の端末から
$ ./show /tmp/aa


# 元の端末側に戻って

$ ./nand.py < cmd_ram2.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

特に問題なさそうです。

変更前と初期化の時間を比べてみます。

$ cp nand.py nand.py-28
$ patch -R < nand.py-28-diff.txt
  :

$ ./nand.py -v -f < cmd_ram2.txt | grep o.ram_test_4.en
0.000000000 cmd_exec "sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )"
0.000000000 o.ram_test_4.en L
57.714820861 o.ram_test_4.en H

# 1分程度から


$ mv nand.py nand.py-27
$ mv nand.py-28 nand.py
$ ./nand.py -v -f < cmd_ram2.txt | grep o.ram_test_4.en
0.000000000 cmd_exec "sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )"
25.830374002 o.ram_test_4.en H

# 26 秒程度に


多入力ORの実装を変更

多入力ORに限らず多入力ANDもそうなのですが、 生成時に「再帰」を使ってます。

例えばANDの8入力を指定されると、 ANDの4入力のものを2つ作って、結果をANDの2入力に入れて、その結果を全体の結果とする。 てな構成です。

「ワイヤードOR」というものがありますが、 実はこれまで使ってきたJointクラスは、ORとしても機能します。

	def update(self):
		ivs = [ pin.v for pin in self.pins if not pin.conn ]
		f = lambda r, v: r if v == None else ( v if r == None else r or v ) # OR
		v = reduce(f, ivs, None)
			:

ここのコメント'# OR'の箇所です。

これまで、Jointクラスを「そういう風に」使ってきませんでした。

通常は、外部の端子からJoint内の1つの端子に向かう方向に「1つだけを」つなぎ、 Joint内部の複数の端子から、外部の複数の端子へと「複数」つなぎます。

これで、Jointへの1つの入力は、Jointから複数へと出力されます。

このとき、入力側(conn == None)の v をジョイント全体の値として、 出力側(conn != None)の各端子に v をセットしてまわります。

入力側(conn == None)の端子がもし1つでなかったら、 ジョイント全体の値をどのように決めるか?

そこで「OR」をとって決めるようにしてました。

「NANDがあれば何でもできる」のモットーではありますがすが、、、 ここは「ワイヤードOR」的にジョイントを使わせてもらいます。;-p)

OR_N が出来れば、全ての入力と出力を反転させれば、AND_N になります。 OR_N から AND_N を作るにはNOTがたくさん必要なので、 NOTクラスを複数まとめただけのNOTSetクラスを追加しておきます。

nand.py-29-diff.txt

$ cat nand.py-29-diff.txt
--- nand.py-
+++ nand.py
@@ -201,61 +201,25 @@
 	# inp_x, out
 	def __init__(self, parent, n, name='and_n', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
-		AND(self, 'and_', latency)
-		if n == 2:
-			Pin(self, 'inp_0').conn = self.and_.inp_a
-			Pin(self, 'inp_1').conn = self.and_.inp_b
-
-		elif n == 3:
-			AND_N(self, 2, 'and_n', latency)
-			new_pin_conn(self, self.and_n, 0)
-			self.and_n.out.conn = self.and_.inp_a
-			Pin(self, 'inp_2').conn = self.and_.inp_b
-
-		else: # n >= 4
-			nb = n / 2
-			na = n - nb
-
-			AND_N(self, na, 'and_na', latency)
-			new_pin_conn(self, self.and_na, 0)
-			self.and_na.out.conn = self.and_.inp_a
-
-			AND_N(self, nb, 'and_nb', latency)
-			new_pin_conn(self, self.and_nb, na)
-			self.and_nb.out.conn = self.and_.inp_b
+		new_pin_n( n, self, 'inp' )
+
+		NOTSet(self, n, 'not_set', latency)
+		conn2_n( n, (self, 'inp', 0), (self.not_set, 'inp', 0) )
+
+		OR_N(self, n, 'or_n', latency)
+		conn2_n( n, (self.not_set, 'out', 0), (self.or_n, 'inp', 0) )
 
-		self.and_.out.conn = Pin(self, 'out')
+		NOT(self, 'not_', latency)
+		self.or_n.out.conn = self.not_.inp
+		self.not_.out.conn = Pin(self, 'out')
 
 class OR_N(Obj):
 	# inp_x, out
 	def __init__(self, parent, n, name='or_n', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
-		OR(self, 'or_', latency)
-		if n == 2:
-			Pin(self, 'inp_0').conn = self.or_.inp_a
-			Pin(self, 'inp_1').conn = self.or_.inp_b
-
-		elif n == 3:
-			OR_N(self, 2, 'or_n', latency)
-			new_pin_conn(self, self.or_n, 0)
-			self.or_n.out.conn = self.or_.inp_a
-			Pin(self, 'inp_2').conn = self.or_.inp_b
-
-		else: # n >= 4
-			nb = n / 2
-			na = n - nb
-
-			OR_N(self, na, 'or_na', latency)
-			new_pin_conn(self, self.or_na, 0)
-			self.or_na.out.conn = self.or_.inp_a
-
-			OR_N(self, nb, 'or_nb', latency)
-			new_pin_conn(self, self.or_nb, na)
-			self.or_nb.out.conn = self.or_.inp_b
-
-		self.or_.out.conn = Pin(self, 'out')
+		Joint(self, 'jt', latency).new_pin().conn = Pin(self, 'out')
+		for i in range(n):
+			Pin( self, name_i('inp', i) ).conn = self.jt.new_pin()
 
 class NAND_N(Obj):
 	# inp_x, out
@@ -472,6 +436,15 @@
 		Obj.__init__(self, parent, name)
 		self.n = n
 		new_pin_n(n, self, 'p', 0, dlst)
+
+class NOTSet(Obj):
+	# inp_x, out_x
+	def __init__(self, parent, n, name='not_set', latency=10):
+		Obj.__init__(self, parent, name)
+		for i in range(n):
+			not_ = NOT(self, name_i('not', i), latency)
+			Pin( self, name_i('inp', i) ).conn = not_.inp
+			not_.out.conn = Pin( self, name_i('out', i) )
 
 class ANDSet(Obj):
 	# inp_x, sel_x, out_x
$ 

cmd_ram2.txt の実行で、特に問題が出ないことを確かめつつ、、、

気になる速度の方は?

$ ./nand.py -v -f < cmd_ram2.txt | grep o.ram_test_4.en
0.000000000 cmd_exec "sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )"
20.967362165 o.ram_test_4.en H

# 21 秒程度に


デコーダの実装を変更

再帰といえば、デコーダもそうです。

nビットデコーダの生成では、n-1ビットデコーダを2つ生成しておいて、 最上位ビットの値で、2つのデコーダに「仕事」を振り分けてます。

ここはひとつ、つい先日「再帰」をやめたAND_Nクラスを使うようにしてみます。

nビットの入力について、PosiNegaをつけて「そのまま」と「反転」の信号を作っておきます。

nビット入力のAND_Nクラスを、(1 << n)個用意しておいて、 AND_Nの入力を、nビットの入力に対応するPosiNegaクラスの、Q あるいは nQ につないでできあがり。

ソースコードの量も減って、シンプルですね。

nand.py-30-diff.txt

$ cat nand.py-30-diff.txt
--- nand.py-
+++ nand.py
@@ -395,40 +395,26 @@
 	# en, inp_x, out_x
 	def __init__(self, parent, bit_n, name='decoder', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = bit_n
-		if bit_n == 1:
-			Pin(self, 'inp_0').conn = Joint(self, 'jt_inp').new_pin()
-			self.jt_inp.new_pin().conn = NOT(self, 'not_', latency).inp
-			self.not_.out.conn = AND(self, 'and_0', latency).inp_a
-			self.jt_inp.new_pin().conn = AND(self, 'and_1', latency).inp_a
 
-			Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
-			self.jt_en.new_pin().conn = self.and_0.inp_b
-			self.jt_en.new_pin().conn = self.and_1.inp_b
+		jts = []
+		for i in range(bit_n):
+			posi_nega = PosiNega( self, name_i('posi_nega', i), latency )
+			Pin( self, name_i('inp', i) ).conn = posi_nega.inp
+			jt_Q = Joint( self, name_i('jt_Q', i) )
+			jt_nQ = Joint( self, name_i('jt_nQ', i) )
+			posi_nega.Q.conn = jt_Q.new_pin()
+			posi_nega.nQ.conn = jt_nQ.new_pin()
+			jts.append( (jt_nQ, jt_Q) )
 
-			self.and_0.out.conn = Pin(self, 'out_0')
-			self.and_1.out.conn = Pin(self, 'out_1')
-		else:
+		n = 1 << bit_n
+		Pin(self, 'en').conn = GATE(self, n, 'gate', latency).en
+		for j in range(n):
+			and_n = AND_N( self, bit_n, name_i('and_n', j), latency )
+			and_n.out.conn = getattr( self.gate, name_i('inp', j) )
+			getattr( self.gate, name_i('out', j) ).conn = Pin( self, name_i('out', j) )
 			for i in range(bit_n):
-				Pin( self, name_i('inp', i) )
-			bn1 = bit_n - 1
-			DECODER(self, bn1, 'dec_u', latency)
-			DECODER(self, bn1, 'dec_d', latency)
-			Joint_N(self, bn1, 'jt_n')
-			self.jt_n.new_pin_conn(self, 'inp', 0, bn1, direc='from_targ')
-			self.jt_n.new_pin_conn(self.dec_u, 'inp')
-			self.jt_n.new_pin_conn(self.dec_d, 'inp')
-
-			DECODER(self, 1, 'dec_1', latency)
-			msb = getattr( self, name_i('inp', bn1) )
-			msb.conn = self.dec_1.inp_0
-			Pin(self, 'en').conn = self.dec_1.en
-			self.dec_1.out_0.conn = self.dec_d.en
-			self.dec_1.out_1.conn = self.dec_u.en
-
-			n1 = 1 << bn1
-			new_pin_conn(self, self.dec_d, 0, n1, 'out', direc='from_in_obj')
-			new_pin_conn(self, self.dec_u, n1, n1, 'out', direc='from_in_obj')
+				jt = jts[i][ (j >> i) & 1 ]
+				jt.new_pin().conn = getattr( and_n, name_i('inp', i) )
 
 class PinSet(Obj):
 	# p_x
$ 

cmd_ram2.txt の実行で、特に問題が出ないことを確かめつつ、、、

気になる速度の方は?

$ ./nand.py -v -f < cmd_ram2.txt | grep o.ram_test_4.en
0.000000000 cmd_exec "sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )"
20.733675003 o.ram_test_4.en H

# 21 秒程度

初期化時間には、さほど影響なかったみたいですね。


少しだけ整えておきます

関数 new_pin_conn() が特注仕様な引数なので、 最初の頃に作ったクラスでしか使ってません。

あとで追加した2つの関数 new_pin_n() で複数の端子を生成して、 conn2_n() で複数の端子をつなぐようにして、 new_pin_conn() は廃止にします。

nand.py-31-diff.txt

$ cat nand.py-31-diff.txt
--- nand.py-
+++ nand.py
@@ -226,8 +226,10 @@
 	def __init__(self, parent, n, name='nand_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
+		new_pin_n( n, self, 'inp' )
 		AND_N(self, n, 'and_n', latency)
-		new_pin_conn(self, self.and_n, 0)
+		conn2_n( n, (self, 'inp', 0), (self.and_n, 'inp', 0) )
+
 		NOT(self, 'not_', latency)
 		self.and_n.out.conn = self.not_.inp
 		self.not_.out.conn = Pin(self, 'out')
@@ -237,8 +239,10 @@
 	def __init__(self, parent, n, name='nor_n', latency=10):
 		Obj.__init__(self, parent, name)
 		self.n = n
+		new_pin_n( n, self, 'inp' )
 		OR_N(self, n, 'or_n', latency)
-		new_pin_conn(self, self.or_n, 0)
+		conn2_n( n, (self, 'inp', 0), (self.or_n, 'inp', 0) )
+
 		NOT(self, 'not_', latency)
 		self.or_n.out.conn = self.not_.inp
 		self.not_.out.conn = Pin(self, 'out')
@@ -911,13 +915,6 @@
 	x += step if direc != 'y' else 0
 	y += step if direc == 'y' else 0
 	return (x, y)
-
-def new_pin_conn(obj, in_obj, sta_i, n=-1, name='inp', direc='to_in_obj'):
-	if n < 0:
-		n = in_obj.n
-	new_pin_n(n, obj, name, sta_i)
-	direc_rev = (direc != 'to_in_obj')
-	conn2_n(n, (obj, name, sta_i), (in_obj, name, 0), direc_rev)
 
 def conn2_n( n, (from_obj, from_name, from_sta_i), (to_obj, to_name, to_sta_i), direc_rev=False ):
 	# replaced args, only ...
$ 

つづいて、あちこちのクラスで保持してる self.n = n について。

とりあえず保持させてきましたが、ほとんどのクラスはコンストラクタのみで、 独自のメソッドを持ちません。

self.n を参照する事が無いものがほとんどなので、不要なものは削除しておきます。

nand.py-32-diff.txt

$ cat nand.py-32-diff.txt
--- nand.py-
+++ nand.py
@@ -225,7 +225,6 @@
 	# inp_x, out
 	def __init__(self, parent, n, name='nand_n', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		new_pin_n( n, self, 'inp' )
 		AND_N(self, n, 'and_n', latency)
 		conn2_n( n, (self, 'inp', 0), (self.and_n, 'inp', 0) )
@@ -238,7 +237,6 @@
 	# inp_x, out
 	def __init__(self, parent, n, name='nor_n', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		new_pin_n( n, self, 'inp' )
 		OR_N(self, n, 'or_n', latency)
 		conn2_n( n, (self, 'inp', 0), (self.or_n, 'inp', 0) )
@@ -353,7 +351,6 @@
 	# inp_x, CLK, out_x
 	def __init__(self, parent, n, name='latch', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		Pin(self, 'CLK').conn = Joint(self, 'jt_clk').new_pin()
 		for i in range(n):
 			dff_i = DFF( self, name_i('dff', i), latency )
@@ -387,7 +384,6 @@
 	# CLK, out_x
 	def __init__(self, parent, bit_n, name='counter', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = bit_n
 		clk = Pin(self, 'CLK')
 		for i in range(bit_n):
 			tff = TFF( self, name_i('tff', i), latency )
@@ -424,7 +420,6 @@
 	# p_x
 	def __init__(self, parent, n, dlst=(), name='pin_set'):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		new_pin_n(n, self, 'p', 0, dlst)
 
 class NOTSet(Obj):
@@ -440,7 +435,6 @@
 	# inp_x, sel_x, out_x
 	def __init__(self, parent, n, name='and_set', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		for i in range(n):
 			and_i = AND( self, name_i('and', i), latency )
 			Pin( self, name_i('inp', i) ).conn = and_i.inp_a
@@ -478,7 +472,6 @@
 	# inp_x, en, out_x
 	def __init__(self, parent, n, name='gate', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 
 		ANDSet(self, n, 'and_set', latency)
 
@@ -494,7 +487,6 @@
 	# en, A_x, D_x, out
 	def __init__(self, parent, bit_n, name='selector', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = bit_n
 		new_pin_n(bit_n, self, 'A')
 		DECODER(self, bit_n, 'deco', latency)
 		conn2_n( bit_n, (self, 'A', 0), (self.deco, 'inp', 0) )
@@ -535,7 +527,6 @@
 	def __init__(self, parent, abit_n, dlst=(), name='rom_d1', latency=10):
 		Obj.__init__(self, parent, name)
 		self.dbgout(dlst)
-		self.n = abit_n
 		dn = 1 << abit_n
 
 		SELECTOR(self, abit_n, 'select', latency)
@@ -831,7 +822,6 @@
 	# Cin, A_x, B_x, Y_x, C
 	def __init__(self, parent, n, name='add_n', latency=10):
 		Obj.__init__(self, parent, name)
-		self.n = n
 		c = Pin(self, 'Cin', 'L')
 		for i in range(n):
 			add_c = ADD_C( self, name_i('add_c', i), latency )
$ 

cmd_ram2.txt の実行で、特に問題が出ないことを確かめつつ、、、

気になる速度の方は?

$ ./nand.py -v -f < cmd_ram2.txt | grep o.ram_test_4.en
0.000000000 cmd_exec "sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )"
20.370757102 o.ram_test_4.en H

# 20 秒程度

これまた初期化時間には、さほど影響なかったみたいですね。


足し算デバイス

RAMクラスをテストする仕組みがそこそこ動いたという事で、RAMクラスを改造して足し算用のデバイスを作ってみました。

RAMクラスをテストする仕組みの、RAMがつながってる箇所をこの「足し算デバイス」に置き換えて、 そのまま動かしてみます。

RAMの構成はこんなのでした。

                CLK                           en   A_0 A_1 .. A_an-1
                 |                            |           |
                 |                            |           |
                 |                            |           /
                 |                            |           |
               +-----------------------+    +-----------------------+
               |CLK                    |    | en   A_0 A_1 .. A_an-1|
               |                       |    |                       |
               |  LATCH_N              |    |             MUX       |
               |                  sel_0|----|deco_out_0             |
               |                  sel_1|----|deco_out_1             |
               |                   :   | :  |  :                    |
               |                sel_N-1|----|deco_out_N-1           |
               |                       |    |                       |
               |                       |    |                       |
               |                out_0_0|----|inp_0_0                |
               |                out_0_1|----|inp_0_1                |
               |                 :     | :  |  :                    |
               |             out_0_dn-1|----|inp_0_dn-1             |
               |                out_1_0|----|inp_1_0                |
               |                out_1_1|----|inp_1_1                |
               |                 :     | :  |  :                    |
               |             out_1_dn-1|----|inp_1_dn-1             |
inp_0          |inp_0            :     | :  |  :               out_0|        out_0
inp_1          |inp_1         out_N-1_0|----|inp_N-1_0         out_1|        out_1
  :     ---/---|  :              :     | :  |  :                :   |---/---  :
inp_dn-1       |inp_dn-1   out_N-1_dn-1|----|inp_N-1_dn-1   out_dn-1|        out_dn-1
               +-----------------------+    +-----------------------+

                                             ( N = 1 << an )

アドレスを1ビットで0番地と1番地だけの構成にして、 0番地のラッチに書き込む値をA、 1番地のラッチに書き込む値をBとして、、、

LATCH_Nの出力にADD_Nをつないで、A+Bを計算して、、、

ADD_Nの出力と、繰り上がりの情報をMUXに入れます。

アドレス0番地を読み出せばA+Bの結果が、アドレス1番地を読み出せば繰り上がりの結果が出力されるようにします。

RAMの構成のLATCH_NからMUXの間にADD_Nをつなげば良さそうです。

これでRAMの動作テストをするつもりで、0番地と1番地に適当な値を書きこんで、 0番地と1番地を読み出してみると、書き込んだ値ではなくて、足し算結果と、繰り上がり結果が読めるはずです。

nand.py-33-diff.txt

$ cat nand.py-33-diff.txt
--- nand.py-
+++ nand.py
@@ -619,45 +619,9 @@
 	# en
 	def __init__(self, parent, abit_n, dlst=(), name='ram_test_4', hz=1.0, pos=(0,0), latency=10):
 		Obj.__init__(self, parent, name, None, pos)
-
-		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
-
-		(x,y) = (0,0)
-		ROM_SEQ(self, 8, abit_n, dlst, 'rom_seq', hz, (x,y), 'CLK', latency)
-		self.jt_en.new_pin().conn = self.rom_seq.en
-		x += 10
-		Joint_N(self, 4, 'jt_d', None, (x,y), ('y',1), 'D')
-		self.jt_d.new_pin_conn(self.rom_seq, 'out', direc='from_targ')
-		y += 4+1
-		Joint_N(self, 3, 'jt_a', None, (x,y), ('y',1), 'A')
-		self.jt_a.new_pin_conn(self.rom_seq, 'out', 4, direc='from_targ')
-		y += 3+1
-		self.rom_seq.out_7.conn = Joint(self, 'jt_ctl', None, (x,y), 'CTL').new_pin()
-		self.rom_seq.nQ.conn = Joint(self, 'jt_nQ').new_pin()
-		y_bak = y
-		
 		RAM(self, 4, 3, 'ram', latency)
-		self.jt_en.new_pin().conn = self.ram.en
-		self.jt_d.new_pin_conn(self.ram, 'inp', direc='to_targ')
-		self.jt_a.new_pin_conn(self.ram, 'A', direc='to_targ')
-
-		x += 10
-		y = 0
-		x_bak = x
-		Joint_N(self, 4, 'jt_ram_out', None, (x,y), ('y',1), 'O')
-		self.jt_ram_out.new_pin_conn(self.ram, 'out', direc='from_targ')
-
-		DECODER_Lamp_7seg(self, 'deco_lamp_7seg', latency, (40,0))
-		self.jt_en.new_pin().conn = self.deco_lamp_7seg.en
-		self.jt_ram_out.new_pin_conn(self.deco_lamp_7seg, 'inp', direc='to_targ')
-
-		x = x_bak
-		y = y_bak
-		AND(self, 'and_w', latency)
-		self.jt_ctl.new_pin().conn = self.and_w.inp_a
-		self.jt_nQ.new_pin().conn = self.and_w.inp_b
-		self.and_w.out.conn = Joint(self, 'jt_wrt', None, (x,y), 'W').new_pin()
-		self.jt_wrt.new_pin().conn = self.ram.CLK
+		DEV_TEST_4(self, self.ram, abit_n, dlst, 'dev_test_4', hz, (0,0), latency)
+		Pin(self, 'en').conn = self.dev_test_4.en
 
 class CLK(Obj):
 	# en, out, Q, nQ


# RAM_TEST_4クラスの実装をごっそり削除、というか、DEV_TEST_4クラスに移動させて
# RAM_TEST_4クラスからはDEV_TEST_4クラスを使うように変更しました


@@ -832,6 +796,82 @@
 			c = add_c.C
 		c.conn = Pin(self, 'C')
 
+class ADD_DEV(Obj):
+	# en, CLK, A_0, D_x
+	def __init__(self, parent, dbit_n, name='add_dev', latency=10):
+		Obj.__init__(self, parent, name)
+
+		LATCH_N(self, 2, dbit_n, 'latch_2', latency)
+		ADD_N(self, dbit_n, 'add_n', latency)
+		MUX(self, dbit_n, 1, 'mux', latency)
+
+		Pin(self, 'CLK').conn = self.latch_2.CLK
+		Pin(self, 'en').conn = self.mux.en
+		Pin(self, 'A_0').conn = self.mux.A_0
+
+		self.mux.deco_out_0.conn = self.latch_2.sel_0
+		self.mux.deco_out_1.conn = self.latch_2.sel_1
+
+		new_pin_n(dbit_n, self, 'inp')
+		conn2_n( dbit_n, (self, 'inp', 0), (self.latch_2, 'inp', 0) )
+		conn2_n( dbit_n, (self.latch_2, 'out_0', 0), (self.add_n, 'A', 0) )
+		conn2_n( dbit_n, (self.latch_2, 'out_1', 0), (self.add_n, 'B', 0) )
+		conn2_n( dbit_n, (self.add_n, 'Y', 0), (self.mux, 'inp_0', 0) )
+		self.add_n.C.conn = self.mux.inp_1_0
+		new_pin_n(dbit_n, self, 'out')
+		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'out', 0) )
+


# ADD_DEVクラス追加です

#                          CLK                                              en       A_0
#                           |                                               |         |
#                           |                                               |         |
#                           |                                               |         |
#                +----------------------+                              +---------------------+
#                |         CLK          |                              |    en       A_0     |
#                |                      |                              |                     |
#                |     LATCH_N          |                              |    MUX              |
#                |     (N=2)            |                              |    (dbit_n=dn)      |
#                |                      |                              |    (abit_n=1)       |
#                |                      |                              |                     |
#                |                 sel_0|------------------------------|deco_out_0           |
#                |                 sel_1|------------------------------|deco_out_1           |
#                |                      |                              |                     |
#                |                      |       +--------------+       |                     |
#                |                      |       |              |       |                     |
#                |                      |       |    ADD_N     |       |                     |
#                |                      |       |    (N=dn)    |       |                     |
#                |                      |       |              |       |                     |
#                |                      |      -|Cin           |       |                     |
#                |                      |       |              |       |                     |
# inp_0          |inp_0          out_0_0|       |A_0        Y_0|       |inp_0_0         out_0|       out_0
# inp_1          |inp_1          out_0_1|       |A_1        Y_1|       |inp_0_1         out_1|       out_1
#   :     ---/---|  :             :     |---/---| :          : |---/---|  :               :  |---/--- :
# inp_dn-1       |inp_dn-1    out_0_dn-1|       |A_dn-1  Y_dn-1|       |inp_0_dn-1   out_dn-1|       out_dn-1
#                |                      |       |              |       |                     |
#                |               out_1_0|       |B_0          C|-------|inp_1_0              |
#                |               out_1_1|       |B_1           |      -|inp_1_1              |
#                |                :     |---/---| :            |      :|  :                  |
#                |            out_1_dn-1|       |B_dn-1        |      -|inp_1_dn-1           |
#                |                      |       |              |       |                     |
#                +----------------------+       +--------------+       +---------------------+


# A_x 端子が1つですが、RAMクラスと同じ端子の構成です
# LATCH_N から MUX の間に ADD_N が挟まった形になってます


+class DEV_TEST_4(Obj):
+	# en
+
+	def __init__(self, parent, dev, abit_n, dlst=(), name='dev_test_4', hz=1.0, pos=(0,0), latency=10):
+
+		#   dev : dbit_n <= 4, abit_n <= 3
+
+		Obj.__init__(self, parent, name, None, pos)
+
+		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
+
+		(x,y) = (0,0)
+		ROM_SEQ(self, 8, abit_n, dlst, 'rom_seq', hz, (x,y), 'CLK', latency)
+		self.jt_en.new_pin().conn = self.rom_seq.en
+		x += 10
+		Joint_N(self, 4, 'jt_d', None, (x,y), ('y',1), 'D')
+		self.jt_d.new_pin_conn(self.rom_seq, 'out', direc='from_targ')
+		y += 4+1
+		Joint_N(self, 3, 'jt_a', None, (x,y), ('y',1), 'A')
+		self.jt_a.new_pin_conn(self.rom_seq, 'out', 4, direc='from_targ')
+		y += 3+1
+		self.rom_seq.out_7.conn = Joint(self, 'jt_ctl', None, (x,y), 'CTL').new_pin()
+		self.rom_seq.nQ.conn = Joint(self, 'jt_nQ').new_pin()
+		y_bak = y
+		
+		self.dev = dev
+		dev_dbit_n = min( 4, get_num(dev, 'inp') )
+		dev_abit_n = min( 3, get_num(dev, 'A') )
+
+		self.jt_en.new_pin().conn = self.dev.en
+		self.jt_d.new_pin_conn(self.dev, 'inp', n=dev_dbit_n, direc='to_targ')
+		self.jt_a.new_pin_conn(self.dev, 'A', n=dev_abit_n, direc='to_targ')
+
+		x += 10
+		y = 0
+		x_bak = x
+		Joint_N(self, 4, 'jt_dev_out', None, (x,y), ('y',1), 'O')
+		self.jt_dev_out.new_pin_conn(self.dev, 'out', direc='from_targ')
+
+		DECODER_Lamp_7seg(self, 'deco_lamp_7seg', latency, (40,0))
+		self.jt_en.new_pin().conn = self.deco_lamp_7seg.en
+		self.jt_dev_out.new_pin_conn(self.deco_lamp_7seg, 'inp', direc='to_targ')
+
+		x = x_bak
+		y = y_bak
+		AND(self, 'and_w', latency)
+		self.jt_ctl.new_pin().conn = self.and_w.inp_a
+		self.jt_nQ.new_pin().conn = self.and_w.inp_b
+		self.and_w.out.conn = Joint(self, 'jt_wrt', None, (x,y), 'W').new_pin()
+		self.jt_wrt.new_pin().conn = self.dev.CLK
+
 class Sched:
 	def __init__(self):
 		self.que = []


# 旧RAM_TEST_4クラスの実装を移動してきて、DEV_TEST_4クラスとして汎用化してます
# テストするデバイス・クラスの端子数を数えるために、
# get_num()関数を追加して、使ってます


@@ -930,6 +970,12 @@
 		return dlst # same v
 	return dlst[i] if i < len(dlst) else -1
 
+def get_num(obj, name, sta_i=0):
+	i = sta_i
+	while hasattr( obj, name_i(name, i) ):
+		i += 1
+	return i
+
 def name_i(name, i):
 	return '{}_{}'.format(name, i)
 
$ 

# get_num() 関数の追加です
# obj の name_x 端子の数を数えて、個数を返します

続いて確認用のお試しコマンド。

cmd_add_dev.txt

$ cat cmd_add_dev.txt
o.pos = (4,4)

o.data = [ (1,0,1),(1,1,1),(0,0,0),(0,1,0) ]
o.data += [ (1,0,4),(1,1,5),(0,0,0),(0,1,0) ]
o.data += [ (1,0,8),(1,1,4),(0,0,0),(0,1,0) ]
o.data += [ (1,0,0xf),(1,1,4),(0,0,0),(0,1,0) ]

o.dlst = [ (w<<7)|(a<<4)|d for (w,a,d) in o.data ]

ADD_DEV( o, 4, 'add_dev' )
DEV_TEST_4( o, o.add_dev, 4, o.dlst, 'ram_test_4' )

sched.enque( (1,0), sched.enque_just, o.ram_test_4.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 


# データの(w,a,d)のリストは
# w の 0, 1 が 0 はリード、1はライトです
# a はアドレスで0番地か、1番地
# d はライトの時は、足し算させる4ビットの値
# リードの時は、意味ないので0を指定してます

それでは実行してみます。

# 別の端末から
$ ./show /tmp/aa


# 元の端末側に戻って

$ ./nand.py < cmd_add_dev.txt | tee /tmp/aa | sed -e '/ESC/d'
  :


日の字3つのデバイス

RAMを改造して「足し算デバイス」なるものを作ってみましたが、 このようにRAMと同様の端子の構成で、データを書き込んだり、読み出したりするものを、 ここでの「デバイス」と呼ぶことにします。

なので「デバイス」は「RAM」の構成が元になります。

RAMの中のLATCH_NとMUXの間で、データ用の端子がつながってるところを「ブッた切って」、 間になんやかんやつないでみて「デバイス」になります。

なので、端子のつながりを「ブッた切った」だけの、 DEV_BASEクラスを用意しておきます。

このDEV_BASEクラスにDECODER_Lamp_7segクラスを3つつないで、 日の字のランプを3つ並べた「ランプ・デバイス」を作ってみます。

せっかくなので「足し算デバイス」も、DEV_BASEクラスを使って実装しなおしてみます。

nand.py-34-diff.txt

$ cat nand.py-34-diff.txt
--- nand.py-
+++ nand.py
@@ -748,6 +748,37 @@
 		Lamp_7seg(self, 'lamp', latency, pos)
 		conn2_n(7, (self.deco, 'out', 0), (self.lamp, 'inp', 0) )
 
+class LAMP_7_DEV_3(Obj):
+	# en, CLK, A_0, A_1, inp_x, out_x
+	def __init__(self, parent, name='lamp_7_dev_3', latency=10, pos=(0,0)):
+		Obj.__init__(self, parent, name, None, pos)
+
+		DEV_BASE(self, 4, 2, 'dev_base', latency)
+		Pin(self, 'CLK').conn = self.dev_base.CLK
+		Pin(self, 'en').conn = self.dev_base.en
+		Pin(self, 'A_0').conn = self.dev_base.A_0
+		Pin(self, 'A_1').conn = self.dev_base.A_1
+		new_pin_n( 4, self, 'inp' )
+		conn2_n( 4, (self, 'inp', 0), (self.dev_base, 'inp', 0) )
+		new_pin_n( 4, self, 'out' )
+		conn2_n( 4, (self.dev_base, 'out', 0), (self, 'out', 0) )
+
+		n = (1 << 2)
+		for i in range(n):
+			jt = Joint_N( self, 4, name_i('jt', i) )
+			jt.new_pin_conn( self.dev_base, name_i('dev_out', i), direc='from_targ' )
+			jt.new_pin_conn( self.dev_base, name_i('dev_inp', i), direc='to_targ' )
+		x = 12
+		for i in range(3):
+			dl7 = DECODER_Lamp_7seg(self, name_i('deco_lamp_7seg', i), latency, (x,0) )
+			jt = getattr( self, name_i('jt', i) )
+			jt.new_pin_conn( dl7, 'inp', direc='to_targ' )
+			x -= 6
+
+		self.jt_3.jt_0.new_pin().conn = self.deco_lamp_7seg_0.en
+		self.jt_3.jt_1.new_pin().conn = self.deco_lamp_7seg_1.en
+		self.jt_3.jt_2.new_pin().conn = self.deco_lamp_7seg_2.en
+
 class ADD(Obj):
 	# A, B, Y, C
 	def __init__(self, parent, name='add', latency=10):


# DEV_BASE に DECODER_Lamp_7seg を3個つないで
# LAMP_7_DEV_3クラスとしてデバイスに仕立ててます

# 		    +-------------------------+
# 		    |                         |
# 		    |         DEV_BASE        |
# 		    |                         |
# en ---------------|en                       |
# 		    |                         |
# CLK --------------|CLK                      |
# 		    |                         |
# A_0 ----------/---|A_0                      |
# A_1 ----------/---|A_1                      |
# 		    |                         |
# inp_{dn} -----/---|inp_{dn}                 |
# 		    |                         |
# out_{dn} -----/---|out_{dn}                 |
# 		    |                         |       +----------+
# 		    |                         |       |DECODER_  |
# 		    |                         |       | Lamp_7seg|
# 		    |              dev_out_3_0|---@---|en        |
# 		    |                         |   |   |   ---    |
# 		    |              dev_inp_3_0|---.   |  |   |   |
# 		    |                         |       |   ---    |
# 		    |                         |       |  |   |   |
# 		    |                         |       |   ---    |
# 		    |           dev_out_0_{dn}|-/-@-/-|inp_{dn}  |
# 		    |                         |   |   +----------+
# 		    |                         |   |
# 		    |           dev_inp_0_{dn}|-/-.
# 		    |                         |       +----------+
# 		    |                         |       |DECODER_  |
# 		    |                         |       | Lamp_7seg|
# 		    |              dev_out_3_1|---@---|en        |
# 		    |                         |   |   |   ---    |
# 		    |              dev_inp_3_1|---.   |  |   |   |
# 		    |                         |       |   ---    |
# 		    |                         |       |  |   |   |
# 		    |                         |       |   ---    |
# 		    |           dev_out_1_{dn}|-/-@-/-|inp_{dn}  |
# 		    |                         |   |   +----------+
# 		    |                         |   |
# 		    |           dev_inp_1_{dn}|-/-.
# 		    |                         |       +----------+
# 		    |                         |       |DECODER_  |
# 		    |                         |       | Lamp_7seg|
# 		    |              dev_out_3_2|---@---|en        |
# 		    |                         |   |   |   ---    |
# 		    |              dev_inp_3_2|---.   |  |   |   |
# 		    |                         |       |   ---    |
# 		    |                         |       |  |   |   |
# 		    |                         |       |   ---    |
# 		    |           dev_out_2_{dn}|-/-@-/-|inp_{dn}  |
# 		    |                         |   |   +----------+
# 		    |                         |   |
# 		    |           dev_inp_2_{dn}|-/-.
# 		    |                         |
# 		    |                         |
# 		    |              dev_out_3_3|---@--
# 		    |                         |   |
# 		    |              dev_inp_3_3|---.
# 		    |                         |
# 		    +-------------------------+

# アドレス0,1,2に書き込むと、対応する「日の字」に表示します
# アドレス3の0,1,2ビットは、各「日の字」のen端子につながってます
# アドレス3の対応するビットを'1'にしなければ、表示されません


@@ -801,25 +832,48 @@
 	def __init__(self, parent, dbit_n, name='add_dev', latency=10):
 		Obj.__init__(self, parent, name)
 
-		LATCH_N(self, 2, dbit_n, 'latch_2', latency)
-		ADD_N(self, dbit_n, 'add_n', latency)
-		MUX(self, dbit_n, 1, 'mux', latency)
+		DEV_BASE(self, dbit_n, 1, 'dev_base', latency)
+		Pin(self, 'CLK').conn = self.dev_base.CLK
+		Pin(self, 'en').conn = self.dev_base.en
+		Pin(self, 'A_0').conn = self.dev_base.A_0
+		new_pin_n(dbit_n, self, 'inp')
+		conn2_n( dbit_n, (self, 'inp', 0), (self.dev_base, 'inp', 0) )
+		new_pin_n(dbit_n, self, 'out')
+		conn2_n( dbit_n, (self.dev_base, 'out', 0), (self, 'out', 0) )
 
-		Pin(self, 'CLK').conn = self.latch_2.CLK
-		Pin(self, 'en').conn = self.mux.en
-		Pin(self, 'A_0').conn = self.mux.A_0
+		ADD_N(self, dbit_n, 'add_n', latency)
+		conn2_n( dbit_n, (self.dev_base, 'dev_out_0', 0), (self.add_n, 'A', 0) )
+		conn2_n( dbit_n, (self.dev_base, 'dev_out_1', 0), (self.add_n, 'B', 0) )
+		conn2_n( dbit_n, (self.add_n, 'Y', 0), (self.dev_base, 'dev_inp_0', 0) )
+		self.add_n.C.conn = self.dev_base.dev_inp_1_0
+


# ADD_DEVをDEB_BASEを使って実装しなおしてみました


+class DEV_BASE(Obj):
+	# en, CLK, A_ax, inp_dx, out_dx, dev_out_x_dx, dev_inp_x_dx
+	def __init__(self, parent, dbit_n, abit_n, name='dev_base', latency=10):
+		Obj.__init__(self, parent, name)
 
-		self.mux.deco_out_0.conn = self.latch_2.sel_0
-		self.mux.deco_out_1.conn = self.latch_2.sel_1
+		n = 1 << abit_n
+		LATCH_N(self, n, dbit_n, 'latch_n', latency)
+		Pin(self, 'CLK').conn = self.latch_n.CLK
+		new_pin_n( dbit_n, self, 'inp' )
+		conn2_n( dbit_n, (self, 'inp', 0), (self.latch_n, 'inp', 0) )
+		for i in range(n):
+			nm = name_i('dev_out', i)
+			new_pin_n( dbit_n, self, nm )
+			conn2_n( dbit_n, (self.latch_n, name_i('out', i), 0), (self, nm, 0) )
 
-		new_pin_n(dbit_n, self, 'inp')
-		conn2_n( dbit_n, (self, 'inp', 0), (self.latch_2, 'inp', 0) )
-		conn2_n( dbit_n, (self.latch_2, 'out_0', 0), (self.add_n, 'A', 0) )
-		conn2_n( dbit_n, (self.latch_2, 'out_1', 0), (self.add_n, 'B', 0) )
-		conn2_n( dbit_n, (self.add_n, 'Y', 0), (self.mux, 'inp_0', 0) )
-		self.add_n.C.conn = self.mux.inp_1_0
-		new_pin_n(dbit_n, self, 'out')
+		MUX(self, dbit_n, abit_n, 'mux', latency)
+		Pin(self, 'en').conn = self.mux.en
+		new_pin_n( abit_n, self, 'A' )
+		conn2_n( abit_n, (self, 'A', 0), (self.mux, 'A', 0) )
+		new_pin_n( dbit_n, self, 'out' )
 		conn2_n( dbit_n, (self.mux, 'out', 0), (self, 'out', 0) )
+
+		conn2_n( n, (self.mux, 'deco_out', 0), (self.latch_n, 'sel', 0) )
+		for i in range(n):
+			nm = name_i('dev_inp', i)
+			new_pin_n( dbit_n, self, nm )
+			conn2_n( dbit_n, (self, nm, 0), (self.mux, name_i('inp', i), 0) )
 
 class DEV_TEST_4(Obj):
 	# en
$ 


# 差分にADD_DEVクラスの更新前のものが食い込んでますが、
# DEV_BASEクラスの新規追加分です
# RAMクラスの構成で、LATCH_NとMUX間のデータの接続を「ブッた切って」
# dev_out_x_dx, dev_inp_x_dx 端子にして外に見せてます

続いて確認用のお試しコマンド。

cmd_lamp_dev.txt

$ cat cmd_lamp_dev.txt
o.pos = (4,4)

o.data = [ (1,0,1),(1,1,2),(1,2,3),(1,3,7) ]
o.data += [ (1,0,10),(1,1,11),(1,2,12),(1,3,0) ]
o.dlst = [ (w<<7)|(a<<4)|d for (w,a,d) in o.data ]

LAMP_7_DEV_3( o, 'lamp_dev', pos=(28,5) )
DEV_TEST_4( o, o.lamp_dev, 3, o.dlst, 'dev_test_4' )

sched.enque( (1,0), sched.enque_just, o.dev_test_4.en.set, 'H' )
sched.enque( (120,0), sched.quit )

# EOF
$ 

それでは実行してみます。

# 別の端末から
$ ./show /tmp/aa


# 元の端末側に戻って

$ ./nand.py < cmd_lamp_dev.txt | tee /tmp/aa | sed -e '/ESC/d'
  :


バス

複数のデバイス間で1つのラッチを共有させれば、 あるデバイスからラッチに書き込んだデータを、 ラッチから別のあるデバイスへと読み出せるはず。

これって「バス」ってことでしょうか。

複数のデバイスの出力から、1つだけ選んで共有する1つのラッチに書き込むには、 MUXでデータの端子を選べばよさそうです。

共有する1つのラッチの出力を、あるデバイスに書き込むには、 あるデバイスにだけ書き込みのCLKを入れてやれば、よさそうです。

デバイスのもつアドレス空間とは別に、 デバイスそのものに、「デバイス・アドレス」を割り振っておいて、 「バス」にデバイス・アドレスを指定することで、「共有ラッチ」と「指定のデバイス」の間で、 データのやりとりをさせてみます。

書き込み用のCLK信号はどうすべきか? 「デバイスからラッチ」方向に書き込むときは「ラッチ」に、 「ラッチからデバイス」方向に書き込むときは「デバイス」に与える必要があります。

「バス」に、TO_DEVという端子を用意して、 これが'H'のときは「デバイス」への書き込み、 'L'のときは「ラッチ」への書き込みということにしておきます。

あれこれ考えてみたあげく、BUSを構成するために2つの部品を追加します。

1つはDESEL2クラス。

機能的には、デコーダにJointANDSetをつないだやつの、アドレス1ビット版です。 1ビットのデータを、1ビットのアドレスで2つの出力に振り分けるだけのものです。

CLKを「ラッチ」に振るか「デバイス」に振るかで、使います。

もう1つはDEMUXクラス。

MUXと反対の動作ですが、MUXとセットで使う前提にしてて、デコーダはMUX側のdeco_out出力を使います。

例えば4ビットのデータについて、3ビットのデバイス・アドレス空間があったとして、 MUXのdeco_outは(1<<3)で8本。これをDEMUXのsel_0からsel_7に入れます。

DEMUXに入ってる4ビットのデータは、 sel_0からsel_7で指定された1つのデバイスに対して、 DEMUXの出力 out_{指定デバイス番号}_{0から3} の4ビットに出力されます。

ややこしいですが、図で見ると「わりと」単純です

              +-------------+
              |             |
              | JointANDSet |
              |             |
inp_0 --------|inp          |
              |        out_n|---/--- out_n_0
sel_n --/--@--|sel_n        |
           |  |             |
           |  +-------------+
           |  +-------------+
           |  |             |
           |  | JointANDSet |
           |  |             |
inp_1 --------|inp          |
           |  |        out_n|---/--- out_n_1
           @--|sel_n        |
           |  |             |
           |  +-------------+
           :         :
           |  +-------------+
           |  |             |
           |  | JointANDSet |
           |  |             |
inp_dn-1 -----|inp          |
           |  |        out_n|---/--- out_n_dn-1
           .--|sel_n        |
              |             |
              +-------------+

nand.py-35-diff.txt

$ nand.py-35-diff.txt
--- nand.py-
+++ nand.py
@@ -468,6 +468,21 @@
 		new_pin_n(n, self, 'out')
 		conn2_n( n, (self.and_set, 'out', 0), (self, 'out', 0) )
 
+class DESEL2(Obj):
+	# inp, A_0, out_0, out_1
+	def __init__(self, parent, name='desel2', latency=10):
+		Obj.__init__(self, parent, name)
+
+		PosiNega(self, 'posi_nega', latency)
+		Pin(self, 'A_0').conn = self.posi_nega.inp
+
+		JointANDSet(self, 2, 'jas', latency)
+		Pin(self, 'inp').conn = self.jas.inp
+		self.posi_nega.nQ.conn = self.jas.sel_0
+		self.posi_nega.Q.conn = self.jas.sel_1
+		self.jas.out_0.conn = Pin(self, 'out_0')
+		self.jas.out_1.conn = Pin(self, 'out_1')
+ 
 class GATE(Obj):
 	# inp_x, en, out_x
 	def __init__(self, parent, n, name='gate', latency=10):


# DESEL2クラス追加です
# 1つのinp入力を、1ビットのA_0によって、出力out_0かout_1に振り分けます


@@ -522,6 +537,26 @@
 				Pin(self, nm).conn = getattr( aso, name_i('inp', i) )
 			aso.out.conn = Pin( self, name_i('out', di) )
 
+class DEMUX(Obj):
+	# sel_x, inp_dx, out_x_dx
+	def __init__(self, parent, dbit_n, n, name='demux', latency=10):
+		Obj.__init__(self, parent, name)
+
+		new_pin_n( dbit_n, self, 'inp' )
+
+		new_pin_n( n, self, 'sel' )
+		Joint_N( self, n, 'jt_sel' )
+		self.jt_sel.new_pin_conn( self, 'sel', direc='from_targ')
+
+		for i in range(dbit_n):
+			jas = JointANDSet(self, n, 'jas', latency)
+			getattr( self, name_i('inp', i) ).conn = jas.inp
+			self.jt_sel.new_pin_conn( jas, 'sel', direc='to_targ' )
+
+			for j in range(n):
+				nm = name_i('out', j)
+				getattr( jas, nm ).conn = Pin(self, name_i( nm, i ) )
+
 class ROM_D1(Obj):
 	# en, A_x, D_0
 	def __init__(self, parent, abit_n, dlst=(), name='rom_d1', latency=10):


# MUXの逆ですが、デコーダはMUX側のdeco_outを使います


@@ -828,7 +863,7 @@
 		c.conn = Pin(self, 'C')
 
 class ADD_DEV(Obj):
-	# en, CLK, A_0, D_x
+	# en, CLK, A_0, inp_x, out_x
 	def __init__(self, parent, dbit_n, name='add_dev', latency=10):
 		Obj.__init__(self, parent, name)
 

# コメント箇所をちょいと修正


@@ -925,6 +960,84 @@
 		self.jt_nQ.new_pin().conn = self.and_w.inp_b
 		self.and_w.out.conn = Joint(self, 'jt_wrt', None, (x,y), 'W').new_pin()
 		self.jt_wrt.new_pin().conn = self.dev.CLK
+
+class BUS(Obj):
+	# TO_DEV, CLK, DEV_CLK_{dev#}, en, dev_en_{dev#}
+	# inp_{dev#}_{dbit#}, out_{dev#}_{dbit#}, DEV_A_{dev_abit#}, AI_{abit#}, AO_{dev#}_{abit#}, 
+	def __init__(self, parent, dbit_n, abit_n, dev_abit_n, name='bus', latency=10):
+		Obj.__init__(self, parent, name)
+
+		self.dbit_n = dbit_n
+		self.abit_n = abit_n
+		self.dev_abit_n = dev_abit_n
+		dev_n = (1 << dev_abit_n)
+
+		DESEL2(self, 'desel2', latency)
+		Pin(self, 'TO_DEV').conn = self.desel2.A_0
+		Pin(self, 'CLK').conn = self.desel2.inp
+
+
+		MUX(self, dbit_n, dev_abit_n, 'mux', latency)
+
+		Pin(self, 'en').conn = Joint(self, 'jt_en').new_pin()
+		self.jt_en.new_pin().conn = self.mux.en
+
+		new_pin_n(dev_abit_n, self, 'DEV_A')
+		conn2_n( dev_abit_n, (self, 'DEV_A', 0), (self.mux, 'A', 0) )
+
+		for i in range(dev_n):
+			nm = name_i('inp', i)
+			new_pin_n( dbit_n, self, nm )
+			conn2_n( dbit_n, (self, nm, 0), (self.mux, nm, 0) )
+
+
+		LATCH(self, dbit_n, 'latch', latency)
+
+		self.desel2.out_0.conn = self.latch.CLK
+		conn2_n( dbit_n, (self.mux, 'out', 0), (self.latch, 'inp', 0) )
+
+
+		DEMUX(self, dbit_n + abit_n + 2, dev_n, 'demux', latency)
+
+		conn2_n( dev_n, (self.mux, 'deco_out', 0), (self.demux, 'sel', 0) )
+		self.jt_en.new_pin().conn = getattr( self.demux, name_i('inp', dbit_n + abit_n) )
+
+		self.desel2.out_1.conn = getattr( self.demux, name_i('inp', dbit_n + abit_n + 1) )
+
+		conn2_n( dbit_n, (self.latch, 'out', 0), (self.demux, 'inp', 0) )
+
+		new_pin_n( abit_n, self, 'AI' )
+		conn2_n( abit_n, (self, 'AI', 0), (self.demux, 'inp', dbit_n) )
+
+		for i in range(dev_n):
+			nm = name_i('out', i)
+
+			new_pin_n( dbit_n, self, nm )
+			conn2_n( dbit_n, (self.demux, nm, 0), (self, nm, 0) )
+
+			anm = name_i('AO', i)
+			new_pin_n( abit_n, self, anm )
+			conn2_n( abit_n, (self.demux, nm, dbit_n), (self, anm, 0) )
+
+			getattr( self.demux, name_i(nm, dbit_n + abit_n) ).conn = Pin( self, name_i('dev_en', i) )
+			getattr( self.demux, name_i(nm, dbit_n + abit_n + 1) ).conn = Pin( self, name_i('DEV_CLK', i) )
+
+	def conn(self, dev_addr, dev, dev_pin_o='out', dev_pin_i='inp'):
+		abit_n = min( get_num(dev, 'A'), self.abit_n )
+		conn2_n( abit_n, (self, name_i('AO', dev_addr), 0), (dev, 'A', 0) )
+
+		if hasattr(dev, 'en'):
+			getattr( self, name_i('dev_en', dev_addr) ).conn = dev.en
+
+		if dev_pin_o:
+			dbit_n = min( get_num(dev, dev_pin_o), self.dbit_n )
+			conn2_n( dbit_n, (dev, dev_pin_o, 0), (self, name_i('inp', dev_addr), 0) )
+
+		if dev_pin_i:
+			dbit_n = min( get_num(dev, dev_pin_i), self.dbit_n )
+			conn2_n( dbit_n, (self, name_i('out', dev_addr), 0), (dev, dev_pin_i, 0) )
+			if hasattr(dev, 'CLK'):
+				getattr( self, name_i('DEV_CLK', dev_addr) ).conn = dev.CLK
 
 class Sched:
 	def __init__(self):
$ 


# BUS本尊です
# MUX, LATCH, DEMUX で構成してます
# デバイスをつなぐとき用に conn()メソッドを作ってます
# デバイス側の端子の有無や上限を調べて、「できるだけ」つなごうとします

続いて確認用のお試しコマンド。

これまで作った4つのデバイス、ROM, RAM, ADD_DEV, LAMP_7_DEV_3 を、 データビット幅4ビットで用意して、バスにつないでます。

8ビットROM_SEQを用意して、 バスに対して「読み書きの方向」、「デバイス・アドレス」と「デバイス内のアドレス」を出力します。

「読み書きの方向」は、バスのTO_DEV端子を'H'にするか'L'にするかです。

  en
  |
  @--------------------.
  |                    |
+------------+       +------------------+                   
| en         |       | en               |                   
|            |       |                  |                   
| ROM_SEQ    |       | BUS              |       +----------+
| 8 bit      |       | 4 bit            |       |          |
|            |       | 3 dev_abit       |       | ROM      |
|            |       |                  |       |          |
|            |       |          dev_en_0|-------|en        |
|            |       |         DEV_CLK_0|-      |          |
|       out_0|-------|AI_0        AO_0_x|---/---|A_x       |
|       out_1|-------|AI_1       inp_0_x|---/---|D_x       |
|       out_2|-------|AI_2       out_0_x|-/-    |          |
|       out_3|-------|AI_3              |       +----------+
|            |       |                  |
|            |       |                  |       +----------+
|            |       |                  |       |          |
|            |       |                  |       | RAM      |
|            |       |                  |       |          |
|            |       |                  |       |          |
|            |       |          dev_en_1|-------|en        |
|       out_4|-------|DEV_A_0  DEV_CLK_1|-------|CLK       |
|       out_5|-------|DEV_A_1     A_1_x |---/---|A_x       |
|       out_6|-------|DEV_A_2    inp_1_x|---/---|out_x     |
|            |       |           out_1_x|---/---|inp_x     |
|       out_7|-------|TO_DEV            |       |          |
|            |       |                  |       +----------+
|          nQ|-------|CLK               |
|            |       |                  |       +----------+
+------------+       |                  |       |          |
                     |                  |       | ADD_DEV  |
                     |                  |       |          |
                     |          dev_en_2|-------|en        |
                     |         DEV_CLK_2|-------|CLK       |
                     |             A_2_0|       |A_0       |
                     |           inp_2_x|---/---|out_x     |
                     |           out_2_x|---/---|inp_x     |
                     |                  |       |          |
                     |                  |       +----------+
                     |                  |
                     |                  |       +-------------------+
                     |                  |       |                   |
                     |                  |       |  ---   ---   ---  |
                     |                  |       | |   | |   | |   | |
                     |                  |       |  ---   ---   ---  |
                     |                  |       | |   | |   | |   | |
                     |                  |       |  ---   ---   ---  |
                     |                  |       |                   |
                     |                  |       |      LAMP_7_DEV_3 |
                     |          dev_en_3|-------|en                 |
                     |         DEV_CLK_3|-------|CLK                |
                     |             A_3_x|---/---|A_x                |
                     |           inp_3-x|---/---|out_x              |
                     |           out_3-x|---/---|inp_x              |
                     |                  |       |                   |
                     +------------------+       +-------------------+

                     BUSクラスお試しの構成

この環境のデータ4ビットのデバイスを使って、3つの8ビットの値を足し算してみます。

cmd_add3.txt

$ cat cmd_add3.txt
o.pos = (4,4)

o.data =  [ (0,0,7),(1,3,3) ]
o.data += [ (0,0,0),(1,2,0),(0,0,2),(1,2,1),(0,2,1),(1,1,0),(0,2,0),(1,2,0) ]	# rom0 -> add0, rom2 -> add1, add1 -> ram0, add0 -> add0
o.data += [ (0,0,4),(1,2,1),(0,2,1),(1,1,1),(0,2,0),(1,3,0) ]			# rom4 -> add1, add1 -> ram1, add0 -> lamp0
o.data += [ (0,1,0),(1,2,0),(0,1,1),(1,2,1),(0,2,0),(1,2,0) ]			# ram0 -> add0, ram1 -> add1, add0 -> add0
o.data += [ (0,0,1),(1,2,1),(0,2,1),(1,1,2),(0,2,0),(1,2,0) ]			# rom1 -> add1, add1 -> ram2, add0 -> add0
o.data += [ (0,0,3),(1,2,1),(0,2,1),(1,1,3),(0,2,0),(1,2,0) ]			# rom3 -> add1, add1 -> ram3, add0 -> add0
o.data += [ (0,0,5),(1,2,1),(0,2,1),(1,1,4),(0,2,0),(1,3,1) ]			# rom5 -> add1, add1 -> ram4, add0 -> lamp1
o.data += [ (0,1,2),(1,2,0),(0,1,3),(1,2,1),(0,2,0),(1,2,0) ]			# ram2 -> add0, ram3 -> add1, add0 --> add0
o.data += [ (0,1,4),(1,2,1),(0,2,0),(1,3,2) ]					# ram4 -> add1, add0 -> lamp2
o.data += [ (0,0,0) ] * 14 # none sense

o.dlst = [ (to_dev<<7)|(dev_addr<<4)|a for (to_dev, dev_addr, a) in o.data ]
ROM_SEQ( o, 8, 6, o.dlst, 'rom_seq' )

BUS( o, 4, 3, 2, 'bus' )
conn2_n( 4, (o.rom_seq, 'out', 0), (o.bus, 'AI', 0) )
conn2_n( 3, (o.rom_seq, 'out', 4), (o.bus, 'DEV_A', 0) )
o.rom_seq.out_7.conn = o.bus.TO_DEV
o.rom_seq.nQ.conn = o.bus.CLK

o.rom_dlst = [ 7,8, 8,9, 9,1 , 3,7 ]
ROM( o, 4, 3, o.rom_dlst, 'rom' )
o.bus.conn( 0, o.rom, 'D', None )

RAM( o, 4, 3, 'ram' )
o.bus.conn( 1, o.ram )

ADD_DEV( o, 4, 'add_dev' )
o.bus.conn( 2, o.add_dev )

LAMP_7_DEV_3( o, 'lamp_dev' )
o.bus.conn( 3, o.lamp_dev )

Pin( o, 'en' ).conn = Joint( o, 'jt_en' ).new_pin()
o.jt_en.new_pin().conn = o.rom_seq.en
o.jt_en.new_pin().conn = o.bus.en

sched.enque( (1,0), sched.enque_just, o.en.set, 'H' )
sched.enque( (480,0), sched.quit )

# EOF
$ 

4ビットのROMの先頭から、下位4ビット、上位4ビットの順で、足し算する3組の8ビットデータを用意してます。 (リトルエンディアンです)

7,8,8,9,9,1 なので 0x87 + 0x98 + 0x19 を目指してます。

末尾のデータ 7 は、日の字ランプデバイスを有効にするために書き込む値として、用意してます。

その前の 3 は、使ってません。;-p)

ROM_SEQに用意してるデータが「プログラムのような」ソフトウェアですね。

あまりにも呪文な雰囲気ですが... 説明をば。

なんとも面倒な。

それでは実行してみます。

# 別の端末から
$ ./show /tmp/aa


# 元の端末側に戻って

$ ./nand.py < cmd_add3.txt | tee /tmp/aa | sed -e '/ESC/d'
  :

初期化も実行も、かなり時間がかかります。

答えあわせは、

$ gdb
(gdb) p /x 0x87+0x98+0x19
$1 = 0x138
(gdb) quit
$ 

最初に日の字デバイスの表示を有効にする書き込みまで140秒かかってます。 2分以上も変化なしだ('_';


工事中...