2026/06/21

最強のOS-9マシン復活プロジェクト ~バスブリッジ回路の検討~

 FM-11のシステムバスとFM16βのシステムバスの呼称

FM-11のシステムバスは、仕様書において「メインバス」や「本体拡張バス」などと表記されており、正式名称は不明です。 ここでは簡単のためとFM16βのバスと区別しやすいように「FM-100バス」と呼ぶことにします。

FM16βのバスは「FM130バス」もしくは「FM-130バス」と呼ばれています。ここではFM-11のバスの呼称に合わせて「FM-130バス」と呼ぶことにします。 

 二つのバスの比較

FM-100バスとFM-130バスを比較すると一目瞭然ですが、FM-130バスはFM-100バスを拡張したものです。主な変更点(拡張点)は以下の通りです。

  1. アドレスバスを20bitから24bitに拡張して最大メモリ容量を16MBとした
  2. データバスを16bitに拡張して16bit CPUに対応した 
  3. バイト単位でアドレスが連続して割り当てられている8ビットのI/Oデバイスが16bit CPUでも動作できるように、データバスのバイトスワップ機能を追加した(I/OはFM-11の上位互換)
  4. CPU切り替え関係の信号の変更 

上記3.の「バイトスワップ機能」ですが、「スワップ機能」というよりは「ステアリング機能」と呼ぶのがふさわしいかもしれません。

FM-100バスの信号は、CPU切り替えに関するものを除くとほとんどそのままFM-130バスで使用されています。バスのタイミングを制御する信号も*EBやRWBです。

バスブリッジ回路の検討

 以上より、バスブリッジ回路(CPU切り替え機能を除く)に必要なのはFM-100バスから追加された必須の信号の生成です。以下の三種類があります。

  1. アドレスバスの上位4bit
  2. バイトスワップ機能を制御する信号*UBSと*LBS
  3. メモリアクセスの信号*MEMRと*MEMW 

アドレスバスの上位ビットは値を0に固定すればよいでしょう。

バイトスワップ回路の制御は、AB0とNOT AB0をそれぞれ*UBSと*LBSに接続すれば良さそうです。

メモリアクセス信号は*EとRWBから生成します。 

不足しているI/Oポートの追加

FM16βのI/OポートはほぼFM-11の上位互換です。また、ディスプレイサブシステムについても同様です。 

不足するI/Oポートですが、FM16βのブートデバイスはFM-11と異なり、DIPスイッチで設定されるわけではありません。よって、FM-11のIPL ROMが正常に動作するためにはDIPスイッチの値を読み出すポートと等価なI/Oを追加する必要があります。

追加するI/Oポートですが、DIPスイッチを実装せずに固定値を返すことにします。FM-11 AD2+のIPL ROMを使う前提ですが、接続されているブートデバイスを全部"1"に、ドライブ番号を"0"にします。こうすることでブートデバイスを大容量のものから順番にサーチしてくれます。(実際の信号は負論理です。)

なお、ドライブ0以外からブートする需要はないと判断しました。 

2026/06/20

最強のOS-9マシン復活プロジェクト ~始動~

 プロジェクトの概要

富士通FM16βのFM-130バスとFM-11のFM-100バスのブリッジ回路+若干のI/Oポートを作成し、FM16βでFM-11の6809CPUカードとForksの68000CPUカードを動作させます。

ソフトウェアはFM-11用OS-9/6809 Level2とFM16β用OS-9/68000が使用できます。

OS-9/6809は無変更で起動しますが、OS-9/68000は専用のIPLが必要です。このIPLでCPUを6809から68000に切り替えることで二つのOSのデュアルブートを実現します。

開発計画

以下のようなフェーズに分けて開発する予定です。 

フェーズ0

原理試作として、バスブリッジと6809用のブートデバイス情報を返すI/Oポートのみを実装します。CPUを切り替える回路は省略します。これにより、6809CPUカードが単体で動作します。ユニバーサル基板で作る予定です。

フェーズ1

FM-100バスについては仕様書が手元にあるのですが、FM-130バスの仕様書は書庫のどこかに埋もれているか、あるいは処分してしまったかなので、手元にはありません。まずは、仕様書を発掘するか、記憶のかなたから情報を引き出すか、あるいは実験してみないとCPU切り替え回路が設計できません。 

設計ができたらCPUを切り替える回路を追加して、 デュアルブートできるようにします。

