OpenMPW入門 改訂版

質問、修正案、その他連絡は@Cra2yPierr0tマデ

久々に以前書いた入門記事を読んだら日本語は雑だし内容は古いしこの世の終わりみたいな出来だったので90nmに備えて書きます。

オレオレLSIを焼きたいですよね? 焼きましょう。 Skywater社がPDKを公開し、OSSなGDSIIコンパイラであるOpenLANEも生まれました。そしてGoogleの出資によってEfablessが無料でLSIを作らせてくれるプログラム、Open MPW Shuttle Programをスタートしました。 今こそオレオレLSIを焼くチャンスです。あなたの作りたいチップをGoogleの金で作りましょう!

OpenMPWとは

OpenMPW(Open Multi Project Wafer)はEfablessにGoogleが出資して生まれたシャトルプログラムであり、ホームページには次の文言が書かれています。


The shuttle provides opportunities for designers to experiment and push the state-of-the-art without having to reconcile the risk associated with the cost of fabrication. The shuttle program is open to anyone, provided that their project is fully open source and meets the other program requirements. Costs for fabrication, packaging, evaluation boards and shipping are covered by Google for this program.

出典:https://efabless.com/open_shuttle_program

このシャトルはデザイナーに製造コストに纏わるリスクを負うことなく、実験し、最先端を追求する機会を提供します。 シャトルプログラムは、プロジェクトが完全にオープンソースであり、一定の要件を満たしていれば、誰でも参加することができます。 製造、パッケージング、評価ボード、そして送料は全てGoogleが負担します。


つまりデザインをオープンソースにすれば完全無料で自分の半導体を作れるプログラムという事ですね。これは熱い、参加するしかない、Google最高一生ついていきます。

と言っても提出された全てのデザインを焼いてくれるという訳ではなく、いくつかの条件が存在しています。

今すぐにでも自分のHDLをLSIにしたい気持ちは分かりますが、必要な事がちょっと多いので少しずつやっていきましょう。

この次は各ツールの概要に入りますが、既に知っておりインストール済みなら飛ばして頂いて構いません。

宣伝

高位合成ツール、RgGenを使ったWalkthorughが生えました!

RgGen ✕ OpenMPWでLSIを焼こう!

環境構築

まずはOpenMPWの開発を始めるための最低限の環境を構築しましょう。Linux/M1 Mac/Intel Mac/Winに対応しています。

最小要件はこちら

以下のコマンドはUbuntuとArchlinuxを対象としたのもですが、pipとvirtualenvとdockerがインストールさえ出来ればどんなOSでも構いません。

pipとvirtualenvのインストール

Ubuntu

sudo apt install python3-venv python3-pip

Archlinux

sudo pacman -S python-virtualenv python-pip

dockerのインストール

Ubuntu

sudo apt install docker.io

Archlinux

sudo pacman -S docker

自分をdockerグループに追加しましょう。これはセキュリティ上大変よろしくありませんが、これを回避するためにpodmanを使ったり色々試したが結局無理だったので諦めました。回避出来るなら教えてほしい。

Ubuntu, Archlinux共通

sudo usermod -a -G docker $(whoami)

一旦再起動する。

Dockerデーモンの起動

Ubuntu, Archlinux共通

systemctl start docker

OpenLANEの概要

OpenLANEとはOSSなRTL-to-GDSIIコンパイラであり、20個くらいのOSSを組み合わせて作られています。このOpenLANEにPDKとRTLとコンフィグを揃えて実行するとGDSIIが生成されます。凄いですね。

このGDSIIをファブに送りつけるとLSIを焼いてくれます。

OpenLANEはDockerコンテナが提供されており、Dockerでインストールするのが推奨されています。一応Dockerコンテナを使わずにインストールすることも可能ですが、It is more complexとか書いてあるので近づかない方が良いです。

OpenLANEのインストール

次にOpenLANEをインストールしますが、後述するCaravel経由でインストールした方がなにかと都合が良いので先にcaravelをインストールします。

最初にcaravelをダウンロード。<tag>は次のページから最新のものを指定してください。執筆時点ではSkywaterの130nmを使うシャトルの場合はmpw-8cでGlobalFoundriesの180nmを使う場合はgfmpw-0dです。https://github.com/efabless/caravel_user_project/tags

git clone -b <tag> https://github.com/efabless/caravel_user_project.git

そしてdependenciesディレクトリを作成、OpenLANEとPDKをインストールするディレクトリを作成し、環境変数を設定します。

cd caravel_user_project
mkdir dependencies
export OPENLANE_ROOT=$(pwd)/dependencies/openlane_src
export PDK_ROOT=$(pwd)/dependencies/pdks

次にインストールするPDKの種類を設定する。PDKの中身はOR回路とかAND回路とかFFとかの設計図とか物性情報です。

<pdk>にはインストールしたいPDKの種類を指定します。PDKの種類にはsky130A, sky130B, gf180mcuCなどがあり、シャトルで使うプロセスに応じて選択してください。

export PDK=<pdk>

make setupでインストールを開始する。

make setup

環境変数のOPENLANE_ROOTPDK_ROOTPDKは重要であり、これが設定されていなければOpenLANEもCaravelも動きませんので、必ず設定するようにしてください。

