SETCLK.MAC つづき

前回後回しにした RADCLK を読んでいくかね。

 67 .IIF NDF,SLUREG SLUREG == 176560 ;default slu device address
...
 83 ;
 84 ; Clock descriptor block
 85 ;
 86 .	=	0
...
 90 NB.STA:	.BLKB	1		;initial state
 91 NB.CNB:	.BLKB	1		;timecode length
...
 93 NB.POL	=	.		;beginning of poll string
 94 ;
 95 ; Dlv11/dl11 device register block
 96 ;
 97 .	=	0
 98 INPCSR:	.BLKW	1		;input status register
 99 INPBUF:	.BLKW	1		;input buffer register
100 OUTCSR:	.BLKW	1		;output status register
101 OUTBUF:	.BLKW	1		;output buffer register
185 ;
186 ; Radio clock
187 ;
188 RADCLK:	.MRKT	#ARGBLK,#DELAY,#TCU10,#1 ;set poll timeout
189 	MOV	DEVREG,R2	;get register base
190 	BNE	4$		;branch if given
191 	MOV	#SLUREG,R2	;use default
192 	TSTB	OUTCSR(R2)	;test live slu
193 4$:	MOVB	#NB.POL,R1	;send poll sequence
194 	ADD	R5,R1
195 1$:	MOVB	(R1)+,R0
196 	BEQ	3$		;branch if done
197 2$:	TST	FLAG
198 	BNE	NBS99		;branch if timeout or dead slu register
199 	TSTB	OUTCSR(R2)
200 	BPL	2$		;branch if not ready
201 	MOVB	R0,OUTBUF(R2)	;send char
202 	BR	1$
203 ;
204 3$:	MOV	#TIMCOD,R1	;point to timecode
205 	MOVB	NB.STA(R5),NBSFLG ;set initial state
206 	MOVB	NB.CNB(R5),NBSCNB
207 	MOV	#COM31,ERRPTR	;no reply

また新しい #SLUREG というデバイスが現れた、これは Serial Line Unit(SLU) というもの。 いくつかのモデルがあるけど代表的なのが DL-11 ってやつ。

今でいうシリアル通信ポート増設カードである、そこに繋いでるデバイスと通信しようと初期化してるコードすかね。

シリアル通信ポートに一体何のデバイスを繋いでいるのか

答えはソースのコメントにあった。

  • Spectracom 8170 and Netclock/II WWVB receivers
  • Kinemetrics TrueTime receivers (all models)
  • Heath GC-1000 WWV/WWVH receiver
  • Precision Standard Time/Traconex 1020 WWV/WWVH reciever

これらは NBS(National Bureau of Standards 国立標準局) つまり現在の NIST(National Institute of Standards and Technology アメリカ国立標準技術研究所) によって運営される WWV / WWVH / WWVB のいずれかのラジオ局を受信するレシーバーである。

これらのラジオ局からは何の毒電波が発せられてるのか、毒蝮三太夫のミュージックプレゼントか、はたまた実況椎野茂のエキサイトベースボールか。

【時報】時報【時報】

これらのラジオ局で配信されているのは時報である。 北米で販売されてる電波時計はこのラジオを受信して時刻合わせを行っている。

ちなみに日本にも NICT(National Institute of Information and Communications Technology 情報通信研究機構) による JJY という WWVB と同種のラジオ局が運営されており、国内で販売されてる電波時計はこれを受信しているはずである。

タイムコード仕様についてはイカリング参照。

WWVB を例にとると

  • 有音部と無音部で構成される
  • 軽音部は社会不適合者で構成される
  • 音の高低ではなく長さに意味がある
  • タイムフレームは 1 フレーム 60 秒である
  • 1 秒毎に以下のいずれかの音声を送信する
    • マーカー音声 (0.2s)
    • 二進数の 0 を表す音声 (0.8s)
    • 二進数の 1 を表す音声 (0.5s)
  • 0 秒時にフレームマーカー (FRM)
  • 9/19/29/39/49/59 秒時にそれぞれポジションマーカー (P1/P2/P3/P4/P5/P0)
  • 4/14/24/34/44/54 秒時は常に未使用で 0
  • 分 … 1/2/3/5/6/7/8 秒時の 7bit
  • 秒 … 12/13/15/16/17/18 秒時の 6bit
  • 年初からの日数 … 22/23/25/26/27/28/30/31/32/33 秒時の 10bit
  • DUT1(世界時 UT1 と世界協定時 UTC の差) の符号 … 36/37/38 秒時の 3bit
  • DUT1 の絶対値 … 40/41/42/43 秒時の 4bit
  • 年下 2 桁 … 45/46/47/48/50/51/52/53 秒時の 8bit
  • 閏年 … 55 秒時の 1bit
  • 閏秒 … 56 秒時の 1bit
  • 夏時間 … 57/58 秒時の 2bit