プロジェクト名に「復活」と入っている理由

1986年頃だったと思いますが、「最強のOS-9マシン」は実現に成功していました。

当時は学生で資金や開発ツールなどに制約があり、ユニバーサル基板にTTL ICとラッピングワイヤーを用いたハンダ付けで実装しました。なお、資料が残っていないので回路図はおぼろげな記憶の中にしか存在しません。

今回の復活プロジェクトは、PLDを用いて部品点数を削減し、近代的な(?)実装にするつもりです。余力があれば当時は夢にも思わなかったPCB化を試みるかもしれません。

2026/04/17

TangConsoleDCJ11MEMにおける2BSDの誤動作の調査 その3

誤動作の原因の二つ目が判明しました。

符号なし2進数と符号付2進数の比較

後述するSOFUB_MAPの実装に符号なし2進数と符号付2進数を比較しているコードがありました。直接の比較ではありませんでしたが、符号なし整数の値を符号付整数の変数に代入し、その変数と定数を比較していたため、意図した通りには動作しない場合がありました。

問題発生のシナリオは以下の通りです。

  1. メモリ不足のためプロセスのテキスト領域(サイズ>=32768)のswap領域への書き出し処理が始まる
  2. buf構造体のb_bcountにサイズが格納されてsofub_alloc()に渡される
  3. sofub_alloc()では符号付整数のcountにb_bcountが代入される
  4. 関数の中でcountを正の定数と比較する(if文)
  5. countは負の値となるため条件が不成立となる(条件が成立した場合はテキスト領域のコピー処理が実行される)
  6. 5.で条件が不成立となった結果テキスト領域がコピーされずに過去にコピーされた別のデータがswap領域に書き込まれる
  7. 後にテキスト領域がswap領域から読み込まれるが、無関係のデータが復元されるためプログラムが誤動作する

2.11BSDのCプログラムのスタートアップコードはpure executable用とseparate executable用の二種類あるのですが、誤動作していたプログラムテキストの先頭部分はpure用ではなくseparate用に置き換わっていました。先頭以外の部分にも他のプログラムのヒープ領域と思しきデータがありました。

 SOFUB_MAPとは

UNIBUS用のデバイスはDMAアドレスが18bitなので、メインメモリのアドレスが18bitを越える領域に直接アクセスすることができません。このため、PDP-11ではUNIBUS用のMMUが実装されているのですが、TangConsoleDCJ11MEMにはDMA用のMMUはありません。

そこで登場するのがSOFUB_MAPです。18bitのDMAアドレスでアクセスできる範囲に中間バッファを設け、このバッファで中継することでメインメモリ全領域とのI/Oを実現しています。ISAバスのバスマスタデバイスで1MBを越えるメインメモリを扱う際の技法であるbounce bufferと同類です。

SOFUB_MAPではDMAの前後に必要に応じてメモリコピーを実行するのですが、上述のシナリオでこのコピー処理がスキップされた結果、バッファに残っていた別のデータがディスクに書き込まれていました。 

通常のファイルI/Oでは転送サイズが32KB以上になることはないので正常に動作していましたが、swapデバイスとのI/Oでは32KB以上になることがあるため、問題が発生していました。

修正内容

問題の原因がわかってしまえば修正は簡単で、変数countの型を符号なしにしました。修正パッチ 

なお、DMA完了時に呼ばれるsofub_relse()ではcountはunsignedで宣言されているので修正の必要はありませんでした。

2026/04/10

TangConsoleDCJ11MEMにおける2BSDの誤動作の調査 その2

問題の原因の一つが判明しました。

DCJ11が駆動する場合のABORT信号の扱いが仕様を満たしていないのがメモリ内容の上書きを発生させていました。

ABORT信号の扱い

