ディスクリートロジックで10BASE-Tネットワークカードを製作
(qdiv.dev)- 市販CPUや専用ネットワークチップを使わずに作ったディスクリートロジックコンピュータに、10BASE-T Ethernetの送受信機能を追加したハードウェア製作記
- 以前に作成した10BASE-T↔SPI物理層アダプタの上にMAC層モジュールを追加してhomebrew computerに接続し、送信機と受信機は独立したフルデュプレックス構成になっている
- 受信機はSPIデータをバイトに変換して2 kB SRAMに保存し、先頭6バイトをハードウェアで検査して
FE:FA:F6:F2:EE:EAまたはブロードキャストMACだけを受け取る - 送信機は回路を単純化するため、FCS生成とプリアンブル準備をソフトウェアに任せ、1024バイト固定長フレームのみをサポートする
- uIP 1.0をコンパイルできるCコンパイラまで作成してネットワークアプリを実行し、結果はping平均85 ms、HTTP静的ファイルのダウンロード2.6 kB/s程度
ディスクリートロジックコンピュータにEthernetを付ける
- 完全なコンピュータシステムをディスクリートロジック部品で作る作業の延長として、ネットワークアプリケーションを実行できるEthernetアダプタを実装した
- 以前は10BASE-T Ethernet信号をSPIに変換して戻す物理層アダプタを作成し、その際の動作テストにはSTM32マイクロコントローラを使用した
- 今回の作業の核心は、そのアダプタをhomebrew computerに接続するためのMAC層モジュールである
- アダプタはフルデュプレックス構造で、送信部と受信部が互いに独立して動作する
受信機: SPIデータをフレームバッファに保存
- 受信機はSPIシリアルデータをバイト単位のパラレルデータに変換し、バイトクロックを抽出する
- 宛先MACアドレスは先頭6バイトで検査し、条件に合わないフレームは拒否される
- 受信したバイトは6116 2 kB SRAMバッファに書き込まれる
- フレームが終わると受信機は無効化され、再度有効化されるまで追加フレームは受け取らない
- バイトカウンタは停止後も値を保持し、CPUが受信長を読める
- FCSはハードウェアでは検査しない
-
データ取得とバッファアクセス
- SPIシリアルデータはシフトレジスタ
U32に入り、U30とU31がそれぞれビット数とバイト数を数える - Dフリップフロップ
U29BがSRAM書き込み信号recv_buf_weを生成し、この信号は入力データ8ビットごとに一瞬Lowになる - 受信したバイトは
U20である6116 SRAMに記録される U13、U16、U18はアドレスマルチプレクサを構成し、SRAMアドレス入力としてバイトカウンタまたはシステムアドレスバスを選択するU21は受信バイトをRAMへ渡す3状態バッファとして動作する- CPUが受信データと長さにアクセスできるよう、RAMとバイトカウンタはシステムデータバスに接続されている
U25は受信RAMをシステムデータバスに接続する- フレーム完了後、バイトカウンタ値は
recv_byte_cntバスに保持される U26、U27は特定アドレスへの読み出し要求があると、この値をシステムデータバスへ渡すU27の残り半分は、受信機と送信機の状態を照会する2ビット読み取り専用ステータスレジスタを作る
- SPIシリアルデータはシフトレジスタ
ハードウェアMACアドレスフィルタリング
- Ethernetトラフィックを解析すると、フレームは通常、短い遅延で区切られた3〜4個単位の小さなまとまりで入ってきて、同じまとまりの中でも宛先MACアドレスが異なることが多かった
- コンピュータがソフトウェアでMACフィルタリングを行って受信機を再有効化するには十分速くない可能性があり、ハードウェアMACフィルタリングが必要だった
- ユーザー定義のMACアドレスを保存して先頭6バイトと比較する方式は複雑すぎるため除外した
- 単一バイト反復MACアドレスも可能だったが、最終的にはバイトインデックスの関数としてMACアドレスを作った
- bit 0は0に固定
- bit 1は1に固定
- bit 2〜4はバイトインデックスの反転値
- bit 5〜7は1に固定
- この規則で作られたMACアドレスは
FE:FA:F6:F2:EE:EA - ARP動作のため、ブロードキャストMAC
FF:FF:FF:FF:FF:FFも受け取る U33はデータbit 0とbit 2〜4が期待値と一致するかを比較し、U34A出力はそれらのビットが一致するとHighになるU35AはブロードキャストMAC検査を実装し、bit 0とbit 2〜4がすべて1なら出力がHighになる- この2つの信号は
D7とR6を使ったダイオードORで結合され、U35Bが残りのビットがすべて1かを検査する - 単一バイトの有効性結果は
U10Aに累積される- フレームを受信していないとき、incoming SPI slave select信号
ssはLowで、U10Aは1に設定される - フレーム受信中は、各受信バイトごとに値が更新される
- 宛先MACアドレスが条件に合えば、
U10Aの値はHighのまま維持される - バイトアドレスが5に達すると最終値が
U36Bにラッチされ、宛先アドレスが一致しない場合はフレーム受信が阻止される
- フレームを受信していないとき、incoming SPI slave select信号
送信機: 固定長フレームで回路を単純化
- 送信機も受信機と同様にFCS生成をハードウェアでは実装せず、ソフトウェアで処理する
- 回路を減らすため、送信機は固定長フレームのみをサポートする
- フレーム長は1024バイトに選び、一般的なMTU 1500バイトに近い値にした
- 10BASE-Tに必要なプリアンブルは複数の
0x55と末尾の0xD5で構成され、ソフトウェアがこの1024バイト内に一緒にロードしなければならない - 固定フレーム長は上位プロトコルに影響しない
- 上位プロトコルはパケットサイズをヘッダにエンコードする
- 実際のEthernetフレーム長には依存しない
-
送信データフロー
- 送信データはSRAMに保存される
- 20 MHzクロックが4ビットカウンタに入力され、オーバーフロー出力がバイトクロックとして使われる
- 特定の書き込み専用メモリ位置に値を書き込むとカウンタが有効化され、フレーム送信が始まる
- パラレルのバイトデータはシフトレジスタを通してシリアル化される
- 受信機と同様に
U12がビットを数え、U14がバイトを数える - 20 MHzクロックは内蔵オシレータから来るが、直接は使わず少なくとも2分周して使用する
- この方式により、オシレータのデューティサイクルが出力信号に影響しない
-
RAM、シフトレジスタ、タイミング
- RAM
U22のアドレス入力選択には、受信機と同様に3個の74HC157マルチプレクサが使われる U23はRAMにデータをロードするために使われるU24は現在送信中のバイトの中間保存領域として機能する- バイトカウンタ74HC4040はリップルカウンタのため安定化が遅い
- RAM出力がまだ有効でない間、
U24が安定した出力を提供する - データはシフトレジスタ
U28に入り、ビット単位でシフトされる - RAMからシフトレジスタへ入るビット順を誤って接続したハードウェアバグがあり、ソフトウェアでビットを並べ替えて回避しなければならなかった
MOSIとSCKは良好な10BASE-T信号を作るため、正確に同期している必要があるU11AとU8Bがこの同期を処理するtx_cnt0はビットカウンタのbit 0で、20 MHzを2分周した信号をクロックとして使用するU11Aはこの信号に合わせて出力を切り替えるU8BはU11Aが作る遅延に合わせるためクロックを遅延させる- Dラッチは単純なANDゲートより複雑で約5 ns大きな遅延があるため、より高速な74LV74Aを使用した
- 74LV74Aはこのボードで唯一の高速系列チップである
- RAM
CPUインターフェースとメモリマッピング
- プログラマの観点では、Ethernetアダプタはメモリマップトインターフェースとして見える
- 2つのフレームバッファは
0xF000にマップされている - 読み取り専用レジスタは2つある
0xFB00の8ビットステータスレジスタはRX_FULLとTX_BUSYフラグを持つRX_FULLはフレーム受信完了状態を示すTX_BUSYはフレーム送信中状態を示す0xFB02の16ビットレジスタは受信データ長を保持する
- 書き込み動作は制御コマンドとして使われる
0xFB00に任意の値を書き込むと受信機を再有効化する0xFB01に任意の値を書き込むと送信を開始する
- CPUは割り込みをサポートしないため、割り込みはない
- 関連アドレスはすべて上位4ビットが1の
Fで始まり、この条件をU2Aが検査する - バッファアドレスはbit 11が0でなければならず、
U1D、D2、R2、U1Eがこれを検査する - レジスタアドレスは2番目の16進数桁が
Bの1011である必要があり、U1BとU2Bが確認する U4A、U4Bデコーダは個別機能の選択に使われる- LED 2個はバッファまたはレジスタアクセスを表示する
プログラミングと性能
- ネットワーク対応は欲しかったがTCP/IPスタックを自作したくはなく、アセンブリプログラミングも不便だったため、Cコンパイラを作成した
- このコンパイラは小さなTCP/IPライブラリであるuIP 1.0をコンパイルできるほど成熟している
- CPUのコード密度は非常に低いが、uIPはRAMに収まり、実際のアプリケーション用の空間も残る
- ネットワーク性能は低いが、市販CPUや特殊チップなしで実装した結果である
- ping往復平均: 85 ms
- HTTPサーバーのダウンロード速度: 2.6 kB/s
- HTTPサーバーはSDカード上の静的ファイルを提供する
- モデル、回路図ファイル、PCB図面はGitHubリポジトリにある
1件のコメント
Hacker News のコメント
共有してくれてありがとう、素晴らしい仕事です。推論過程のスタックトレースが特に良く、多くのことを第一原理から解きほぐしていたり、初心者の視点で説明しようとしていたりする点が、教育的にとても優れています
実際のネットワーキングには実用的でなくても、単なるお遊びではないと思います。過度に複雑なネットワークチップからバックドアが見つかる時代なので、今後はもっと真剣な読者層やプロジェクトの動機が生まれるかもしれません
これは完全カスタムコンピューター用なので、それ自体でもはるかに印象的ですし、「それで C コンパイラを作った」というくだりは言うまでもありません。それでも「普通の」PC 向け Ethernet カードの最小実装がどの程度になるのか気になります
かなりの部分は似たものになりそうで、チェックサムは PC の CPU に処理させることもできそうです。接続は生のシリアルか、より実用的には USB が必要で、結局は「本物の」ドライバを使うか、ユーザー空間に渡して処理する必要がありそうです
似たようなものを見ていて、デバイスが https://en.wikipedia.org/wiki/USB_communications_device_clas... を実装すれば、独自ドライバなしで「そのまま動く」ようにできるのではないかと思いましたが、チェックサムをすべてホスト側で処理するという話とはあまり相性が良くなさそうです
検索しているうちに https://en.wikipedia.org/wiki/Ethernet_over_USB も見つけましたが、物理接続だけを USB に変換するアダプタを作り、残りはコンピューターに任せて処理させられる、という意味なのかもしれません
FTDI 風の USB デバイスに 10base2 Ethernet をビットバンギングさせるよう説得することはできるかもしれません。回線上のトラフィックをきれいなビットストリームに変換し、フレーム開始を合わせる「PHY」側だけを実装して、PC が残りをすべてソフトウェアで処理する方式です
USB 側は CDC-NCM 自体ならどんな MCU でも実装は難しくありませんが、USB HS PHY の実装には実質的に ASIC ハードウェアが必要です
0.30 ドルの USB HS ULPI PHY を使えば、FPGA で USB CDC-NCM をかなり簡単に実装できるはずです
末尾に、このプロジェクト用に作った C コンパイラへのリンクがあります: https://github.com/imihajlow/ccpu-cc
リンカと libc もあるようです。ハードウェア設計がどれほど複雑なのかは正確には分かりませんが、C コンパイラをさっと作って組み合わせた点がすごいです
本当に印象的です。こういうプロジェクトを自分でもやってみたいですし、システムを理解してから作るところまで注ぎ込まれた情熱と数え切れない時間に敬意を抱きます
引退を特に望んでいるわけではありませんが、たぶんその頃にはこうしたハードウェア・ソフトウェアプロジェクトに時間を使うことになるのかもしれません
それで Etherlink 3c501 より良いのか、それとも悪いのか? :-D
https://mirror.math.princeton.edu/pub/oldlinux/Linux.old/net...
記憶が正しければ、CPU が読み出そうとしていたバッファを、ネットワークから入ってきた新しいパケットが上書きするような仕組みでした。しばらく Linux で使っていましたが、性能は本当にひどかったです
ドライバやファームウェアのアップデートが、どれほど多くのものを視界から隠してくれているのか驚かされます
私のものはバッファが 2 つあるので、もっとましです :) それでも受信フレームは 1 つしか保持されません
「フレーム長を固定しても上位プロトコルには影響しない。上位プロトコルはヘッダにパケットサイズをエンコードしており、実際の Ethernet フレーム長には依存しないからだ」という部分が興味深いです
最近パケットデコーダを作ったのですが、各層で下位層の長さが合っているかを明示的に検証しました。IP の場合、私のデコーダでは IP データグラム長が Ethernet フレーム長とリンク層ヘッダ長に正確に一致していなければなりません
厳密にしたかったというより、短いフレームを検出したかったのですが、その後、長いフレームもエラーと見なすことにしました。作者は uIP を使っていますが、Linux や他の現代的な OS がどう処理するのか気になります。相互運用性テストをしたのかも気になります
アプリケーションが L2 データを見ないなら、Linux の IP スタックは単に無視します
DEC の最初の SSI Ethernet カードセットよりは、物理的にはずっと小さいです: https://i.ebayimg.com/images/g/NEYAAOSw-mZlg0lZ/s-l1600.jpg
DEC DEUNA のボードは長さが 1 フィートを超えますが、機能もずっと多いです。DEUNA は「本物の」NIC で、送受信キューを持ち、それを自律的に処理し、DMA も行います。もちろんカード上に専用の PDP-11 も載っていて、それを実行しています
本当に素晴らしいです。どれくらいかかったのか気になります
ネットワークポートにつながったチップの中にバックドアを仕込むのがどれほど簡単かを示しています
Communication Systems Engineering の授業で Ethernet 信号処理を実装し、続いて ARP とスイッチングを含む TCP/IP スタックを Motorola 68k QUIC アセンブリで実装しました
人生で最も長い 18 か月でした