AIで半導体回路設計・第二弾
夏休みの宿題「AIで回路設計・第二弾」Open-Source TR-1um を使った OpenSUSI-MPW サービスでも実装できそうなロジック回路として非同期 I2C 回路を Claude に実装させてみました。はてさて、どこまで出来るのでしょうか?😁 Verilog が出来たら、TR-1um のスタセルの情報から、ネットに合成できるか?まで試します。
See https://github.com/jun1okamura/TR-1um_Async_I2C
先ずは、「I2C バス仕様およびユーザーマニュアル」をNXPのサイトからダウンロードして Claude に読み込ませます。
I2C インターフェース回路を非同期ロジック回路で実装します。I2Cの仕様書を読み込んでください。
しばらくすると、Claude が実装に関して要点と質問をしてきますので回答します。
I2C仕様書(UM10204 Rev.5.0J、NXP)を読み込みました。非同期ロジック回路での実装に関わる要点は以下のとおりです。
プロトコルの基本ルール
ビット転送: SCLがHIGHのときはSDAは安定必須。データの変化はSCLがLOWの間のみ許可(図4)
START/STOP: SCL=HIGH中のSDAのHIGH→LOW遷移がSTART、LOW→HIGHがSTOP
バイト構成: 8bit・MSBファースト、各バイト後にACK/NACKビット(9クロック目)
クロックストレッチ: スレーブがSCLをLOWに保持してマスタを待機させられる
クロック同期/調停: SCLはワイヤードAND接続(LOW期間が最長のマスターが支配)。マルチマスタ時のみ必要
タイミング仕様(表10、Standard/Fast/Fast-mode Plus)
tHD;STA、tLOW、tHIGH、tSU;STA、tHD;DAT、tSU;DAT、tr/tf、tSU;STO、tBUFなど、非同期回路のゲート遅延・セットアップ/ホールド時間の制約に直結する値を確認済みです(例: Standard-modeでtLOW min 4.7µs、tHIGH min 4.0µs、tSU;DAT min 250ns)。
電気特性(表9)
VIL/VIH(VDDの30%/70%)、オープンドレイン出力、ヒステリシス入力など、非同期回路のスレッショルド設計に必要な情報も確認済みです。
この先、実装対象を決める必要があります。
- 非同期ロジック回路の実装対象は何ですか?
- I2Cスレーブ
- 実装形式はどちらを想定していますか?
- HDL記述(Verilog/VHDL、非同期スタイル)
出力ファイル
しばらく悩んだ後に以下のメッセージとファイルが出力されます。
i2c_slave_async.v(SCL/SDAのエッジのみで動く、システムクロック無しの非同期I2Cスレーブ、7bitアドレス指定)を実装しました。ロジックはMyHDLで挙動を検証済み(書き込み/読み出し/アドレス不一致NACKの全シナリオPASS)です。iverilog等がこの環境になかったため、i2c_slave_async_tb.vはローカルでの実行用に用意しています。
design_notes.mdにFSM構造、UM10204各節との対応、Table 10のタイミング制約、既知の制限(クロックストレッチ未対応など)をまとめています。
Github
公開用に TR-1um_Async_I2C としてリポジトリを作って Local のフォルダーの内容を整理する。
Local フォルダーへのアクセスを許可して整理したファイルを Claude に確認してもらう。
確認できたこと
src/ に .v 2本(RTL + テストベンチ)、script/ にPythonモデル/テスト一式、design_notes.md・README.mdは正しく配置。src/i2c_slave_async_model.pyとtest/i2c_slave_async_tb.vをそれぞれscript/・src/へ移動した差分は内容の欠落なし。
git remoteはgit@github.com:jun1okamura/TR-1um_Async_I2C.git 設定済み(このサンドボックスからはSSH認証できないため到達性は未確認)。
移動はまだgit add/コミットされていません(git statusは削除+未追跡として表示中)。
試しに Readme を生成してもらうと。。。。
なるほどぉ。。。素晴らしい!ココまでめちゃ順調。。。
TEST
Readmeの記載どおりにテストベンチを走らせて見る。
iverilog -o sim i2c_slave_async.v i2c_slave_async_tb.v && vvp sim
VCD info: dumpfile i2c_slave_async_tb.vcd opened for output.
[t=160000] FAIL: busy asserted after START
[t=880000] FAIL: slave ACKed matching address (write)
[t=880000] FAIL: addr_match asserted
[t=880000] FAIL: rw indicates WRITE
[t=1600000] FAIL: slave ACKed data byte
[t=1620000] FAIL: rx_data == 0xA5
[t=1680000] FAIL: busy cleared after STOP
[t=2500000] FAIL: slave ACKed matching address (read)
[t=2500000] FAIL: rw indicates READ
[t=3140000] FAIL: read byte == 0x3C
[t=3280000] FAIL: busy cleared after final STOP
[t=4060000] FAIL: unmatched address -> NACK (no slave ack)
[t=4060000] FAIL: addr_match not asserted for foreign address
[t=4120000] FAIL: busy cleared after STOP following NACK
---- RESULT ----
14 check(s) FAILED
ということで全く動かず。。。Orz... Claude に動かないことを指摘すると。
バグを特定・修正しました。
原因: i2c_slave_async_tb.v が rst_n のリセットパルスを一度も発行していませんでした(rst_n = 1; を設定するのみ)。4-state Verilogシミュレータでは全レジスタが不定値(X)で起動し、DUTの初期化はif (!rst_n)分岐でのみ行われるため、リセットが一度も走らないとstate/sda_oe/scl_q/sda_qがXのまま伝播し、STARTすら検出できず全項目FAILになります。MyHDL側の検証でこれが見えなかったのは、MyHDLのSignal()がPython側で明示的な初期値を持つ2値シミュレーションのため、X不定値の問題が存在しなかったからです。
src/i2c_slave_async_tb.v にリセットパルス(rst_n=0 を4T保持後 rst_n=1)を追加し、design_notes.md に「§5 パワーオンリセット必須」の節を追加して、実機でもPOR回路が必須である点を明記しました。お手元で再実行をお願いします。
2度目のTEST
iverilog -o sim i2c_slave_async.v i2c_slave_async_tb.v && vvp sim
VCD info: dumpfile i2c_slave_async_tb.vcd opened for output.
[t=240000] FAIL: busy asserted after START
[t=960000] FAIL: slave ACKed matching address (write)
[t=960000] FAIL: addr_match asserted
[t=960000] FAIL: rw indicates WRITE
[t=1680000] FAIL: slave ACKed data byte
[t=1700000] FAIL: rx_data == 0xA5
[t=1760000] OK: busy cleared after STOP
[t=2580000] FAIL: slave ACKed matching address (read)
[t=2580000] FAIL: rw indicates READ
[t=3220000] FAIL: read byte == 0x3C
[t=3360000] OK: busy cleared after final STOP
[t=4140000] OK: unmatched address -> NACK (no slave ack)
[t=4140000] FAIL: addr_match not asserted for foreign address
[t=4200000] OK: busy cleared after STOP following NACK
---- RESULT ----
10 check(s) FAILED
むむむ。。。再度 Claude に動かないことを指摘すると。
2つ目の実バグを修正しました。
原因: i2c_slave_async.v で start_cond/stop_cond/scl_rise/scl_fall を独立した wire = ... 連続代入として宣言していましたが、それらを読む always ブロック自体も同じ scl/sda_in の変化で起動するため、「wireの再評価」と「always内でそのwireを読む」の実行順序はVerilog仕様上保証されません。iverilogではalways側が遷移前の古い値を読んでしまい、START/STOP検出が恒常的に失敗していました。MyHDL側はこの条件をPythonのローカル変数として直接計算していたため、レース自体が存在せず検出できませんでした。
修正: 該当のwireを廃止し、scl_q/sda_q/scl/sda_in をalways内で直接読んでインライン評価するよう変更(同一プロセスが所有する信号を直接読むだけなので、レースが原理的に発生しません)。design_notes.mdにもこの経緯を記録しました。
3度目のTEST
iverilog -o sim i2c_slave_async.v i2c_slave_async_tb.v && vvp sim
VCD info: dumpfile i2c_slave_async_tb.vcd opened for output.
[t=240000] OK: busy asserted after START
[t=960000] OK: slave ACKed matching address (write)
[t=960000] OK: addr_match asserted
[t=960000] OK: rw indicates WRITE
[t=1680000] OK: slave ACKed data byte
[t=1700000] OK: rx_data == 0xA5
[t=1760000] OK: busy cleared after STOP
[t=2580000] OK: slave ACKed matching address (read)
[t=2580000] OK: rw indicates READ
[t=3220000] OK: read byte == 0x3C
[t=3360000] OK: busy cleared after final STOP
[t=4140000] OK: unmatched address -> NACK (no slave ack)
[t=4140000] OK: addr_match not asserted for foreign address
[t=4200000] OK: busy cleared after STOP following NACK
---- RESULT ----
All checks PASSED
3度目の正直で、全テスト合格! Claude 君にも報告します。
確認しました。全14項目PASS — write / read / アドレス不一致NACKの3シナリオすべて正常動作しています。これで i2c_slave_async.v は実際のVerilogシミュレータ(iverilog)で動作確認済みのRTLになりました。
i2c_slave_async.v のヘッダコメントと design_notes.md(§7 検証方法)を、この2件の実バグ(リセットパルス漏れ/wire-alwaysレース)と最終PASS結果を反映して更新しました。MyHDL(2値シミュレーション)だけでは検出できなかった実機依存のバグだったため、今後この設計を変更する際は必ずiverilog等での再実行を、という注意点も明記しています。
波形確認
波形でも確認してみます。
チャント動いている様です。Claude にも報告します。
波形を確認しました。テキスト結果(14/14 PASS)と整合しています。
rw: 0.96µs付近でWRITE(0)に確定→2.58µs付近でREAD(1)に切替、いずれもログのrw indicates WRITE/READのタイミングと一致
rx_data=0xA5(1.7µs、書き込みデータ一致)、rd_byte=0x3C(3.22µs、読み出しデータ一致)— いずれも期待値と一致
ack_bit/rd_byteが最初は赤(xx)で表示されているのは、テストベンチ側のregが最初に代入されるまで不定値のままなだけで、DUT側の不具合ではありません(ack_bitは最初のread_ack呼び出しまで、rd_byteは最初のread_byte呼び出しまで未代入)
3トランザクション(書き込み/読み出し/アドレス不一致NACK)とも意図した通りの波形です。何か気になる箇所はありますか?特になければ、このまま最終版としてコミットして問題ないと思います。
TR−1um への実装。
先ずはネットにしないといけませんが、.lib があるわけではないので、Claude に必要な情報を渡してネットへの変換をお願いします。< 無理やりだ。。。
TR−1um スタセル情報
Cloude に TR-1um のスタセル 以下の内容を理解して、実装に利用可能なロジックセルをまとめてもらう。
~/TR-1um/libs.tech/xschem/TR-1um_5_stdcell を確認し、logic_cells_mapping.md に追記しました。
主な追加点:
.symファイルのdir=in/out/inout属性でピン方向が権威的に確定(抽出ネットリストのピン順だけでは不明だった部分)
RS/RR$1/TOPはxschem側にも回路図が存在しません。RSはGDSのみでスキーマも無く、本当に未完成のセルの可能性が高いです。前回提案した「NAND2交差結合で自作」が現実的な代替です
DFFR.schを軽く追跡した限り、RSTピンはActive-HIGHの可能性が高い構成に見えます(保持段に直接ゲートされたNMOSが1個追加されている構成)。本設計はrst_n(Active-LOW)前提なので、DFFRを使う場合はRST = ~rst_nのインバータが必要になりそうです。ただしトランジスタレベルの完全な追跡はしていないため、実際に使う前に簡単なSPICEシミュレーションで極性を確認することをお勧めします
ネット記述へ
Cloude に、読み込ませたスタンダードセルを使って verilog から net 記述に論理変換出来るか挑戦させる。
はい、可能です。このサンドボックスに yowasp-yosys(YosysのWebAssembly版、pip経由でインストール可能)を入れて動作確認済みです。
ただし、Yosysでこのカスタムセルライブラリへ技術マッピングするには、各セルの論理機能・ピン容量・(DFFR/DFFSは)非同期セット/リセット付きフリップフロップの定義を記述したLiberty(.lib)ファイルが必要です。現状このライブラリにはxschemの回路図/シンボルしかなくLibertyがないので、まず私の方でセル名(NAND2, AND2, DFFR等の標準的な命名)と.symのピン方向から機能定義のLibertyファイルを作成し、それを使ってyosys+ABCでマッピングする形になります。
その上で2点確認させてください。
質問1: Libertyファイルのタイミング情報の精度
- A. 付け値でよいのでまず機能検証(推奨) — 面積/遅延は仮値で、まず論理的に正しいnetlistが得られるかを確認する。
質問2: 最終的な出力形式
- A. 構造Verilogネットリスト(推奨) — 各セルをインスタンス化したVerilog。シミュレーションでの検証がしやすく、LVS前の確認にも使える
おぉおぉ!!凄いぞ! しかし、、、この後かなりの時間ダンマリになる。
実際にYosysでi2c_slave_async.vを読み込ませたところ、合成不可というエラーが出ました。
ERROR: Found non-synthesizable event list!
原因は、always @(scl or sda_in or negedge rst_n)という現在のalwaysブロックの書き方にあります。これは「SCL/SDAのレベル変化+非同期リセット」を1つのブロックに混在させたビヘイビア記述で、シミュレータ(MyHDL/iverilog)では正しく動きますが、Yosys等の論理合成ツールは「単一クロックのposedge/negedgeで駆動される同期回路」か「純粋な組合せ回路(@*)」のどちらかしか認識できません。このRTLはそのどちらでもないため、ツールが機械的にゲートへ変換できません。
実際にゲートで作るには、記述を以下のように再構成する必要があります。
ADDR/DATA_WR/DATA_RDのビットサンプルと状態遷移: always @(posedge scl or negedge rst_n)という素直な同期記述に直す(DFFRへ直接マッピング可能になる)
SDA出力の更新: always @(negedge scl or negedge rst_n)(同様にDFFRへマッピング可能)
START/STOP検出: そもそも「クロック」を持たない本当の非同期回路なので、Yosysに合成させるのではなく、NAND2の交差結合(SRラッチ)+DEL1(SDAの遅延コピーを作ってエッジ検出)を使って**手で構造記述(structural instantiation)**する
この再構成は単純な書き換えではなく、状態エンコーディングも見直しが必要です(START時にstateレジスタを特定パターンへ非同期セットする必要があるが、DFFRは非同期リセットのみ・DFFSは非同期セットのみで、両方を1本のFFで賄えないため)。
i2c_slave_async.v の修正提案をしてください。
i2c_slave_async.v の修正案です。ポイントは、Yosysが認識できない「レベル変化+非同期リセット混在」のalwaysブロックを、実際にDFFR/DFFS/NOR2/DEL1へマッピング可能な形に分解することです。
アーキテクチャ変更の要点
START/STOP検出: DEL1でSDAの遅延コピーを作り、scl & sda_d & ~sda_in(立下り)/scl & ~sda_d & sda_in(立上り)で検出パルスを生成。クロックを一切使わない、本当の非同期エッジ検出(合成ツールには任せず手で構造記述する必要がある部分)。
busyラッチ: NOR2交差結合のSRラッチ。start_pulseでSET、stop_pulseor~rst_nでRESET。
ステートエンコーディングを変更: ADDR状態をリセット値(3'b000)に割り当てることで、START時の「特定パターンへの非同期セット」問題を回避。DFFRの非同期RESETだけで「START時にADDR状態へ強制的に戻す」ことが実現できる(RST = ~rst_n | start_pulseをそのままDFFRのRSTピンへ接続)。アドレス不一致/NACK時は新設したPH_IGNORE状態でSTOPまで待機(元のIDLE相当の動作を維持)。
SCL立上りドメイン(phase, bit_cnt, shreg, addr_ok, rw_bit): DFFR、CK=scl、RST=~rst_n|start_pulse
SCL立下りドメイン(sda_oe, txreg): DFFR、CK=~scl(INV_X1で反転)、RST=~rst_n|~busy(busy=0の間は常にSDA解放)
rx_valid/addr_match/rwはphase/addr_ok/rw_bitから素直な組合せ出力になり、専用レジスタが不要になりました。
注意点
ポートにVDD/GNDを追加しました(DEL1/NOR2/INV_X1を実インスタンス化するため)。既存のi2c_slave_async_tb.vはこのままでは繋がらないので、テストベンチ側にVDD=1'b1, GND=1'b0の接続を追加する小修正が必要です。
DFFRのRST極性がActive-HIGHという前回の推定を前提にしています(未確定のため、進める場合は先にSPICE確認を推奨、と以前お伝えした点は変わりません)。
動作は元のi2c_slave_async.vと等価になるよう設計していますが、書き直しなので再検証が必要です。
この方針で進めてよければ、(1) 実際にi2c_slave_async.vを更新、(2) テストベンチをVDD/GND対応に修正、(3) MyHDLモデルも同じ構造に合わせて更新して再検証、(4) Yosys合成が通ることを確認、まで進めます。進めてよいですか。
進めましょう!
v2への書き直し・合成・検証・ドキュメント反映まで完了しました。まとめると:
RTL (i2c_slave_async.v) — Yosysが合成不能と判定した単一always構成をやめ、非同期SR部(DEL1遅延線+NOR2クロスカップルラッチでSTART/STOP検出)とSCLエッジ駆動のDFFR同期部(posedge scl / posedge ~scl の2ドメイン)に分離。phaseのリセット値をPH_ADDRに一致させ、DFFRの非同期RESETのみでSTART復帰できるようにした。
合成 — yowasp-yosys(サンドボックス内でpip導入可)で TR1um_5_stdcell.lib(自作プレースホルダLiberty)に対して合成し、i2c_slave_async_net.v(33×DFFR+NAND/NOR/AND/OR/MUX/INV混成、ラッチ推論・未マッピングなし)を生成。ABC出力に欠けていたVDD/GND接続はpost-processで補完済み。
検証 — v2構造を写したMyHDLモデルmodel_v3.pyで同一シナリオ(write/read/アドレス不一致)が全PASS。ただし形式的等価性チェックはサンドボックスの45秒制限で断念、実iverilog確認もv2に対してはまだ未実施。
ドキュメント — design_notes.md(§2/§3書き直し、§7.5新設で合成結果・制約を記載)、logic_cells_mapping.md(NAND2→NOR2の記述訂正、合成での妥当性確認を追記)を更新済み。
次のお願い: v1で2回実バグを検出できたのはユーザー環境のiverilog実行のみだったため、v2でも同様に以下をローカルで実行し、結果を教えてください。
実行します。
% iverilog -o sim i2c_slave_async.v stdcell_behavioral_stubs.v i2c_slave_async_tb.v && vvp sim
VCD info: dumpfile i2c_slave_async_tb.vcd opened for output.
[t=240000] FAIL: busy asserted after START
[t=960000] FAIL: slave ACKed matching address (write)
[t=960000] OK: addr_match asserted
[t=960000] OK: rw indicates WRITE
[t=1680000] FAIL: slave ACKed data byte
[t=1700000] OK: rx_data == 0xA5
[t=1760000] OK: busy cleared after STOP
[t=2580000] FAIL: slave ACKed matching address (read)
[t=2580000] OK: rw indicates READ
[t=3220000] FAIL: read byte == 0x3C
[t=3360000] OK: busy cleared after final STOP
[t=4140000] OK: unmatched address -> NACK (no slave ack)
[t=4140000] OK: addr_match not asserted for foreign address
[t=4200000] OK: busy cleared after STOP following NACK
---- RESULT ----
5 check(s) FAILED
不具合が出たので Claude 君にも報告します。
原因が判明しました。RTL自体のバグではなく、シミュレーション用ビヘイビアモデルの遅延設定に起因する競合です。
原因: busyラッチ(NOR2交差結合)をSETするstart_pulseはDEL1の遅延幅ぶんのワンショットパルスです。スタブではDEL1もNOR2も同じ#1遅延にしていたため、パルス幅とラッチ内部のゲート遅延がちょうど一致し、Verilogのinertial delayによってSETパルスがラッチに届く前に消えてしまい、busyが一度もHIGHになりませんでした。busyに依存するsda_oe/txreg(ACK駆動・読み出しデータ駆動)が常時リセット状態に固定され、ACK関連の5項目だけが軒並みFAILした一方、busyに依存しないaddr_match/rw/rx_dataは正常だった、という今回のFAILパターンと完全に一致します。
対処: stdcell_behavioral_stubs.vのDEL1遅延を#1→#4(DEL2/DEL4も#8/#16)に拡大し、パルス幅をラッチのゲート遅延に対して十分広くしました。RTL本体(i2c_slave_async.v)は変更していません。これは実シリコンでも成立させるべきタイミング制約(DEL1の遅延はNOR2帰還ループより十分大きくする必要がある)でもあるため、design_notes.md§7.6とlogic_cells_mapping.mdに記録済みです。
お手数ですが、もう一度同じコマンドで再実行し、結果を教えてください。
再実行します。
% iverilog -o sim i2c_slave_async.v stdcell_behavioral_stubs.v i2c_slave_async_tb.v && vvp sim
VCD info: dumpfile i2c_slave_async_tb.vcd opened for output.
[t=240000] OK: busy asserted after START
[t=960000] OK: slave ACKed matching address (write)
[t=960000] OK: addr_match asserted
[t=960000] OK: rw indicates WRITE
[t=1680000] OK: slave ACKed data byte
[t=1700000] OK: rx_data == 0xA5
[t=1760000] OK: busy cleared after STOP
[t=2580000] OK: slave ACKed matching address (read)
[t=2580000] OK: rw indicates READ
[t=3220000] OK: read byte == 0x3C
[t=3360000] OK: busy cleared after final STOP
[t=4140000] OK: unmatched address -> NACK (no slave ack)
[t=4140000] OK: addr_match not asserted for foreign address
[t=4200000] OK: busy cleared after STOP following NACK
---- RESULT ----
All checks PASSED
おお!!!
まとめ:
原因はDEL1とNOR2の遅延が同値だったことによるbusyラッチのSETパルス競合(RTL自体のバグではなくシミュレーションスタブの遅延設定)
stdcell_behavioral_stubs.vのDEL1遅延を拡大して解消
実シリコンでも同種のタイミングマージン(DEL1 >> NOR2遅延)が必要である旨を設計制約として文書化し、実SPICE特性化後の再確認事項として明記済み
これで一連の非同期I2Cスレーブ実装(RTL→合成→ネットリスト→検証→文書化)は完了です。
回路図
またも、無理スジな質問「P&R環境が無いので、レイアウトを手作業で進めます。回路図を生成することは可能ですか?」
- 回路図の出力形式はどうしましょうか?
- xschem形式(.sch)
- 配線の描き方はどうしましょうか?
- ネットラベル方式(推奨)
はぁ、出来るんかい!!!
合成済みネットリスト(i2c_slave_async_net.v、144セル)から xschem 形式の回路図を自動生成し、プロジェクトに配置しました。
schematic/i2c_slave_async_net.sch — 全144セルインスタンス(DFFR×33、NAND/NOR/AND/OR/MUX/INV混成×111)+手動配置していたu_del_sda/u_inv_scl/u_lat_q/u_lat_qnをxschemシンボルとして配置し、各ピンにネットラベル方式(TR-1um_5_stdcellライブラリ自身の.schと同じ手法)で接続情報を付与したものです。見た目の引き回し配線はしていませんが、接続関係は完全に正確です。トップの入出力(VDD/GND/rst_n/scl/sda_in/tx_data[7:0]を左端、sda_oe/rx_data[7:0]/rx_valid/addr_match/rw/busyを右端)も配置済みです。
script/gen_schematic.py — 生成スクリプト。ネットリストを再合成した場合はこれを再実行すれば回路図を再生成できます。Yosysが作る大量のネットエイリアス(040 = bit_cnt[0]等)はUnion-Findで一本化し、可能な限りphase[0]/addr_ok/rst_nのような意味のある名前を表示するようにしています。
注意点(design_notes.md §9に記載):
サンドボックスにxschem本体が無いため、生成した.schを実際にxschemで開いての構文・接続確認は未実施です。まずお手元のxschemで開いてエラーが無いこと、セル数・接続がi2c_slave_async_net.vと一致することを確認してください。
セル配置はネットリスト順の機械的なグリッド配置で、論理的なブロック構造(非同期SR部/SCL posedge・negedgeドメイン)は意識していません。手動レイアウトの際はdesign_notes.md §2/§3も併せて参照することをお勧めします。
スタンダードセルの参照は絶対パス、I/Oピンはdevices/ipin.sym等の相対参照(既存ライブラリと同じ書き方)にしています。
一度xschemで開いてみて、エラーやセル数の食い違いがないか確認してもらえますか。問題があれば教えてください。
配線を引いた schematic も作成してください。
配線を引いた版を追加しました。
schematic/i2c_slave_async_net_routed.sch — 144セルを論理段数(レベル)順に左→右へ自動配置し、各ネットをドライバから各レシーバまで実際に直交配線(横→縦→横)で結線したものです。VDD/GNDのみ既存ライブラリの慣習に合わせてラベル方式のまま残しています。
配置アルゴリズムを作る過程で一つ実装ミスを見つけて直しました。レジスタ(DFFR)の出力を「そのレジスタ自身のD入力側の論理段数」としてそのまま下流に伝播させていたため、phase/bit_cnt等が相互参照するFSMレジスタ群を辿ると見かけ上49段もの直列チェーンになってしまう問題があり、レジスタの出力は「新しい起点(レベル0)」として扱うよう修正し、最大9段という妥当な論理深さに収まりました。
正直な評価として、144セル規模の平坦なゲートレベル回路を1枚のシートに収めているため、scl/rst_nのような高ファンアウト信号は交差が多く密集します。きれいに整った回路図というより「どのピンとどのピンが線で結ばれているか目で追える」参考図として使ってください。手作業レイアウトの区切りはdesign_notes.md §2/§3のブロック単位(非同期SR部/SCL posedge・negedgeドメイン)で分けるのが実務的だと思います。
design_notes.md §10に詳細と生成スクリプトの説明を追記済みです。
Layout に挑戦
TR−1um PDK には P&R のサポートが無いので、手配線になりますが、I2C のスレーブをレイアウト設計するかなぁ。。。思い直して、TR-1um の STDCEL の GDSII を読み込ませて、ClaudeでP&R出来るか?無理スジで進めてみます。
Placement
何度かやり取りを繰り返して、以下のような手順でPlacementまで進められた。
I2C非同期スレーブのRTLから物理レイアウト準備までの作業を以下にまとめます。
まず回路図面では、Verilogネットリストからgen_schematic.pyでxschem回路図(ネットラベル方式・配線済み方式の2種)を自動生成しました。当初lab=属性をワイヤに付けるだけでは接続にならない(xschemの接続はあくまで幾何学的接触による)という誤りがあり、正しいdevices/lab_pin.sym方式に修正した上で、ユーザーがxschemから書き出したSPICEネットリストとVerilog側の接続情報をUnion-Find正規化した上で突き合わせる検証スクリプトverify_spice.pyを作成し、完全一致を確認しています。あわせて標準セルLibertyファイルも、データテーブル駆動のgen_liberty.pyで再現可能な形にしました。
物理実装では、まず標準セルの本当の突き付け境界がGDSのcell.bbox()ではなくprBoundaryレイヤ(235/0)であることが判明し、これに基づいてセル幅・行ピッチを再計算した結果、4行構成・コア高さ220umの配置に修正しました(plan_placement.py/gen_gds_placement.py)。次に、配線層がM1/M2の2層のみという制約のもとでM1横配線・M2縦配線に必要な面積を、ネットリストの区間重なり密度から概算し(estimate_m1.py/estimate_m2.py)、その結果に基づいて外側2行(論理行0・3)に専用のM1チャネル、内側2行(論理行1・2)に共有チャネルをFILLセルで確保する構成(コア高さ605um)としました。
さらに、各行の未使用幅を右端に固めず横方向に分散配置し、電源レール共有ペア(論理行0+1、論理行2+3)内でコリドー位置がなるべく揃うよう調整(プレパディング処理を含む)しました。各段階で、電源レール(VDD/GND)の連続性、行内の突き付け・重なりゼロ、行合計幅の1800um超過なしを生成後のGDS読み戻しで検証しています。
現状のLayout/i2c_slave_async_layout.gdsは配置+フィラーのみで、M1/M2/polyの実配線はまだ含まれていません。
出来上がった配置Layoutです。ココまで約半日。DRCもクリーンです!
配線だ!
「M1を横、M2を縦、短い距離ならGCも使って良い」と指示して、1チャネルだけ試しに無理スジで配線をさせて見ます。かなり悩んだ後で。。。
おお!ビアにDRCエラーがあるので、配線処理に回避策を追加します。
なお、ここまで記載しているすべての設計作業に使った処理は scripts/の下の Python script として保存されています。
ここまで、仕様書読み込みから、ザックリ2日。
3日目
縦方向の配線にチャレンジした瞬間に白旗が上がる。。。Orz...
配置からやり直しましょう。配置の最適化として配線の本数を目標としましょう。原状の「配線+2段積み+配線+2段積み+配線」は、変えずにセルの配置だけを入れ替えて検討してください。
このやり方ではダメそう。。。セルの配置方針からやり直して少し試行錯誤して以下のレイアウトが出来た。
最終結果は181ネット配線、M2 DRC違反5件、ショート疑い3件(既知パターン)、オープン55件(安全側の未配線)です。
さて、もう少しです。
4日目
| 指標 | 修正前 | 修正後 |
|---|---|---|
| DRC違反 | 1件(M2) | 0件(完全クリーン) |
| チャネル内オープン | 29 | 64 |
| 真の接続性問題ピン合計 | 201 | 248 |
む〜。。。いろいろ指示をして配線のDRCはクリーンになったが、未配線が消えない。結局 「スタセルがクソ」 ということが分かったので、チャッピーにも意見を具申してみる。
これまでの試行で、スタセルを改良した方が良いと判断していますが、アドバイスがありますか?
今回の一連の試行で繰り返しつまずいた原因を振り返ると、スタセル改良の優先順位は次の通りです。
最大の原因は「ピン位置が配線グリッドに乗っていない」ことです。ピンが固定のM1/M2トラックピッチ上に無いため、セルを突き付けただけでは自動的に整合せず、毎回ジョグ探索・クリアランス検証・パッド未検証バグ(§34.10/34.15/34.16で3回同じクラスのバグを踏んだ)が発生しました。ピンをグローバルなM1ピッチ(例えば現状のM1トラックピッチに合わせて)にスナップし、セル境界からの相対オフセットも全セル共通ルールにしておけば、チャネル配線の大部分が「隣接トラックへの直結」で済み、手作業ルータの複雑さそのものが大きく減ります。
次に、フィラーセルの種類がFILL1(幅16.5um固定)1種類しかなく、行の端数埋めで無駄な余りが常に発生していました(§13/16参照)。FILL1/FILL2/FILL4/FILL8のような倍数系列を用意すれば、隙間を過不足なく埋められます。
DFFRが面積の45%を占めている点も気になります。より高密度なFFがあれば、コア面積そのものが縮小できます。
最後に、ピンがM1のみでM2ピンオプションが無いため、セル上のM2チャネルが常に「素通り」前提になり、今回の§34.12〜34.13の複雑なM1スイッチ方針が必要になりました。一部ピンをM2アクセス可にするだけでも、配線密度の高いブロックでは有効です。
優先度をつけるなら、ピングリッド整合>フィラー種類の拡充>駆動力バリエーション、の順で着手すると投資対効果が高いと思います。
まとめ
I2Cの仕様書から、非同期のロジック回路を生成して TR-1um の既存ライブラリ(sch/symだけ)の情報を使って .lib を生成して Yosys-ABC で合成。その後 xschem で編集可能な schematic の合成まで、ほぼ一日で完了です。また、Github と連携しながら Claud が repository を直接編集して整備する可能性に関してもお試してみました。
結果、P&Rの可能性があることがわかりましたが、同時にスタセルの改良が必要なことも判明しました。悔しいので、スタセルの改良へ進みます。