ABORT信号は双方向の信号で、CPUが駆動する場合と外部回路が駆動する場合があります。CPUが駆動する場合の扱いについては、DCJ11 UG 3-6に以下のように規定されています。 

 If an internally generated abort condition such as an MMU error or address error exists, the DCJ1l asserts ABORT during the first part of the cycle. If this type of abort occurs, the DAL, BS, and MAP information should be ignored for the remainder of the cycle.

 オリジナルのFPGAの実装ではCPUが駆動しているABORT信号については関知していないため、以下のシナリオでメモリが破壊されていました。

  1. haltコマンドのテキスト領域とスタック領域が物理アドレス上で隣接する場所に割り当てられる。アドレスは昇順にテキスト、スタックと並んでいる。
  2. スタックポインタが減算される。減算されたポインタの物理アドレスは上記のテキスト領域を指している。
  3. 2.で減算されたポインタへの書き込み命令が実行されようとする 。書き込むデータは#4で命令コードとして解釈するとIOTとなる。
  4. MMUによりスタック領域の範囲外へのアクセスが検出され、バスへの書き込みサイクルがABORT信号の駆動によって無効化されるが、バスサイクルそのものは発生する。
  5. FPGAはABORT信号の入力を関知せず、書き込みサイクルを実行する。
  6. 5.の書き込みによって隣接するテキスト領域の命令が上書きされてIOT命令になる。
  7. MMUによってトラップが発生し、スタック領域に別のメモリ領域が割り当てられて拡張される。
  8. 4.で中断された書き込み命令が再度実行される。 
  9. haltコマンドが6.のIOT命令を実行して異常終了する。

 FPGAのメモリへの書き込み処理を変更して、ABORT信号がアサートされている場合にバスサイクルを無視するように修正したところ、2.11BSDのhalt命令の誤動作と、2.9BSDのカーネルビルド時の誤動作については再現しなくなりました。

2026/03/25

2BSDの誤動作をバイナリパッチで捕まえる方法 その2

 実行中にODTでメモリを書き換える方法は

  1. 操作が煩雑
  2. 問題の再現性が低い(気がする)、あるいは挙動が変化する

ため、 カーネルにバイナリパッチを当てて自力でトリガをかけられるようにしました。

ソースコードの変更ではなくバイナリパッチなのは、

インストールされるカーネルバイナリと一致するソースコードが見つからない

ためです。 

後述するFPGAの変更と組み合わせることで、誤動作の発生を高確率で捕まえられるようになりました。

カーネルのバイナリパッチ

オリジナルのカーネル(rpunix)とパッチを当てたカーネルを比較すると以下のようになります。

    41 324 202
   42   3   4
   49 324 202
   50   3   4
 1187 346 300
 1188  35  25
 1189 170 172
 1190 373 377
 1191  46 310
 1192  20  25
 1193 346   1
 1194  35   0
 1195 234 300
 1196 133  25
 1197 146 200
 1198  20 376
 1199 246 310
 1200  20  25
 1201 346   1
 1202  20   0
 1203 203   0
 1204  35   0

先頭の4か所はトラップベクタの書き換えです。"Illegal instruction trap"と"IOT trap"の発生時に関数dzdmaにジャンプします。残りの部分は、使用されていないドライバの関数(002202 T dzdma)を上書きして、以下の命令列を書き込んでいます。アドレス依存性はないのでソースコードのアドレス指定は気にしなくても大丈夫です。

        1                                .asect
       2 000400                         .=400
       3 000400 012700  177572          mov #177572,r0
       4 000404 012710  000001          mov #1,(r0)
       5 000410 012700  177200          mov #177200,r0
       6 000414 012710  000001          mov #1,(r0)
       7 000420 000000                  halt

177572はMemory Management Register #0のアドレスです。"1"を書き込むことでアドレス変換を有効にしています。

177200はHALT信号をアサートするためのI/Oポートで、今回のデバッグのために追加しました。FPGAの変更については後述します。

FPGAの変更

I/Oポート(177200)に書き込みを行うとHALT信号をアサートするように改変しました。変更内容は以下のとおりです。

 diff --git a/hdl/20251017/TangConsoleDCJ11MEM_project/src/top.v b/hdl/20251017/TangConsoleDCJ11MEM_project/src/top.v