OpenLANEの動作確認

OpenLANE以下に入りmake testを実行してOpenLANEの動作確認をすることが出来ます。やってみましょう。

cd $OPENLANE_ROOT
make test

Basic test passedと出れば問題なくインストール出来ています。

既に用意されているdesign/以下のデザインをビルドしたい場合は、make mountしてから./flow.tcl -design ディレクトリ名でビルド出来ます。

以下では試しにAPUをビルドしています。

cd $OPENLANE_ROOT
make mount
./flow.tcl -design APU

OpenLANEを直接使ってデザインをビルドした際に生成されたGDSIIは OpenLANE/designs/APU/runs/RUN_<date>/results/final/gds以下に存在しており、klayoutを用いてレイアウトを見られます、見てみましょう。テンションが上がりますね。

klayout APU.gds

この画像は回路が生成されている感を感じるためにデキャップセルを非表示にしており、実際に見られるものとは少し異なります。

OpenLANEの設定

OpenLANEを使う上で、HDLを書くのと同時に、そのデザインに応じてOpenLANE用の設定ファイルを書く必要があります。

試しにデフォルトで用意されているデザインの設定ファイルを見てみましょう。デフォルトのデザインはOpenLANE/designs/以下に存在しており、usbやpicorv32a等色々ありますがそれらのディレクトリ内のconfig.tclがOpenLANEの設定ファイルとなっています。

設定ファイルはtclファイルのconfig.tclかjsonファイルのconfig.jsonで記述可能です。

設定ファイルの基本的な書き方の説明はここにあります、後で解説します。 https://openlane.readthedocs.io/en/latest/usage/hardening_macros.html

また利用可能な変数の一覧はここにあり、その中で必須な変数を後で解説します。 https://openlane.readthedocs.io/en/latest/reference/configuration.html

Caravelの概要

今までさんざん出てきたCaravelですが、これはOpenMPWの半導体設計用テンプレートです。 このCaravel一つでデザインのビルド、プリチェック、テストベンチをすることが可能となっています。凄いですね。

CaravelはManagement AreaとUser Project Areaに分かれています。Management Areaは変更不可能な領域であり、軽量なRISC-Vコアと各種IOからなります。またUser Project Areaは自由に変更できる領域であり、開発者はここに自分のデザインを挿入します。

Management Areaのサイズは2.920um x 3.520umであり、大体10mm^2です。

caravelの概要

User Project Areaから外部には38本のGPIO線が伸びており、ここから直接自分のデザインにアクセス出来ます。またManagement Areaとは32bitのwishboneインターフェースと128bitのLogic Analyzer線で繋がっており、RISC-Vコア上で走っているプログラムがMMIOでアクセスを行えます。

Caravelのインストール

ここの作業は、上記のOpenLANEのインストールで既にCaravelのインストールを完了している場合はやる必要はありません。むしろやらない方が良い。

ただOpenLANEもpdkもCaravelも全部消してもう一度OpenMPWで必要なOSSをインストールしたいならここを見るのが手っ取り早いです。

caravelをダウンロード。<tag>は次のページから最新のものを指定してください。執筆時点ではSkywaterの130nmを使うシャトルの場合はmpw-8cでGlobalFoundriesの180nmを使う場合はgfmpw-0dです。https://github.com/efabless/caravel_user_project/tags

git clone -b <tag> https://github.com/efabless/caravel_user_project.git

dependenciesディレクトリを作成、OpenLANEとPDKをインストールするディレクトリを作成し、環境変数を設定する。

cd caravel_user_project
mkdir dependencies
export OPENLANE_ROOT=$(pwd)/dependencies/openlane_src
export PDK_ROOT=$(pwd)/dependencies/pdks

次にインストールするPDKの種類を設定する。PDKにはOR回路とかAND回路とかFFとかの設計図が入っている。

<pdk>にはインストールしたいPDKの種類を指定する。PDKの種類にはsky130A, sky130B, gf180mcuCなどがありますので、シャトルで使うプロセスに応じて選択してください。

export PDK=<pdk>

インストールを開始。

make setup

Caravelの動作確認

Caravelが正しくインストールされている事を確認するために、既に用意されているデザインをビルドしてみましょう。caravel_user_projectのルートに入り、以下のコマンドでビルドを開始します。

make user_proj_example

GDSIIファイルはgds/以下に生成されます。klayoutで見てみましょう。

Caravelのファイル構造

Caravelには多くのディレクトリがありますが、基本的に以下の4つのディレクトリの使い途を知っていれば十分です。

Caravelの全てのディレクトリの説明はここにあります。気になりましたら見てみてください。

https://caravel-harness.readthedocs.io/en/latest/getting-started.html#required-directory-structure

Caravelにおけるデザインのビルド

Caravelにおいて、デザインのビルドは二段階に分かれています。第一段階では自分のVerilogのGDSIIへのビルドであり、第二段階がそのGDSIIを内包したラッパーのビルドです。OpenMPWではこのラッパーの方を提出します。

ここで言うラッパーとは自分のデザインを1つ以上包んだデザインの事であり、user_project_wrapperという名前です。