ちゅう感なんすかね、まぁレシーバー側で勝手にデコードしてくれるしプログラマはデコード結果のフォーマットだけ知ってればいい。 俺は無銭おじさんではあるが無線マニアではないし GNU Radio による SDR(Software Defined Radio) でレシーバー自作とか興味無いからしかたないね。

局によってフォーマットが違うのと、レシーバーによるデコード結果文字列にも差異がある。

 47 ; Spectracom format 0:	<cr><lf>q  ddd hh:mm:ss  tz=zz<cr><lf> (26 char)
 48 ; Spectracom format 2:	<cr><lf>iqyy ddd hh:mm:ss.xxx ld (26 char)
 49 ; Truetime format:	<cr><lf><ctrl-a>ddd:hh:mm:ssq<cr> (16 char)
 50 ; Heath format:		<cr>hh:mm:ss.x     dd/mm/yr<cr>	(25 char)
 51 ; PST/Traconex format:	hh:mm:ss.fff  yy/dd/mm/ddd status<cr> (50 char)

フォーマットについては教授のページに より詳細な情報 がある。

この差異を判定するために CLKREC の中で CLKJMP というテーブルで処理を分岐しまくっている。 この手の処理をアセンブラで書かざるを得ない時代にプログラマやってなくてほんとうによかった。

208 ;
209 ; Receive timecode
210 ;
211 CLKREC:	TST	FLAG		;get char
212 	BNE	NBS99		;branch if timeout
213 	TSTB	INPCSR(R2)	
214 	BPL	CLKREC		;branch if not ready
215 	MOVB	INPBUF(R2),R4
216 	BIC	#^C177,R4
217 	MOVB	NBSFLG,R0	;branch to state segment
218 	ASL	R0
219 	MOV	CLKJMP(R0),PC
220 ;
221 NBS99:	JMP	CNVERR		;handrail
...
657 ;
658 ; Data segment
659 ;
660 ; Clock dispatch table
661 ;
662 CLKJMP:	.WORD	CLKREC		;0 idle
663 	.WORD	NBS01		;1 spectracom/heath first char
664 	.WORD	NBS22		;2 spectracom/heath time/date string
665 	.WORD	CLKREC		;3 wait for process decode
666 	.WORD	NBS30		;4 truetime first char
667 	.WORD	NBS31		;5 truetime time/date string
668 	.WORD	NBS20		;6 pst first char
669 	.WORD	NBS21		;7 pst time string
670 	.WORD	NBS21		;8 pst date string
671 	.WORD	NBS25		;9 status string
...
757 TIMCOD:	.ASCIZ	'<nothing received>' ;timecode
758 .	=	TIMCOD+64.	;timecode buffer
...
759 ;
760 ; Program variables
761 ;
...
769 NBSFLG:	.BLKB	1		;nbs state
770 NBSCNB:	.BLKB	1		;nbs timecode count
...

CLKJMP でジャンプする先の NBS* ラベルについては特に解説する必要も無いので省略、要はデコード結果文字列の中から

  • 日付
    • ddd (年始からの日数)
    • あるいは mm/dd/yr (月/日/年下二桁)
  • 時刻 hh:mm:ss.f (.f はオプション)

が出現する場所を探して見つかったら NB.DATNB.TIM にオフセットとして保持するだけだもの。

83 ;
84 ; Clock descriptor block
85 ;
86 .	=	0
87 NB.TIM:	.BLKB	1		;time offset
88 NB.DAT:	.BLKB	1		;date offset

いまさらレガシーコードを完全理解しようなんて思わず普通に高級言語使おうね。

ただし、どこにジャンプしても必ず .GTIM システムコールで現在時刻を取得し REFTIME に保存してることだけは覚えておくように、後で出てくるよ。

231 NBS31:	CMPB	R4,#CR		;state 5. is this on-time char
232 	BNE	NBS34		;branch if no
233 	.GTIM	#ARGBLK,#REFTIM	;yes. update reference timestamp
...
240 NBS20:	.GTIM	#ARGBLK,#REFTIM	;yes. update reference timestamp
...
759 ;
760 ; Program variables
761 ;
...
765 REFTIM:	.BLKW	2		;reference time

さてお次、NB.DATNB.TIM を RT-11 の日付時刻形式に変換する処理が CNVCLK である。

