The Fuzzball (最古の NTP リファレンス実装) を読む[中編] - インターネット時刻同期の歴史(その12)
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.DAT
と NB.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.DAT
と NB.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:59TIME
… 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 実行すると最低一分待たされるとかそういう事は無いわけだ。
しかし RADCLK
も TCUCLK
と同じで最後は RT-11 に丸投げだから The Fuzzball の tick は変わらず 60Hz なんだよねこれ、これでクロック 1000Hz 達成といっていいのだろうか、うーん。
次回
次回も 1000Hz の謎にせまるべく本陣 NTPSRV.MAC に攻め入るしかないのか、その前に完全に飽きたから更新止まる可能性がだいぶ増してきた。