ビルドされたuser_project_wrapperは通常、以下の画像のようにラッパーにデザインが挿入された形になります。

この画像も回路が生成されている感を出すためデキャップセルを非表示にしています。

開発者は自分のデザイン用とuser_project_wrapper用の2つのOpenLANEの設定ファイルを編集する必要がある、という事です。後者の方は少ししか編集しないので安心してください。

Caravelのインストール直後ではuser_project_wrapperuser_proj_exampleを含むようになっているため、user_proj_wrapperをビルドした後user_project_wrapperをビルドすれば上の画像のようなGDSIIが生成されます。

make user_proj_example
make user_project_wrapper

klayout gds/user_project_wrapper.gds

Caravelのドキュメント

Caravelのドキュメントはここにあり、後で詳細な使い方を説明します。

https://caravel-harness.readthedocs.io/en/latest/index.html

OpenMPWで自分のデザインを焼こう!

ここからは本格的に自分のデザインをビルドし、OpenMPWに提出する方法を説明する。なお、Verilogの書き方は説明しないのでデザインは自分で用意してほしい。

必要なものはこちら。上3つに関しては先の章で既に済ませている。

1. 焼きたいデザインを用意する

まずはデザインを用意しよう。今回は例としてEthernet MACを用います。

https://github.com/Cra2yPierr0t/Vthernet-SoC/tree/main/verilog/rtl/Vthernet_MAC

貴方のために作りました。

2. 自分のデザインにCaravel用のインターフェースを生やす

自分のデザインをCaravelのMGMT Coreに接続すれば、ファームウェアでレジスタの設定やデータの受送信を行う事が出来るようになる。また、自分のデザインを外部ピンに接続すれば、直接外部とデータを受送信が行えるようになる。

ここでは、自分のデザインをCaravelに対応させるために必要な事項を説明する。

Management Coreと接続する方法は2通り存在し、WishboneLogic Analyzerという名前である。外部と接続する方法は1通りであり、mprj_ioという名前が付いている。

まとめるとこう。

信号名 バス幅 方向
Wishbone 32bit user_design <-> Management Core
Logic Analyzer 128bit user_design <-> Management Core
mprj_io 38bit user_design <-> Outside

また、自分のデザインからMGMT Coreへ割り込みを掛けることも可能である。割り込みについては上の3つの信号の説明の後、扱い方を解説する。

WishboneとLogic Analyzerの違い

自分のデザインとMGMT Core間の通信を行う方法として、32bit幅のWishboneと128bit幅のLogic Analyzerの2通りが用意されていると書いたが、この2つの何が異なるのか疑問に思う方もいるだろう。なのでここでは、この2つの概要をさっくり説明する。

まずWishboneだが、これは通信プロトコルでありAXI4とかAMBAの友達である。10種類の信号線をプロトコルに沿って操作することでREADやWRITEが可能となる。Wishboneは32bitのデータ線とアドレス線に分かれており、アドレスで指定したデータの読み書きが行える。Caravelにおいては0x30000000~0x80000000-1までのアドレスがWishbone用に確保されており、この範囲でなら自由に利用できる。

次にLogic Analyzerだが、これはただの信号線名にすぎない。バス幅は128bitとWishboneの4倍あり、データ線もアドレス線も分かれておらず、3種類の信号線があるだけとなっている。MGMT Coreからは128bitを4分割した0x250000000, 0x250000004, 0x250000008, 0x25000000cの4つのレジスタとして見えている。

HDLでの実装しやすさで言ったらLogic Analyzerの方が実装しやすいが、アドレスは4つしか割り当てられない。一方WishboneはLogic Analyzerの単純さに比べれば多少複雑だが、広範囲のアドレスを扱える。まあ用途によってどちらが適しているか変わるので、本節では両方の使い方を説明する。

Wishbone interfaceを作る

Wishboneでレジスタのデータを読み出したり、レジスタにデータを書き込んだりする機構の作り方を解説する。これを作ることでRISC-VプロセッサからMMIOで自分のデザインとデータの受送信が出来るようになる。

Wishboneの信号線は以下の通り。

信号名 方向 働き
wb_clk_i MGMT Core -> user_design マスターから入力されるクロック。
この立ち上がりのタイミングで値が読まれる。
wb_rst_i MGMT Core -> user_design リセット信号。
wbs_stb_i MGMT Core -> user_design ストローブ入力。
スレーブが選択されている事を示す。
wbs_cyc_i MGMT Core -> user_design サイクル入力。
有効なバスサイクルが進行している事を示す。
wbs_we_i MGMT Core -> user_design ライトイネーブル入力。
1でWrite、0でRead。
wbs_sel_i[3:0] MGMT Core -> user_design セレクタ入力。
wbs_dat_i[31:0]の有効なバイトがどれか示す。8ビット粒度。
wbs_dat_i[31:0] MGMT Core -> user_design データ入力。
wbs_adr_i[31:0] MGMT Core -> user_design アドレス入力。
wbs_ack_o MGMT Core <- user_design アクノリッジ出力。
1にすると正常なバスサイクルの終了を示す。
wbs_dat_o[31:0] MGMT Core <- user_design データ出力。