index 4133bd9..e4a4380 100644
--- a/hdl/20251017/TangConsoleDCJ11MEM_project/src/top.v
+++ b/hdl/20251017/TangConsoleDCJ11MEM_project/src/top.v
@@ -2219,6 +2219,13 @@ module top(
        default:;
       endcase

+  always @(posedge sys_clk or negedge INIT_n)
+    if( ~INIT_n )
+      dbg_trg <= 1'b0;
+    else if(negedge_SCTL_n & bus_write && address == 18'o777200)
+      dbg_trg <= 1'b1;
+
+/* -----\/----- EXCLUDED -----\/-----
   always @(posedge sys_clk or negedge RESET_n)
     if( ~RESET_n )
       {REG_DBG_CP, dbg_trg} <= 0;
@@ -2230,6 +2237,7 @@ module top(
       REG_DBG_CP <= 1'b1;
     else if( (address[15:0] == REG_DBG2) & aio_iread  & REG_DBG_CP)
       dbg_trg <= 1'b1;
+ -----/\----- EXCLUDED -----/\----- */
 //    else if( (address == 16'o001040) & aio_iread ) // trap at 'panic:'
 //      dbg_trg <= 1'b1;
 //  else if( (dpwa == (16'o25246 >> 1)) & (we0_lo | we0_hi |we1_lo | we1_hi))

オリジナルのデバッグ機能(ブレークポイント)と競合するので、該当部分をコメントアウトしています。

バッチの置き場所

パッチを当てたカーネルのバイナリと、FPGAのソースコードに対するパッチをここに置きました。ご利用ください。add-self-abort.patchとrpunix.patchedです。 

(3月27日追記) 2.11BSDでも同様の解析ができるように、カーネルパッチを置きました。2.11BSDのカーネルパッチ FPGAは2.11BSD用のアドレス拡張版が必要です。FPGAのパッチ

2026/03/23

2BSDの誤動作をバイナリパッチで捕まえる方法 その1

 カーネルに仕込みを入れなくても"Illegal instruction trap"を捕まえる方法を考案しました。成功率は高くないかもしれませんが、うまくいった場合はHALT信号をトリガにすることができます。手順を以下に説明します。

1) カーネルを起動してシングルユーザーモードのシェルのプロンプトが出た状態にする

2) HALTスイッチを押してODTを起動する

3)以下のようにメモリ書き換え/レジスタ設定を実行する 

@10/001724 400            // Illegal instruction trapベクタを400に変更
@20/001724 400            // IOT trapベクタを400に変更
@400/001724 0              // HALT命令に書き換え
@402/000356 0              // 念のためにもう一つHALT
@777100/177775 400     // breakpointを400に設定
@p                                 // シェルに復帰
 
4) シングルユーザモードのまま以下のコマンドを実行
 
# mount -a
# cd /usr/src/sys/GENERIC; make clean; make
 
うまくいけば"Illegal instruction trap"や"IOT trap"が発生した際にODTを起動し、breakpointによってHALT信号をアサートしてロジアナなどのトリガにすることができます。
 
成功して、ロジアナで取れたデータを解析したところ、やはり2.11BSDの場合と同様
 
正常な命令を読み込んだにもかかわらず"Illegal instruction trap"が発生した 
 
ように見えるという現象が観測できました。
 
なお、再現実験を繰り返しているとファイルシステムが破壊される可能性が高いので、SDカードのイメージのバックアップを取っておくことをお勧めします。 
 

補足説明

このような回りくどい方法を採用したのには理由があります。

DCJ11がトラップベクタの2 word(PC, SR)をアクセスする際は、「命令アクセス」ではなく、データとしてのメモリアクセスを行います。よって、命令アクセスを検出するブレークポイントでトラップベクタのアドレスを指定しても効果がありません。やむを得ず、トラップベクタのPCをアクセスされないはずのメモリ領域(アドレス400近傍)に書き換え、そこをブレークポイントで検出するようにしました。

2026/03/21

TangConsoleDCJ11MEMにおける2BSDの誤動作の調査 その1

2.9BSDの誤動作

オリジナルのFPGAで2.9BSDの誤動作が確認できました。確実に再現します。

再現方法と実行結果

 シングルユーザモードで起動後、以下のように実行します。

# mount -a
Mounted /usr on /dev/rp1h
# cd /usr/src/sys/GENERIC; make clean; make
rm -f *.o *.i
cc -V -S -O -DKERNEL -I. -I/usr/include ../sys/sys2.c
ed - < :splfix.movb sys2.s
as -V - -o sys2.o sys2.s
rm sys2.s

(snip)

cc -V -S -O -DKERNEL -I. -I/usr/include ../dev/ttynew.c
ed - < :splfix.movb ttynew.s
as -V - -o ttynew.o ttynew.s
rm ttynew.s
cc -V -S -O -DKERNEL -I. -I/usr/include ../dev/tty.c
ed - < :splfix.movb tty.s
Illegal instruction - core dumped
# strings core | head
@q6N
core
core
make
h
 h
  .