270 ;
271 ; Convert time code to internal format.
272 ;
273 CNVCLK:	.CMKT	#ARGBLK,#0	;stop the bubble machine
274 	MOV	#COM34,ERRPTR	;not synchronized
275 	MOVB	NB.OFS(R5),R1	;is status okay
276 	CMPB	TIMCOD(R1),#'?
277 	BEQ	CNVERR		;branch if no	
278 	MOV	#COM35,ERRPTR	;bad date
279 	MOVB	NB.DAT(R5),R1	;yes. convert date to internal format
280 	ADD	#TIMCOD,R1
281 	MOVB	NB.FMT(R5),R0
282 	JSR	PC,NBSDAT
283 	BCS	CNVERR		;branch if error
284 	MOV	R0,DATE
285 	MOV	#COM36,ERRPTR	;bad time
286 	.GTIM	#ARGBLK,#TIME	;compute offset since reference tick
287 	SUB	REFTIM+2,TIME+2
288 	SBC	TIME
289 	SUB	REFTIM,TIME
290 	MOVB	NB.TIM(R5),R1	;convert time-of-day to internal format
291 	ADD	#TIMCOD,R1
292 	JSR	PC,NBSTIM
293 	BCS	CNVERR		;branch if error
294 	ADD	R1,TIME+2	;compute update time
295 	ADC	TIME
296 	ADD	R0,TIME
297 	.PRINT	#TIMCOD		;print timecode
298 	.SDTTM	#ARGBLK,#DATE	;set system date and time
299 	JMP	EXIT
300 ;
301 CNVERR:	.CMKT	#ARGBLK,#0	;stop the bubble machine
302 	.PRINT	#TIMCOD		;print timecode
303 	.PRINT	ERRPTR		;timecode failed
304 	JMP	EXIT
...

変換処理の実体は

  • 日時 … NBSDAT
  • 時刻 … NBSTIM

なんだけど、前回 RT-11 の日付時刻 48bit のフォーマットについては学習済なのでコメントだけ読んで終わりとする。

342 ;
343 ; NBSDAT (nbs) convert date in ddd or mm/dd/yr format
344 ; r0 = format code, r1 = field pointer; returns r0 = date (rt-11
345 ; format), cc = c if error
346 ;
347 NBSDAT:	MOV	R2,-(SP)	;save
...
422 ;
423 ; NBSTIM (nbs) convert time in hh:mm:ss.f format (.f is optional)
424 ; R1 = field pointer, returns r0-r1 = time (milliseconds), cc = c if error
425 ;
426 NBSTIM:	MOV	R2,-(SP)	;save
...

いまさらレガシーコードを完全理解しようなんて思わず普通に高級言語使おうね(二度目)。

時刻については単純に NB.TIM の変換結果を使っていないことに注意である。

  • REFTIME … レシーバーからデータを読み込んだ直後の時刻
  • TIME … デコード結果のパース及び変換処理がすべて終わった直後の時刻

のふたつの .GTIM の結果があることにお気づきだろうか。

この差を変換結果に足すことで、ここまでの処理にかかった計算時間分の補正を行っているちゅうこと。

はい RADCLK 完全に理解、つーか勘のいい人ならこの部分にバグあるの即気づくよね。 なんせ同じようなコードが timed(8) にもありそっちはきちんと対策されてたからね。

そう .GTIM #ARGBLK,#REFTIM.GTIM #ARGBLK,#TIME の間に日を跨ぐと補正時間が負の値になるのだ。

例えば

  • REFTIME … 23:59:59
  • TIME … 00:00:00

なら現在時刻から 23:59:59 引かれちゃうんだよね、本当は 00:00:01 足さないとならないのに。

ただ UNIX と違って時刻をマイナス 24 時間しても日付はマイナス 1 日にならないから発覚しないのだ。 本当は 1 秒進むはずが 1 秒戻るだけだし実際には秒未満の tick 単位だしよっぽどきっちりテストしてないと発覚しないよね。

さて俺が死んだらあの世で Mills 教授に報告するバグが 2 つ目になってしまった。 今の俺なら Donald Knuth 先生の小切手でも換金して酒代にするぜ!

はい今日はここまで、 Joy Division/Transmission の歌詞のようにラジオ生放送に合わせてタコ踊りに成功した。

Lou Reed も The Velvet Underground/Rock N’ Roll で NY のラジオ局に周波数を合わせればいつだって最高の音楽が流れてくるしロックンロールでクソみたいな人生も救われるとおっしゃられている。 あー生活に余裕があれば Wim Wenders の PERFECT DAYS 観に行きたいね…

ようやくミリ秒単位の話が出てきたけれど

タイムコードそのものでは秒単位でしか時刻補正できんし時刻合わせに最低でも 1 フレーム つまり 1分かかるにも関わらず、一部の機種でデシ秒とかミリ秒の数字を返してくるからどこからこの精度が出てくるのかとしばし悩んだ。

でも Heath GC-1000 とかの現物写真検索したら疑問氷解、ラジオチューナーみたいなの想像してたんだけど見た目ふつうの置時計ですわこれ。 レシーバーもまた時計であって内蔵のクオーツでクロック作ってるわけだ、そらそうか。

なので SETCLK.SAV 実行すると最低一分待たされるとかそういう事は無いわけだ。

しかし RADCLKTCUCLK と同じで最後は RT-11 に丸投げだから The Fuzzball の tick は変わらず 60Hz なんだよねこれ、これでクロック 1000Hz 達成といっていいのだろうか、うーん。

次回

次回も 1000Hz の謎にせまるべく本陣 NTPSRV.MAC に攻め入るしかないのか、その前に完全に飽きたから更新止まる可能性がだいぶ増してきた。