信号線が10本もあり、AXI4やAMBA等の通信プロトコルをあまり扱わなかった方は怯むかもしれないが、扱い方は非常に単純である。まずREADとWRITEの波形を以下に示す。これはWishboneの仕様書から拝借した。

READの波形

READはMGMT Coreにデータを送る操作(MGMT CoreがMaster)であり、2サイクルで完了する。

WRITEの波形

WRITEはMGMT Coreからデータを受け取る操作であり、同様に2サイクルで完了する。 以降プロトコルの説明を行う。

まず大前提として知ってほしいのがadr_o, dat_i, dat_oの3つの信号線である。それぞれadr_oはアドレス線であり、dat_iはMGMT Coreへのデータ(READ用)であり、dat_oはMGMT Coreからのデータ(WRITE用)である。

1サイクル目で最初に見るべきはstb_ocyc_oである。この2つが立っていれば、自分が選択され、通信が開始された事になる。次にwe_oが立っていればそのサイクルではWRITEを行い、立っていなければREADを行う事を意味する。WRITEならadr_odat_oにはvalidな値が来ており、adr_oで指定されている先にdat_oの値を入れればよい。READならadr_oは欲しいデータのアドレスとなっている。

2サイクル目で最初に見るべきはdat_iack_iである。dat_iはREADの場合のみに用い、adr_oで指定されたデータをここに入力する。ack_iはREADとWRITEの場合両方に用い、このサイクルで立てて通信が完了したことを示す。

グダグダ説明したが、まあ波形を見ればわかると思う。あとwbs_sel_iはバイトイネーブルです。

仕様書にはパイプライン化されたトランザクションとか色々書いてあるが、Caravelはおそらく連続したトランザクションを行わず、トランザクション同士の間隔は離れていると思われる。少なくともシミュレーションではそうだった。

上記のプロトコルを満たすロジックを書くのは非常にダルいのでRgGenを使うと楽です。

RgGen ✕ OpenMPWでLSIを焼こう!

一応、以下に筆者が作ったWishbone interfaceを置いておくので、RgGenを使わない場合は実装の参考にしてほしい。(ライセンスフリー!)