R0
MAKE.  VERSION 2.58     14 MARCH 1979
=|^();&<>*?[]:$`'"\
 :;&>|
No description argument after -f flag

# adb /bin/make core
$R
ps      0170011
pc      034110
sp      0171272
r5      0171306
r4      0171370
r3      0
r2      0
r1      06
r0      01560
034110:         mov     sp,r5
# make
cc -V -S -O -DKERNEL -I. -I/usr/include ../dev/tty.c
ed - < :splfix.movb tty.s
as -V - -o tty.o tty.s
rm tty.s

カーネルをビルドするとmakeが異常終了します。

コアダンプを調べても実行した命令は正常です。また、続けてmakeを実行すると正常に動作するので、ファイル上にもメモリ上にも不正な命令は存在しないものと考えられます。

2.11BSDの誤動作

 改造版のFPGAとPCBで2.11BSDの誤動作を確実に再現できる方法が見つかりました。

 再現方法と実行結果

 シングルユーザモードで起動後、以下のように実行します。

 @773010
73Boot from rk(0,0,0) at 0177404
:
: rk(0,0,0)unix
Boot: bootdev=03000 bootcsr=0177404

2.11 BSD UNIX #32: Fri Mar 13 00:15:09 PST 2026
    root@:/usr/src/sys/SMALL


phys mem  = 655360
avail mem = 414912
user mem  = 307200

March 15 18:32:17 init: configure system

hk ? csr 177440 vector 210 skipped:  No autoconfig routines.
ht ? csr 172440 vector 224 skipped:  No autoconfig routines.
ra ? csr 172150 vector 154 skipped:  No autoconfig routines.
rl ? csr 174400 vector 160 skipped:  No autoconfig routines.
tm 0 csr 172520 vector 224 attached
tms ? csr 174500 vector 260 skipped:  No autoconfig routines.
ts ? csr 172520 vector 224 skipped:  No autoconfig routines.
xp ? csr 176700 vector 254 skipped:  No autoconfig routines.
erase, kill ^U, intr ^C
# halt
IOT trap - core dumped
# ls *.core
halt.core
# adb /sbin/halt halt.core
adb: no memory for symbols
adb> $r
ps      0170000
pc      041174
sp      0174510
r5      0175214
r4      0175262
r3      040312
r2      045
r1      0
r0      0144
041174:         add     $010,r4
adb> #
#

haltコマンドがIOT命令(000004)を実行して異常終了します。

ODTでメモリダンプを調べてみましたが、異常終了したプロセスの該当するPCの命令はファイル上の命令と一致していました。 

48chのロジックアナライザを接続してすべての信号を観測できる環境を作り、カーネルとFPGAに仕込みを入れてロジアナにトリガをかけてみましたが、バス上で観測された命令はファイル上の命令と一致しており、IOT命令ではありませんでした。

バスのトランザクションとしては以下のような流れでした。

  1. PC(041174)近傍の4命令が読み出される
  2. IOT trapのベクタの2 word(PC, SR)が読み出される
  3. IOT trapベクタに設定されているロジアナトリガ用の命令列が読み出される
  4. トリガ用のI/Oアドレスに書き込みが実行されてトリガ信号がアサートされる 

念のためにIOT命令を実行するテストプログラムを作成してロジアナで解析しましたが、こちらはきちんとIOT命令を読み込んでいることが確認できたので、解析方法に誤りはないと思います。 

考察

 以上の観測結果から考えて、どちらの誤動作も

メモリから読み出したものとは異なる命令を実行した

のが現象のように見えます。この仮説が正しいとして、原因が何かを考えてみたのですが、現時点では何も思いつきません。

(4月6日追記) IOT Trap発生時にカーネルスタックに保存されるPCは、IOT命令のアドレスではなく、+2した値のようなので、上記の解釈は誤りでした。-2したアドレスにはIOT命令が存在することが確認できました。

なお、以上の現象は2セットの個体を用いて検証しているので、ハードウェアの個体差やCPUの故障などは考えにくいと思われます。

(追記) "/.profile"を編集してPATHに2文字追加しただけで再現しなくなったりします。再現条件はかなり限定されるようです。

最強のOS-9マシン復活プロジェクト ~原理試作~

 プロジェクトのフェーズ0として、原理試作を行いました。  FM-130バスの仕様書は書庫からは見つかりませんでした。8bitパソコンの時代のマシンやマニュアル類、雑誌などもすべて姿を消していたので、処分したのだと思います(忘却の彼方)。  遭遇した不具合(1) 導通の不安定 原...