module wb_interface #(
    parameter TEST_CSR0 = 32'h3000_0000,
    parameter TEST_CSR1 = 32'h3000_0004,
    parameter TEST_CSR2 = 32'h3000_0008
)(
    // wishbone signals
    input   wire        wb_clk_i,
    input   wire        wb_rst_i,
    input   wire        wbs_stb_i,
    input   wire        wbs_cyc_i,
    input   wire        wbs_we_i,
    input   wire [3:0]  wbs_sel_i,
    input   wire [31:0] wbs_dat_i,
    input   wire [31:0] wbs_adr_i,
    output  reg         wbs_ack_o,
    output  reg  [31:0] wbs_dat_o,
    // CSRs
    output  reg  [31:0] test_csr0,
    output  reg  [31:0] test_csr1,
    output  reg  [31:0] test_csr2
);

    localparam WB_IDLE  = 2'b00;
    localparam WB_READ  = 2'b01;
    localparam WB_WRITE = 2'b10;

    reg [1:0] wb_state;

    always @(posedge wb_clk_i) begin
        if(wb_rst_i) begin
            wb_state    <= WB_IDLE;
            wbs_ack_o   <= 1'b0;
        end else begin
            case(wb_state)
                WB_IDLE : begin
                    wbs_ack_o   <= 1'b0;
                    if(wbs_stb_i && wbs_cyc_i) begin
                        if(wbs_we_i) begin
                            wb_state <= WB_WRITE;
                        end else begin
                            wb_state <= WB_READ;
                        end
                    end
                end
                WB_READ : begin
                    wb_state    <= WB_IDLE;
                    wbs_ack_o   <= 1'b1;
                    case(wbs_adr_i)
                        TEST_CSR0 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr0[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr0[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr0[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr0[31:24] : 8'h00;
                        end
                        TEST_CSR1 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr1[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr1[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr1[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr1[31:24] : 8'h00;
                        end
                        TEST_CSR2 : begin
                            wbs_dat_o[7:0]   <= wbs_sel_i[0] ? test_csr2[7:0] : 8'h00;
                            wbs_dat_o[15:8]  <= wbs_sel_i[1] ? test_csr2[15:0] : 8'h00;
                            wbs_dat_o[23:16] <= wbs_sel_i[2] ? test_csr2[23:16] : 8'h00;
                            wbs_dat_o[31:24] <= wbs_sel_i[3] ? test_csr2[31:24] : 8'h00;
                        end
                        default   : begin
                            wbs_dat_o <= 32'h0000_0000;
                        end
                    endcase
                end
                WB_WRITE : begin
                    wb_state    <= WB_IDLE;
                    wbs_ack_o   <= 1'b1;
                    case(wbs_adr_i)
                        TEST_CSR0 : begin
                            test_csr0[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr0[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr0[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr0[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                        TEST_CSR1 : begin
                            test_csr1[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr1[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr1[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr1[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                        TEST_CSR2 : begin
                            test_csr2[7:0]   <= wbs_sel_i[0] ? wbs_dat_i[7:0] : 8'h00;
                            test_csr2[15:8]  <= wbs_sel_i[1] ? wbs_dat_i[15:8] : 8'h00;
                            test_csr2[23:16] <= wbs_sel_i[2] ? wbs_dat_i[23:16] : 8'h00;
                            test_csr2[31:24] <= wbs_sel_i[3] ? wbs_dat_i[31:24] : 8'h00;
                        end
                    endcase
                end
            endcase
        end
    end

endmodule

多分動きます。

Logic Analyzerを扱う

Logic Analyzerで128bitのデータを受送信することが出来る。 Logic Analyzerで用いる信号線は以下の三種類。

信号名 方向 働き
la_data_in MGMT Core -> user_design データ入力
la_data_out MGMT Core <- user_diesgn データ出力
la_oenb MGMT Core -> user_deisgn データ出力イネーブルバー

la_data_inはデータ入力であり、Logic Analyzerからのデータはここを通じて来る。la_data_outはデータ出力であり、Logic Analyzerへのデータはここに送る。注意すべきなのは、la_oenbである。これはla_data_inla_data_outのどちらが有効になっているかを示す信号であり、MGMT Coreから入力される。つまり、128bitあるLogic Analyzerのどの線が入力用で、どの線が出力用なのかは、MGMT CoreがMMIOを通して決定する。

la_data_oenbはデータ出力イネーブルバーであるので、0の場合に出力(la_data_out)で1の場合に入力(la_data_in)が有効になる。0utputと1nputとして覚えると楽。

以上より、設計者はla_oenbの値を考慮してLogic Analyzerの信号線を扱う必要がある。

以下にLogic Analyzerの使用例を載せておくので実装の参考にしてほしい。

assign la_data_out[31:0] = test_signals;
assign w_super_data = (&la_oenb[63:32]) ? la_data_in[63:32] : 32'h0000_0000;

割と簡単に扱える。ちなみに、2行目の&はリダクション演算子である。

mprj_ioを扱う

mprj_ioを用いる事で、自分のデザインと外部の間で38bitの信号を直接やり取りする事が出来る。GPIOと同じものだと思ってくれて構わない。 mprj_ioで扱う信号線は以下の3種類。

信号名 方向 働き
io_in MGMT Core -> user_design データ入力
io_out MGMT Core <- user_diesgn データ出力
io_oenb MGMT Core <- user_deisgn データ出力イネーブルバー

Logic Analyzerと非常に似通っているが、注意してほしいのがio_oenbである。これは、Logic Analyzerと信号の方向が異なっており、自分のデザインからの出力となっている。よってどの線を出力に用いるか、どの線を入力に用いるかは自分のデザイン側で決定できる。

io_oenbはデータ出力イネーブルバーであるので、0の場合に出力(io_out)で1の場合に入力(io_in)が有効になる。0utputと1nputとして覚えると楽。

あとはファームウェア側で色々設定する必要はあるが、自分のデザインではio_oenbの値を操作するだけでよい。

以下にmprj_ioの使用例を載せておくので実装の参考になれば幸いである。

assign io_enb[31:0]  = 32'hffff_ffff;
assign io_enb[32]    = 1'b1;
assign io_enb[34:33] = 2'b00;
assign data_in = io_in[31:0];
assign hard_reset = io_in[32];
assign io_out[34:33] = led[1:0];

とか。io_outへの入力をassignかalwaysのどちらでやるかは多分適当でよい。

割り込みの扱い方

Caravelでは、自分のデザインからMGMT Coreに割り込みを掛けることが可能である。 割り込みにはuser_irq[2:0]の3bit幅の信号線を用いる。この3bitの内、どれか一つでも立った時に割り込みが発生する。割り込み発生時、MGMT Coreのプログラムカウンタには0x00000000がセットされる。

よって、割り込みハンドラはメモリの0x00000000に置くべきである。

割り込みを有効化するためにファームウェアでMGMT CoreのCSRを設定する必要があるが、自分のデザインでは割り込みを行いたい時にuser_irqを立てるだけでよい。

この動画が非常に参考になる。Interrupt!

https://www.youtube.com/watch?v=pPgnVBguNW8

3. 自分のデザインをGDSIIにする

自分のデザインをCaravelに対応できたら、一旦GDSIIに変換する。この操作をHardeningと呼ぶらしい。また、Hardeningされたものをマクロと呼ぶ。

設定ファイルを書く

まずは自分のデザインのVerilogファイルをverilog/rtl以下にあることを確認する。無ければ持ってくる。

次にOpenLANE用の設定ファイルを格納するためのディレクトリをopenlane以下に作成する。このディレクトリの名前はトップレベルモジュールの名前と合わせる。

mkdir openlane/<user design top module>

ここで作成したディレクトリに設定ファイルを置くわけだが、ゼロから書くのは非常にしんどい。そこで、例として用意されているuser_proj_exampleの設定ファイルをコピーして編集する。

設定ファイルのコピー

cd openlane/<user design top module>
cp ../user_proj_example/config.tcl .

ここからはコピーしてきたconfig.tclの編集すべき部分を説明する。

まずtclそのものについてだが、以下の構文を覚えればOpenLANEでは十分である。

set ::env(VAR_NAME) value

これはVAR_NAMEという名前の変数にvalueという値をセットしている。以降、変数はこのVAR_NAMEの事を指す。

config.tclで最初に変更すべき変数はDESIGN_NAMEVERILOG_FILESの2つである。 DESIGN_NAMEは文字通りデザインの名前であり、トップレベルモジュールと同じ名前にする。VERILOG_FILESは合成するVerilogファイルのパスである。

書き方の例を以下に載せる。VERILOG_FILESには複数のVerilogファイルを指定可能である。

set ::env(DESIGN_NAME) user_design_top_module

set ::env(VERILOG_FILES) "\
    $::env(CARAVEL_ROOT)/verilog/rtl/defines.v \
    $script_dir/../../verilog/rtl/user_design_top_module.v \
    $script_dir/../../verilog/rtl/user_design_sub_module.v"

$script_dirという謎の変数があるが、これは現在読んでいるconfig.tclがある場所である。$script_dirは今後消滅して$DESIGN_DIRに変更される可能性が高い。

次に変更すべき変数はCLOCK_PORTCLOCK_NETCLOCK_PERIODの3つであり、これらはクロックに関係している。CLOCK_PORTにはデザインのクロック入力に用いるポート指定する、CLOCK_NETは正直よくわからない。CLOCK_PORTはSTAのための変数でCLOCK_NETはCTSのための変数であるとかドキュメントに書いてあるけど同じ値をセットして多分問題無いと思う。

CLOCK_PERIODにはクロック周期をナノ秒単位で設定する。ナノ秒単位なので”10”を設定した場合は100MHzとなる。

以下に例を載せる。CLOCK_NETに関しては誰か教えてほしい。

set ::env(CLOCK_PORT) "wb_clk_i"
set ::env(CLOCK_NET) $::env(CLOCK_PORT)
set ::env(CLOCK_PERIOD) "10"

次に変更すべきなのはDIE_AREAである。これにはマクロのサイズをマイクロメートル単位で設定する。設定はx0 y0 x1 y1の順であり、仮に"0 0 200 100"と設定すると200umx100umの長方形となる。座標は第一象限と同じである。

最初は適当でよく、後述するPL_TARGET_DENSITYと合わせて最適な組み合わせを探すと良い。

以下に例を載せる。

set ::env(DIE_AREA) "0 0 900 600"

次に変更するのはFP_PIN_ORDER_CFGであり、これはマクロにおいて、どのピンをどの方角から出すかを設定する。だが、設定しなくても勝手にやってくれるので、コメントアウトするなり行ごと削除するなりして消してほしい。

コメントアウトした例を以下に載せる。

#set ::env(FP_PIN_ORDER_CFG) $script_dir/pin_order.cfg

必要になったら各自で設定してほしい。

最後に変更するのはPL_TARGET_DENSITYである。これはDIE_AREA設定した面積で、どの程度の密度で配置するかを決定する。値は0から1の実数を取る。0か1かの極端な値を設定すると、OpenLANEが最適な値を教えてくれるのでそれを参考にしてもいいが、適当に0.4とか入れても問題ない。

以下に例を載せる。

set ::env(PL_TARGET_DENSITY) 0.2

変更すべき変数をまとめた表が以下になる。

変数名 働き 備考
DESIGN_NAME トップレベルモジュールを指定  
VERILOG_FILES Verilogファイルを指定  
CLOCK_PORT クロックポートを指定  
CLOCK_NET
CLOCK_PERIOD クロック周期を指定 ナノ秒単位
DIE_AREA マクロのサイズを指定 マイクロメートル単位
x0 y0 x1 y1
FP_PIN_ORDER_CFG ピンの方角を指定 削除
PL_TARGET_DENSITY 配置密度を指定 0~1

GDSIIを生成

設定ファイルが完成したら、次にGDSIIを生成する。caravel_user_project以下でmake <design_name>でビルドが開始される。

make <user design top module>

生成されたGDSIIは、openlane/<user design top module>/runs/RUN_<date>/results/final/gds以下に存在しており、klayoutを用いてレイアウトを見られる。

klayout <user design top module>.gds

かわいいね。

4. user_project_wrapperに自分のデザインを加える

verilog/rtl/user_project_wrapper.vuser_proj_exampleの代わりに自分のモジュールを入れるだけである。なお、user_project_wrapperでANDやNOT等のロジックを組むのは予期せぬエラーの原因になるのでオススメしない。

追記: user_defines.vを編集

verilog/rtl/user_defines.vを編集し、IOの状態を設定する。全てのGPIO_MODE_INVALIDをデザインに応じてGPIO_MODE_USER_STD_OUTPUTGPIO_MODE_USER_STD_INPUT_PULLDOWNに変更する。

5. シミュレーションで動作を確認する

Caravelに自分のデザインを接続出来たら次はシミュレーションで動作確認をする。

シミュレーションを行うには、少々の設定と、MGMT Coreで動かすファームウェア及びio_in等にどんな信号を入力するかのテストベンチを書く必要があるので、順番に説明する。

シミュレーション環境のインストール

このコマンドでシミュレーション用の環境をインストールする。

make simenv

使うファイルを指定する

OpenLANEで自分のデザインのVerilogファイルのパスをconfig.tclに設定したのと同様に、シミュレーションにおいても、利用するファイルをシミュレーション環境に伝える必要がある。

編集する必要のある設定ファイルは以下の3つである。

まずincludes.rtl.caravel_user_projectに関してだが、ここにはuser_project_wrapperを含めた、自分のデザインに必要なVerilogファイルのパスを追加する。

以下に例を載せる。

-v $(USER_PROJECT_VERILOG)/rtl/user_project_wrapper.v	     
-v $(USER_PROJECT_VERILOG)/rtl/user_design_top_module.v
-v $(USER_PROJECT_VERILOG)/rtl/user_design_sub_module.v

次にincludes.gl.caravel_user_projectに関してだが、ここにはverilog/gl/以下に生成されているVerilogファイルのパスを追加する。verilog/gl/以下のファイルは自動生成され、複数のVerilogをまとめたものになっている。

以下に例を載せる。

-v $(USER_PROJECT_VERILOG)/gl/user_project_wrapper.v
-v $(USER_PROJECT_VERILOG)/gl/user_design_top_module.v

最後にincludes.gl+sdf.caravel_user_projectに関してだが、これもincludes.gl.caravel_user_projectと同様にverilog/gl/以下のファイルのパスを追加する。

以下に例を載せる。

$USER_PROJECT_VERILOG/gl/user_project_wrapper.v	     
$USER_PROJECT_VERILOG/gl/user_design_top_module.v

以上で設定は完了となる。

ファームウェアを書く

ファームウェアはC言語で書ける。CaravelはRISCV toolchainの入ったDockerコンテナを引っ張ってきてmakeコマンドでコンパイルなりなんなりしてくれる。

まずはシミュレーション用のディレクトリをverilog/dv以下に作成する。

mkdir verilog/dv/<sim_name>

このディレクトリにファームウェアとテストベンチを格納する。

ファームウェアの名前は<sim_name>.cにする。以降、ファームウェアの書き方を説明する。

最初に2つのファイルをインクルードする。

#include <defs.h>
#include <stub.c>

このdefs.hには様々なグローバル変数と定数が定義されている。

https://github.com/efabless/caravel/blob/main/verilog/dv/caravel/defs.h

後はmain関数内にプログラムを書いていくが、普通のC言語と同様に関数を定義したりファイル分割してもいい。

CSRのコンフィギュレーション部分は以下のように記述する。

reg_mprj_io_0   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_1   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_2   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_3   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_4   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_5   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_6   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_7   = GPIO_MODE_USER_STD_INPUT_PULLDOWN;
reg_mprj_io_33  = GPIO_MODE_USER_STD_OUTPUT;
reg_mprj_io_34  = GPIO_MODE_USER_STD_OUTPUT;

reg_mprj_xfer = 1;
while (reg_mprj_xfer == 1);

この例では、mprj_io[7:0]を入力に設定し、mprj_io[34:33]を出力に設定している。 その後のreg_mprj_xfer, whileの部分は設定の適用を行っている部分であり、設定が適用されるとwhile文から抜ける。ここの仕組みは不明。

GPIO_MODE_USER_STD_INPUT_PULLDOWNmprj_ioを自分のデザインへの入力に設定する定数であり、GPIO_MODE_USER_STD_OUTPUTは自分のデザインからの出力に設定する定数である。

その他にGPIO_MODE_MGMT_STD_OUTPUTGPIO_MODE_MGMT_STD_INPUT_PULLDOWNなどの定数を設定する事ができ、この場合はMGMT CoreとGPIOで信号を受送信を行えるようになる。

基本的に、while(reg_mprj_xfer == 1)以前に設定、以降に処理を書けば良い。

テストベンチを書く

テストベンチをゼロから書くのは非常に面倒であるため、既存のテストベンチをコピペしてきて使う。今回は例として、io_ports/io_ports_tb.vをたたき台に用いる。

たたき台のコピー

cp ./dv/io_ports/io_ports_tb.v ./dv/<sim_name>/<sim_name>_tb.v

以下では必要な設定の変更を行う。

まずテストベンチ名を変える。

module <sim_name>_tb;

その下にifdef ENABLE_SDFで囲まれている不気味な部分があるが、ここを編集する必要は無い。

次に波形ファイルの対象と名前を変更する。

initial begin
    $dumpfile("<sim_name>.vcd");
    $dumpvars(0, <sim_name>_tb);
    ...

次にファイルの一番下にあるspiflashに読み込ませるファイル名を変更する。

spiflash #(
    .FILENAME("<sim_name>.hex")
) spiflash (
...

設定は以上、これが終わったら162行目から始まるinitial文の中に、自由に信号を書き込んでいく。 https://github.com/efabless/caravel_user_project/blob/main/verilog/dv/io_ports/io_ports_tb.v#L162

以下に例を載せる。元のファイルにあるifdef GLは気にしなくて良い。$finish;だけは忘れないようにする。

initial begin
    mprj_io[7:0] <= 8'h55;
    # 100
    mprj_io[7:0] <= 8'haa;
    # 100
    wait(mprj_io[34:33] == 2'b11);
    $finish;
end

Makefileをコピーする

Makefileをコピーする、内容を編集する必要は無い。

cp ./dv/io_ports/Makefile ./dv/<sim_name>/

シミュレーションを実行する

ここまでやればシミュレーションの実行準備は万全である。caravel_user_project/で以下のコマンドを実行すればシミュレーションが始まる。

make verify-<sim_name>-rtl

デバッグ頑張ってね。

6. OpenLANEの設定をする

作成したデザインのデバッグが完了したら、ラッパーをビルドする準備に移る。 まずはuser_project_wrapperconfig.tclを編集する。

config.tclで変更すべき変数は以下の通り。

変数名 働き 備考
CLOCK_NET CLOCK_PORTと同じでよい
CLOCK_PERIOD クロック周期を指定 ナノ秒単位
FP_PDN_MACRO_HOOKS マクロへの電源接続の明示  
VERILOG_FILES_BLACKBOX マクロのVerilogファイルの指定  
EXTRA_LEFS 利用するLEFファイルの指定  
EXTRA_GDS_FILES 利用するGDSファイルの指定  

CLOCK_NETの変更

デフォルトではmprj.clkになっているので、CLOCK_PORTと同じものにするかその他の適切なものに変更する。

set ::env(CLOCK_NET) $::env(CLOCK_PORT)

CLOCK_PERIODの変更

これにはお好みの動作クロック周期を設定する。

FP_PDN_MACRO_HOCKSの変更

デフォルトではmprjというマクロへの電源の接続になっているので、自分のマクロ名に変更する。

set ::env(FP_PDN_MACRO_HOOKS) "\
	<user design top module> vccd1 vssd1 vccd1 vssd1"

VERILOG_FILES_BLACKBOXの変更

自分のデザインのトップモジュールのパスに変更する。define.vのパスには変更を加えてはいけない。

set ::env(VERILOG_FILES_BLACKBOX) "\
	$::env(CARAVEL_ROOT)/verilog/rtl/defines.v \
	$script_dir/../../verilog/rtl/<user design top module>.v"

EXTRA_LEFSの変更

EXTRA_LEFSにはcaravel_user_project/lef/に自動生成されている、自分のデザインのLEFファイルのパスを設定する。

set ::env(EXTRA_LEFS) "\
	$script_dir/../../lef/<user design top module>.lef"

EXTRA_GDS_FILESの変更

EXTRA_GDS_FILESにはcaravel_user_project/gds/に自動生成されている、自分のデザインのGDSIIファイルのパスを設定する。

set ::env(EXTRA_GDS_FILES) "\
	$script_dir/../../gds/<user design top module>.gds"

macro.cfgの編集

config.tclの設定が完了したら、最後にmacro.cfgを編集する。

デフォルトではmprjという名前のインスタンスの位置を指定しているが、これを自分のマクロのインスタンスの位置の指定に変更する。フォーマットはinstance_name X_pos Y_pos Orientationになっている。

以下に例を載せる。

<user design instance> 100 100 N

このmacro.cfgには複数のインスタンスの位置を指定することが出来る。

以上の設定が完了したら、最後にラッパーのビルドを行う。

7. 最終レイアウトを生成する

caravel_user_project/で以下のコマンドを実行することで、ラッパーのビルドが開始される。

make user_project_wrapper

これが成功した時、gds/にはuser_project_wrapper.gdsが生成されている事だろう。

8. プリチェックを実行する

プリチェック用のDockerイメージをインストール

make precheck

プリチェックを実行

make run-precheck

プリチェックで問題が無いことを確認しよう。

おめでとう! これであなたのデザインは完成しました!あとはEfablessの指示に従ってリポジトリを登録するだけです。抽選に当たる事を願いましょう!

自分のリポジトリをここに提出しましょう。 https://efabless.com/open_shuttle_program

お疲れ様でした。

Tips

PDKのアップデート

  1. caravel_user_projectで$ make uninstall
  2. dependenciesのpdksとopenlane_srcを削除
  3. caravel_user_projectで$ make install
  4. caravel_user_projectで$ make setup

その他

[0:0]なポートを作るとLVS Error

FP_PDN_MACRO_HOOKSはモジュール毎にカンマで区切る

ポートのビット幅はちゃんと揃えた方がいい

OpenROAD reports unconnected nodes as a warning. OpenLane typically treats unconnected node warnings as a critical issue, and simply quits.

We’ll be leaving it up to the designer’s discretion to enable/disable this: if LVS passes you’re probably fine with this option being turned off.

set ::env(FP_PDN_CHECK_NODES) 0

podmanで試行錯誤した時のメモ

OpenLANEイメージをpullする際に

Error: short-name "efabless/openlane:2022.07.02_01.38.08" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf"

とか言われる場合があるので、/etc/containers/registeries.confに次の行を追加する。

sudo vim /etc/containers/registries.conf

以下の行を追加

 unqualified-search-registries = ["docker.io"]

実行時のエラーはこれで消せる

sudo touch /etc/subuid /etc/subgid
sudo usermod --add-subuids 10000-75535 $(whoami)
sudo usermod --add-subgids 10000-75535 $(whoami)
podman system migrate