Artix-7で一周したら止まるLチカを書く

最初に書いたLチカは色を変えながらずっとループするものだった。その後、すぐにUARTを書こうとして文字の出力が止まらなくなった。今日は少し反省したので、謙虚にLEDを0から3まで順番に点灯させていって最後はすべて消して止まる、という回路を書こうと思う。文字の出力が止まらなくなったので一歩下がってわかりやすい例でちゃんと止まるものを書くべきだと思ったのだ。もっと毎日がっつり時間をかけないとものにならないのでは?

とりあえず以下のようなコードを書いた。これは、2Hzで以下のように動作する。

  • LED0を点ける
  • LED1を点ける
  • LED2を点ける
  • LED3を点ける
  • すべて消して終了

それだけだ。これは動いた。これに関してはさほど苦労しなかった。ただし、いくつかの軽微なエラーにでくわした。

例えば、「multiple driver nets」というのにでくわした。これは、同じシグナルに複数のalways文から入力してしまっているということらしい。教科書を流し読みしただけだったので、これが禁止されていることを理解していなかった。

forums.xilinx.com

理解しないと覚えづらいたち(・・)なのでこれが禁止されている理由について考えてみたい。上のリンクで回答している人も説明してくれればいいのに……。それとも何かの背景知識があると自明なはずなのだろうか。Verilogでは回路の繋ぎ方を書いている、と思っていいのなら、このエラーは複数の順序回路の出力が一つのレジスタに入っているということを意味している。これは……片方が1でもう片方が0の時に不定になる? というかそもそもレジスタへの入力は一つなので繋ぐことができないのかな。本の索引でalwaysが乗ってるページを前からしらみつぶしに見ていたところ、真ん中の方のコラムに「二つのalwaysブロックを書いた結果レジスタが二つ生成されてそれらがセレクタを通して出力されてシミュレータでは動いたが実機では動かなかい」例が載っていた。一つしか宣言していなくてもalways文の数だけ生成されて、あとでセレクタがそれを繋ぐようになるということ? これは合成結果の回路を見ればわかるのかな。これっぽいけど、合成自体がエラーで落ちたんだよな。本が出てから後でこういう場合はエラーで落とすようになったということだろうか。

どうも電子工作の経験が皆無なことがハンデになっている気がしなくもない。あまりちゃんと理解できている感触がない。

ところで、以下のコードでalways @(...)コメントアウトしている部分を使ったところ正しく動かなかった(リセット後3回に2回は何も起きない、残りは2秒ほどすべてのLEDが点いて消える)のだけど、これは何でだろう。どうも、挙動を完全に理解できていないまま動かしていると前に進んでいる感があまりないな……。

module blink_for_a_while(
    input  wire      CLK100MHZ,
    input  wire      reset,
    output wire[2:0] led0,
    output wire[2:0] led1,
    output wire[2:0] led2,
    output wire[2:0] led3
);

    localparam CLK_PER_BLINK = 50000000;
    localparam CLK_COUNTER_W = 1+$clog2(CLK_PER_BLINK);

    reg[CLK_COUNTER_W-1:0] clock_counter = {CLK_COUNTER_W{1'b0}};

    reg      turn_led = 1'b0;
    reg[2:0] led_addr = 3'b0;

    reg[2:0] led0_state = 3'b000;
    reg[2:0] led1_state = 3'b000;
    reg[2:0] led2_state = 3'b000;
    reg[2:0] led3_state = 3'b000;

    assign led0 = led0_state;
    assign led1 = led1_state;
    assign led2 = led2_state;
    assign led3 = led3_state;

    always @(posedge CLK100MHZ) begin
        turn_led <= 1'b0;
        if (reset) begin
            clock_counter <= {CLK_COUNTER_W{1'd0}};
        end else if (clock_counter == CLK_PER_BLINK) begin
            clock_counter <= {CLK_COUNTER_W{1'd0}};
            turn_led      <= 1'b1;
        end else begin
            clock_counter <= clock_counter + 1'b1;
        end
    end

//    always @(turn_led or reset) begin
    always @(posedge CLK100MHZ) begin
        if (reset) begin
            led_addr   <= 3'd0;
            led0_state <= 3'b000;
            led1_state <= 3'b000;
            led2_state <= 3'b000;
            led3_state <= 3'b000;
        end else if (turn_led == 1'b1) begin
            if (led_addr != 4) begin
                led_addr <= led_addr + 1;
                case (led_addr)
                    2'd0: led0_state <= 3'b111;
                    2'd1: led1_state <= 3'b111;
                    2'd2: led2_state <= 3'b111;
                    2'd3: led3_state <= 3'b111;
                endcase
            end else begin
                led0_state <= 3'b000;
                led1_state <= 3'b000;
                led2_state <= 3'b000;
                led3_state <= 3'b000;
            end
        end
    end
endmodule
## This file is a general .xdc for the Arty A7-35 Rev. D
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_35 Sch=gclk[100]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { CLK100MHZ }];

## RGB LEDs
set_property -dict { PACKAGE_PIN E1    IOSTANDARD LVCMOS33 } [get_ports { led0[2] }]; #IO_L18N_T2_35 Sch=led0_b
set_property -dict { PACKAGE_PIN F6    IOSTANDARD LVCMOS33 } [get_ports { led0[1] }]; #IO_L19N_T3_VREF_35 Sch=led0_g
set_property -dict { PACKAGE_PIN G6    IOSTANDARD LVCMOS33 } [get_ports { led0[0] }]; #IO_L19P_T3_35 Sch=led0_r
set_property -dict { PACKAGE_PIN G4    IOSTANDARD LVCMOS33 } [get_ports { led1[2] }]; #IO_L20P_T3_35 Sch=led1_b
set_property -dict { PACKAGE_PIN J4    IOSTANDARD LVCMOS33 } [get_ports { led1[1] }]; #IO_L21P_T3_DQS_35 Sch=led1_g
set_property -dict { PACKAGE_PIN G3    IOSTANDARD LVCMOS33 } [get_ports { led1[0] }]; #IO_L20N_T3_35 Sch=led1_r
set_property -dict { PACKAGE_PIN H4    IOSTANDARD LVCMOS33 } [get_ports { led2[2] }]; #IO_L21N_T3_DQS_35 Sch=led2_b
set_property -dict { PACKAGE_PIN J2    IOSTANDARD LVCMOS33 } [get_ports { led2[1] }]; #IO_L22N_T3_35 Sch=led2_g
set_property -dict { PACKAGE_PIN J3    IOSTANDARD LVCMOS33 } [get_ports { led2[0] }]; #IO_L22P_T3_35 Sch=led2_r
set_property -dict { PACKAGE_PIN K2    IOSTANDARD LVCMOS33 } [get_ports { led3[2] }]; #IO_L23P_T3_35 Sch=led3_b
set_property -dict { PACKAGE_PIN H6    IOSTANDARD LVCMOS33 } [get_ports { led3[1] }]; #IO_L24P_T3_35 Sch=led3_g
set_property -dict { PACKAGE_PIN K1    IOSTANDARD LVCMOS33 } [get_ports { led3[0] }]; #IO_L23N_T3_35 Sch=led3_r

## Buttons
set_property -dict { PACKAGE_PIN D9    IOSTANDARD LVCMOS33 } [get_ports { reset }]; #IO_L6N_T0_VREF_16 Sch=btn[0]

Artix-7からUARTで通信しようとしていた

今のところ制御できていません。「A」と10回だけ出力しようとしたら「A」が無限に出力されてターミナルが埋まるし、「Hello World」と出力しようとしたら「・」が返ってきた。どういうこと? シミュレーションはうまくいってそうに見えるんだけど……。まあVerilog歴1日なので暖かく見守ってください。今日で2日か。

シミュレーションで出てきた波形
様子です。
今よーく見てみたら出力の形がおかしい気がするが、気のせいかもしれない。

久しぶりに書いたコードが全く自分の思ったように動かず、どうしていいのか、何がおかしいのか、あるいは何がわからないのかもわからないという焦りを感じている。直せるという自信があれば特に苦にはならないが、五里霧中だとそういえばこれくらいドロドロした気分になるもんだったな。ソフトウェア技術者が初心を思い返すには別のパラダイムの言語を学ぶんじゃなくて(しばらくやってたらたいていのパラダイムには触れたことくらいあるでしょう?)ハードウェアに挑戦したらよいのではないか。

何はともあれ、プログラミングで重要なことは「とにかく手を動かす」だと思っている。高速にトライアンドエラーを回すためには、手を動かした結果が見える必要がある。つまり、FPGA上で何かを計算したその結果を読み取れる必要があるということだ。そのためにはFPGAの中から外に対して情報を送信できる、信頼できる通信経路が必要だ。今のところ使える通信方法は4つのLEDだけなのだが、LEDを多色に点滅させて計算結果をエンコードするのは論外だろう。このLEDは意外と眩しいので目が潰れる。

というわけで、USB-UARTを通してホストのPCとシリアル通信をしたい。それができれば複雑な結果をPCに送ることができるので、例えばbrainfxxkのインタプリタを実装したり、あるいはレンダリングした画像ファイルを送ることもできるのではないか。というわけで次のチャレンジはUART、と決めたはいいものの、手持ちの本には載っていないようだ。まあ写経ではあまり意味がないので、これもまた一興と自力で実装しようとしたのだが、思っていたよりも難しい。

調歩同期方式についてはとりあえずここを読んでいた。

synapse.kyoto

通信方法については結構わかりやすい。アイドル状態では常に1を出力しておき、データが来たらstart bitとして0を出力し、データ(たいてい8bit)を下の桁から順に出力し、終わったらstop bitを出力する。stop bitの長さには1個、1.5個、2個と少し幅があるようだ。1.5個ってなんだよと思ったが時系列なので電圧上下1.5回分の長さということか。

電圧の上げ下げで信号を送るからには、単位時間について送信側と受信側が合意しておく必要がある。1秒に一回上げ下げする信号を送信したのに、受信側が2秒に一回上げ下げされていると思っていた場合、bitのうち二つに一つは落ちてしまう。この合意を得るためには大きく分けて二つの方法がある。信号と同時にその信号を読み取るのに使うクロックも同時に送る方法(クロック同期)と、先に送受信側の双方が合意しておいてそれぞれが自分のクロックを使う方法(調歩同期)だ。前者はクロックを受信側が持っている必要はないが、信号線を2本用意しないといけない。後者は一本でいいが、双方がそれなりに精度のよいクロックを持っている必要がある。ほーん。

UARTはどうやら調歩同期式で送った信号をシリアル・パラレル変換する回路までを指すようなのだが、どうもふんわりとこの通信のこと全体をこう呼んでそうな雰囲気を感じる。

Arty A7にはmicro USBの口がある。ここからUARTでPCと通信できるらしい。そのピンはこの部分だ。

github.com

最初、uart_rxd_outuart_txd_inとどっちがPC -> ArtyでArty -> PCなのかこんがらがっていたが(TXに書き込むと送信、と説明されていたので……)、リファレンスにわかりやすい図が書いてあった。uart_rxd_out(D10)に書き込むとPC側に送ることができるっぽい。

ついでに、一度PC側と繋いでおくと、USB-UARTでPCからArtyに書き込むことによって(Arty側に受信用の回路を何も書いていなくとも)micro USB端子の横のLEDが光る。通信できているのか? とか気になったら適当なソフトウェア(WindowsだとみんなTeraTermを使っているようだ。SSH用だと思ってたがUSBでのシリアル通信にも対応している)で繋いだ後何か書き込んでみると、とりあえずそのソフトウェアがちゃんと起動できているか、またボードが壊れていないかどうかは確認できるだろう。まあ、電源が入って回路を書き込めてる時点でmicro USBの端子を壊してしまったという可能性は消えているのだが。

reference.digilentinc.com

UARTの仕様を考える限り、

  • データを受け取ったらstart/stop bitを前後に足した10bitのレジスタに乗せ、端から1ビット送るごとにシフトしていく
  • 115200Hz(がよく使われているっぽい)になるよう分周して、そのタイミングで出力を切り替える
  • 送信中にどしどしデータを送られても困るので、送信中かどうかを外に伝える
  • データがなくなっていたら(外から追加のバイトが来なかったら)止まる

というようなモジュールがあればよい、と思っている。その部分はできた気がするが、これにデータを順次送りこむ方で失敗したっぽい。

今後大きめのデータを送る際には、データ送信で処理がブロックされるのは嫌なので、来たデータを置いておくメモリ的なものをUART部分に足しておく方がよいだろうか。計算する側はそこに書き込んでいって、UART側はそれをポップしていくとか? でもそうするとそのメモリからあふれた場合のことも考えておく必要があるが……でも回路を書くんなら問題の規模はだいたい予想ついてるはずだし……。

そもそもこんな風にミスるというのはまだ研鑽が足りていないのでは? おとなしくLEDだけで成否が判定できるような状態機械をいくつか書くくらいのステップは踏んだ方がよかったのでは? 本も読みなおした方がいいと思うが……。

まあ今日はもう遅いので寝ます。

Artix-7でLチカ

突然何を始めたんだ??

背景

高校の頃から生物を選択し、学部の頃は大腸菌の遺伝子を組み替えたりDNAで折り紙をしたりと割とWetな道をたどっていたはずなのだが、何の因果かどうやら回路設計に一枚噛む可能性が出てきたので、しばらくFPGA関係のことを調べて環境構築をしていた。マジで意味わからん道に迷い込んでいるな。俺はこの後どうなるんだ?

先人に「まあ個人でやる分にはXilinxのやつがいいかなあ」「ZyboっていうのとArtyってのがあって、ZyboにはArmコアが乗ってる。Artyにはない。まあでも正直どっちでもなんとかなる」「Verilogの勉強には『改訂 入門VerilogHDL記述』がよい」などの話を聞いていたので、とりあえず検索して出てきたArty A7 35Tを購入した。

japan.xilinx.com

値段はそこそこするが、「自腹を切ると覚悟が決まる」という信仰を持っているのでとりあえず自分が遊ぶための分は自腹で買った。

ところで、私はこれまで電子工作をやったことがほぼない。なぜか中学の頃に何かの講座みたいなのではんだ付けをやった記憶があるので多分何かしらやったことがあるのだろう。でも身にはついていない。 私のプログラミングに関するスキルセットはこの記事を読んでいる人は知っている通りで、一応アセンブリの読み書きはできるし、ローレベルプログラミングの知識がある程度ある。また、CPUには興味があったのでnandgameを頭をひねりつつ全クリした。Verilogに関してははとりあえず『入門Verilog HDL記述』を読んだ。それくらいの感じ。

実は週の頭にはArty A7は届いていたのだが、論文のrevisionで忙しかったこと(自分のミスとレビュアーの要求で結構な量の再計算をしていた)と、micro USBのケーブルが家にあると思ったらなかったという二つの理由で先延ばしにしていた。環境構築だけでそこそこ疲れたので金曜日はVivadoをインストールしたあとすぐ寝て、土曜日は昼まで寝た後飯食って漫画読んでたら17時になってたので急いでVivado立ち上げてコード書いてたらこんな時間になった。しかしLチカした動画の時刻を見ると、実際にはブログを書いていた時間の方が長いっぽい。

というわけで今回はズブの素人が購入後Lチカまでにしたことを書いておく。

環境構築まで

とりあえず以下を用意した。

  • Arty A7 35T
  • micro USBケーブル(Arty A7には同梱されていないので注意)
  • Vivadoを動かすためのマシン(今回は折角あるのでWindowsだがLinuxでもいけるっぽい)

続いて、任意の言語において最難関の関門であるところの環境構築を行った。 導入するソフトウェアはVivado HL WebPACK。これは使える基盤が限定されているかわりにライセンスが無料のバージョンらしい。

Xilinxのページでアカウントを作り、Vivado最新版のインストーラをダウンロードしてきたところ、WebPACKを明示的にインストールする選択肢がなかった。 たとえば、以下のページではVivadoを選択したのち、Vivado Design Edition, System Editionと並んで WebPACKが映っている。

www.acri.c.titech.ac.jp

しかしバージョン2020.3にはこのWebPACKの選択肢はなく、手元にライセンスキーがないためそこでいったんやめて少し情報収集をしてみた。 するとフォーラムに似たようなことを聞いている人がいた。状況が少しややこしいので、時系列を意識する必要がある。

  • (2020年6月)最初の人は、明示的にWebPACKと書いてくれていないのが不安だったようで、WebPACKのインストール方法を尋ねている。
  • (一時間後)中の人が来て、「WebPACK版もUnified Installer経由でインストールできる。ライセンスの問題が発生しているなら教えてほしい」と回答している。
  • (その翌日)最初の人が「WebPACKって書いてないから不安だったけどライセンスを要求されてないってことは大丈夫ってことでいいのね」と言ってcloseしている。
  • (2020年10月)30年間Xilinxを使ってきたと名乗る人物が「WebPACKと明記されてないのはさすがに不親切だろう」と苦言を呈している。
  • (数時間後)最初の人が2020.1を使えば明示的にWebPACK版をインストールできる、とコメントしている。
  • (数時間後)苦言を呈した人が「確かにそのバージョンなら大丈夫だった」とコメントしている。
  • (2021年4月)別の人が来て、2020.3にはWebPACK版の選択肢がないんだけど、と質問している。
  • (数時間後)上と同じ人が、2020.1ならWebPACK版の選択肢がある、とコメントしている。
  • その後進展はなし。

forums.xilinx.com

確かに2020.3にはWebPACKの選択肢がない。以下のページでもこれに関する言及があり、2020.2ならWebPACKをインストールできる、と書かれている。

www.comp.sd.keio.ac.jp

とりあえず安全側に倒して2020.2版を使ってインストールした。数十GBくらいの容量を要求される。結構時間がかかる。 待ってる間に上のリンク先の記事を読んでいた。とりあえず動かす、というところまでに必要なことがほぼすべて書かれており、非常にありがたい。

github.com

ここまではインストール後に済ませておく必要がある。

Vivadoでコードを書く

さてVivadoを起動だ。とりあえず、『FPGAプログラミング大全』の第二章をなぞろうと思う。

さて、Verilogの部分は同じでよいが、Constraintはボードに依存する。本ではCora/Zyboを使っているので、写経では対応できない。

デフォルトのConstraintファイルはDigilent社のレポジトリにある。すべてコメントアウトされているので、必要な部分のコメントを外す。

github.com

今回は以下のようにした。Lチカだけなら、クロックと、LED、そしてボタンだけあればとりあえず足りる。

## This file is a general .xdc for the Arty A7-35 Rev. D
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project

## Clock signal
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_35 Sch=gclk[100]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { CLK100MHZ }];

## RGB LEDs
set_property -dict { PACKAGE_PIN E1    IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[2] }]; #IO_L18N_T2_35 Sch=led0_b
set_property -dict { PACKAGE_PIN F6    IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[1] }]; #IO_L19N_T3_VREF_35 Sch=led0_g
set_property -dict { PACKAGE_PIN G6    IOSTANDARD LVCMOS33 } [get_ports { LED_RGB[0] }]; #IO_L19P_T3_35 Sch=led0_r

## Buttons
set_property -dict { PACKAGE_PIN D9    IOSTANDARD LVCMOS33 } [get_ports { RST }]; #IO_L6N_T0_VREF_16 Sch=btn[0]

名前の部分はデフォルトから変更している。get_portsの部分だ。最初、クロックって何Hzくらい出てるんだ? と思ってリファレンスを見に行ってたが、もう一度このConstraintファイルを見て自分の馬鹿さ加減に驚いた。クロックシグナルの名前が CLK100MHZ になっている。まず読みましょう。

コードとその後に関しては本の通りで動く。一つだけ、本にはジャンパピン設定を済ませておきましょうと書かれているが、Arty A7の場合はJTAGによるconfigurationを開始した場合はジャンパピンの状態によらずconfigurationが始まるらしい。お手軽。

本とか

ちなみにこのブログは特に広告収入とかはもらっていません。

  • 『入門Verilog HDL記述』小林優 CQ出版社
    • 先人にお勧めされたのでとりあえず半日かけて第一部、第二部を読み、Verilog HDLの雰囲気だけ知った。どんな言語も書かずに知れるのは雰囲気までだ。いくらかコードを読み書きしてから戻ってこようと思う。
  • 『FPGAプログラミング大全』小林優 秀和システム
    • おや、著者同じ人なのか。こちらではCora/Zyboがお勧めされているが、これを見るより先にArty A7を買ってしまったのでまあ仕方ない。むしろちょっと調べないといけない分だけ身になる可能性がある。Arty A7には画面出力がないので、やるならPmod VGAを購入する必要がある。とりあえず今は画面出力の部分は飛ばしながら様子見をしている。

ほかにもいくつか買って、積んでいる。読んだら読んだときに書くかも。

Windowsマシンを使う

というわけで設定していた。

初期設定としてやったことは、とりあえず

  • Ctrl/CapsLockの交換
  • WSLでfish/powerline/tmuxを使う

だ。これだけのことでメモしておこうと思うとは思わなかった。

Ctrl/CapsLockの交換

複数の方法が紹介されており、「そのためだけにツールを一つ入れる」「レジストリに何かを置く」「もう少し一般的なキーマップツールを入れる」が見つかった。

とりあえず今回はレジストリに謎のバイナリを置くという方法をとった。以下の記事を参考にした。

ascii.jp

さすがに何もわからないまま書いていくと恐ろしいので(ある程度あたりはつくものの)、一応少し調べた。以下のページが参考になる。ただし最後に出てくる解説用の表には(あまり本質的ではないので問題ないが)typoがある。

Scancode Mapによるキー割り当ての変更

4つしかマッピングを追加していないのに「マッピング数」が5になっているが、ここからこのファイルを読み込んでいるルーチンが先頭ポインタだけを受け取って、ヌルが登場するまで読み続けるという方法をとっているのだろうと推測できる。実際にはこの「マッピング数」は格納する領域の長さを指定しているのに違いない……と思ったが、よく考えるとこのファイルの値を読むなら長さわかるじゃん。中では何が起きてるんだ?

ちょいちょい紹介されているctrl2capsとかいうやつの使い方は以下で紹介されていた。

www.atmarkit.co.jp


しかし、昔ラボで使っていた先輩のおさがりのRealForceには裏面にdipスイッチがあって、それでいくつかのキーを交換できたと思うのだが、今のモデルにはなくなってしまったようだ。今私が家で使っているやつは今の最新のものより一回り古いものだが、dipスイッチはついていない。ラボで使っているRealForceは結構新しめのモデルで、こちらはコードを指してからFn + F11とかを押すことでCtrlとCapsLockを交換できるという機能がついている。しかし何台かのマシンを机に置いてときどきキーボードを差し替えるという状況になると、この機能が面倒に思えてならない。引っこ抜くたびにリセットされてしまうので、再設定しないといけないからだ。dipスイッチ、再度実装してくれませんかねえ……。まあ、RealForceは耐久性が高すぎて壊れてくれないので、買い替えることは当分なさそうなんだけど……。

WSLでfishとbobthefishテーマを使う

いまだにpowerline用のフォントを入れるのに苦労してしまう。以前にもRyzenマシンをセットアップしていたときにフォントが壊れてしまって、結局何故かja_JP.UTF-8になっていたlocaleをen_US.UTF-8にしたら直るという感じのことが起きていた。そういえばあれ何でja_JP.UTF-8になってたんだろう? 英語でセットアップした気がするんだけどな。あんまりよく覚えてない。

さて、私はfish shellを使っていて、かつbobthefish-themeを使っているので、powerline本体は必要としていない。なのでフォントだけ導入して、fishを入れたあと fisher install oh-my-fish/bobthefish-theme で完了するはずだった。

apt install fonts-powerlineを使うと機能しなかったので、一度removeしてからgithubpowerline/fontsをダウンロードして導入する。WSLからbash install.shをしたが上手くいかなかったので(おそらくWSLのBash経由でインストールすると、画面をレンダリングしているWindowsからはWSL上にインストールされたフォントが見えていない?)、Powershellから.ps1を実行するんだろうなと思いつつ一応検索する。

同じことで困っている人がいた。

stackoverflow.com

どうやらブログにインストール方法をまとめてくれている人がいるらしい。

slmeng.medium.com

一応翻訳しておく。元のブログは必要以上に丁寧な気がするのでざっくりまとめた。

  1. GitHubpowerline/fontsをクローンする。
  2. Powershellを管理者として起動、持ってきたfonts/ディレクトリにcdする。
  3. Set-ExecutionPolicy Bypassと入力する。
  4. .\install.ps1を実行する。しばらく待つ。
  5. 一応、Set-ExecutionPolicy Defaultでデフォルトに戻しておく。

これでフォントのインストールは完了する。これが終わると、WSLのコンソールからフォントを選択できるようになる。ここで、ウィンドウの上についてるバーの部分を右クリックしないと設定が出せないことに注意。プロパティを開いてフォント設定から「XXX font For powerline」みたいなやつを選ぶ。

するとbobthefishがちゃんと描画される。これでOK。と思っていたら、tmuxを開いた瞬間にまた世界が壊れてしまった。

tmuxの中でFontがリセットされる問題を解決する

ググったら出てきた。

github.com

CodePageを437にするとよいらしい。またレジストリいじるのかよ。

いじったら本当に解決した。

感想

一瞬で終わると思ってたんだが1時間くらい経ってるな。

まだ一日目なのでわからん。でもWSLは結構いい感じですね。最初はSSDオンボードのm2以外に空いてるPCIeに変換ボード差してもう一つ積んでLinuxも入れようと思っていたが、この感じなら必要ないかもな。お金に余裕ができたら差すかも。

ところでGWの最初あたりにGUIが来た!ってなってたけど、まだInsider Previewっぽい? どうしようかな……。

追記

そうそう、これだとインストールしたpowerline用フォントに日本語が入っていないので、日本語のファイルを開くと全て豆腐になっている。日本語対応powerlineフォントを導入することでこれは解決する。

github.com

とはいえ、何が悪いのかはわからないのだが、どれも若干幅の計算がおかしくなって、右側の時刻表示がはみ出して次の行まで来ていたり、最初の1文字がプロンプトに埋まってしまったりした。もうちょっといじってから報告してみようかな。

特殊化が存在するかどうかをチェックする

機能の紹介

toml11では、ユーザー定義型との間にいくつかの変換方式を提供している。

1つめはtoml::valueを受け取るコンストラクタを使用するもの。続いてfrom_toml(const toml::value&)というメンバ関数を定義するもの。最後にtoml::from<T>という構造体を定義するもの。

このどれかが提供されているなら、ユーザー定義型でもtoml11は様々な箇所で自動変換を行ってくれる。

namespace user
{
struct vec
{
    explicit vec(const toml::value& v)
        : x(toml::find<double>(v)),
          y(toml::find<double>(v)),
          z(toml::find<double>(v))
    {}
    double x, y, z;
};
} // namespace user

const user::vec pos = toml::find<user::vec>(file, "position");

コンストラクタとメンバ関数を両方用意したのは正直若干失敗だったかなと思っている。メンバ関数版は内部的にデフォルトコンストラクタを要求するし、そうなると効率もあまりよくないからだ。まあ何かで使えることもあるかもしれないし、いきなりAPIを廃止するわけにはいかないので今の所そっとしておいている。

toml::from<T>は、メンバ関数やコンストラクタを追加できない外部ライブラリに対する対処法だ。外から変換関数を差し込むことができる。

これは、まず定義のないテンプレート構造体を用意しておいて、もし特殊化されていたならそれをつかうという方法で実装している。

namespace toml
{
template<typename T>
struct from; // 定義なし
} // toml

ユーザーがnamespace tomlを開かないといけないので若干微妙な気もする。この部分はもう少しなんとかできたんじゃないかなあと思っているが……。

実装

特殊化が存在するかどうかは以下のようにしてチェックしている。

struct has_specialized_from_impl
{
    template<typename T>
    static std::false_type check(...);
    template<typename T, std::size_t S = sizeof(toml::from<T>)>
    static std::true_type check(toml::from<T>*);
};

template<typename T>
struct has_specialized_from :
    decltype(has_specialized_from_impl::check<T>(nullptr)){};

定義があればsizeofの値が定まる。その場合下の関数が優先されてマッチする。そうでない場合、下の関数がマッチしないので上の関数で解決される。

これは結構、果たして大丈夫なのかなというコードだが、gcc, clang, MSVCのほぼありとあらゆるバージョンで動いているのでまあいいんじゃないかという気持ちで使っている(は?)。

問題と回避策

さて、今回もまたissue対応の話なのだが(解決した非自明な問題は記事にできるから便利だ)、これはEigen::VectorXdtoml::valueから変換しようとした際にtoml::getambiguousになるというものだ。

これは、Eigen::Matrixが(Eigen::VectorXdエイリアスである)以下のようなコンストラクタを持つことに起因する。

template<typename T>
explicit Matrix(const T& x);

これはありとあらゆるTに一致する。おそらく、数値そのものが来た時と、異なるサイズのMatrixなどが来たときのためにあるのだろうが、この関数は当然toml::valueにもマッチしてしまう。よって変換方法がambiguousになる。

これを回避するには、コンストラクタが存在してもtoml::from<T>が存在したならtoml::from<T>を優先するという順位を付けるしかない。そしてつけた。toml::getの条件をis_constructible<T, toml::value> && negation<has_specialized_from<T>>にしただけだが。

というわけで、変換方法に優先順位が入った。toml::from<T> == T.from_toml > constructorという順番になる。toml::from<T>T.from_tomlに優先順位をつけなかったのは、これはどちらもユーザーが足さない限り発生しないものなので、重複していたらエラーになってほしいのではないかと思ったからだ。

そんな感じです。あ、READMEにこのこと書くの忘れてた。書きます。

GCCの-Wshadowとその推移について、あとお前を消す方法

-Wshadow-Wallでオンにならない警告オプションで、shadowingを警告してくれる。

そもそも、実はC++にもshadowingというのはあり、ブロックを分けさえすれば同じ名前の変数を定義しても特に問題はない。ブロックが同じだとエラーになるのであまり便利ではないが。

#include <iostream>

int main()
{
    int x = 42;
    std::cout << x << std::endl;
    
    {
        double x = 3.14;
        std::cout << x << std::endl;
    }
    return 0;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

まあでも意図せずこれをやると困ることもあるので、-Wshadowというオプションによって警告することができる。

prog.cc: In function 'int main()':
prog.cc:9:16: warning: declaration of 'x' shadows a previous local [-Wshadow]
    9 |         double x = 3.14;
      |                ^
prog.cc:5:9: note: shadowed declaration is here
    5 |     int x = 42;
      |         ^

これには三つのオプションがさらにあり、global, local, compatible-localとなっている。この順に少しずつ弱くなる。

globalはありとあらゆるshadowingを警告する。これはあとでいくつかまとめて例を出すつもりだ。 localは、ローカル変数がローカル変数をshadowingしているとき(一番最初の例だ)に警告する。 compatible-localは、ローカル変数がローカル変数をshadowingしているとき、かつそれらが変換可能であるときだけ警告する。

個人的には、globallocalの間にもう少し何かある気がしなくもないが、あまりうまく区別できなさそうなのでよくわからなくなっている。

さて、globalだけではまずかったのか、どういうときに人類はお目こぼしを貰いたいのだ? ということが次の疑問だ。

例えば、globalはグローバルにあるusingによる型エイリアスと、関数の引数の名前が衝突していたりすると警告する。

#include <iostream>

using x = int;

double f(double x) {return x * 2;}

int main()
{
    std::cout << f(3.14) << std::endl;
    return 0;
}
prog.cc: In function 'double f(double)':
prog.cc:5:17: warning: declaration of 'x' shadows a global declaration [-Wshadow]
    5 | double f(double x) {return x * 2;}
      |          ~~~~~~~^
prog.cc:3:7: note: shadowed declaration is here
    3 | using x = int;
      |       ^

他に、例えば以下の人は「globalな変数やエイリアスenum classの名前が警告される」と文句を言っている。

c++ - -Wshadow=global considers enum class entry shadowing a global. Why? - Stack Overflow

つまりこういうことだ。

#include <iostream>

inline constexpr int x = 42;

enum class foo
{
    x = 0,
    y = 1
};

int main()
{
    std::cout << x << std::endl;
    return 0;
}
prog.cc:7:5: warning: declaration of 'x' shadows a global declaration [-Wshadow]
    7 |     x = 0,
      |     ^
prog.cc:3:22: note: shadowed declaration is here
    3 | inline constexpr int x = 42;
      |                      ^

ここで注意したいのは、これがenumではなくenum classであることだ。2021年にこんなことを注意する必要はないと思うが、enum classなのでfooxyにアクセスするにはfoo::xと明示的に書く必要がある。なので実際にはここで混乱は生じない。

これはどうも、Stackoverflowで質問する人が出てくる程度には、厳しすぎると思われていたのだろう。ローカル変数のshadowingだけをチェックさせたい人のために、=local=compatible-localが存在するというわけだ。

この-Wshadow=hogeのオプションは、gcc-7で導入された。これ以前のバージョンでは、-Wshadow=globalといった制御は効かない。 GCC 7 Release Series — Changes, New Features, and Fixes - GNU Project

ついでに-Wshadow自体はいつ導入されたのかと思って、6、5、4と遡ってみたが、changesの中にshadowは入っていなかった。てことは結構昔からある機能なのかな。

こういうオプションごとの年表みたいなのが欲しいと結構昔から思っているが、結構作業が膨大になりそうなので手を出せずにいる。せめて、ということでこうして調べてものだけ備忘録を残している。


ところでなぜ調べたのか? いつものことだが、toml11に「-Wshadowで警告が出る」との報告がきたからだ。世の中には私の想像よりもたくさん、-Wall -Wextraではオンにならない警告オプションを大量に手動で設定し、-Werrorを付けてコンパイルしている人がいるらしい。

原因の一つは引数の名前をkeyにしていたからで、これがtoml::keyという型エイリアスと衝突していた。これはいい。直せる。

もう一つは、型エイリアスtoml::booleanenum class value_tの名前、toml::value_t::booleanが衝突しているというものだ。これはAPIに深く刻まれているので修正とかそういうのはできそうにない。

一応念のため、これが衝突しているからと言って問題が起きるのは限られたケースでしかないと主張しておく。そもそもenum classの列挙子は値であり、toml::booleanは型だ。コンテキストからこれらの区別がつかなくなることはほぼない(非型テンプレート引数を取るメタ関数と型名を取るメタ関数を別の名前空間で同名で定義した後にそれぞれをusing namespaceすると衝突するだろうか?)。また、通常、enum classの列挙子にアクセスするにはvalue_t::booleanのようにする必要があり、これを衝突させるにはusing enumというC++20での新機能を使用する必要がある。その上で、toml::booleanをあいまいにするためにusing namespace tomlあるいはtoml名前空間の中でことを起こす必要がある。ちなみにこれらを同時に使っても(using namespace toml; using enum toml::value_t;)g++-11では衝突による問題は起きず、問題を起こそうとするとusing enum toml::value_tではなくusing toml::value_t::booleanusing toml::booleanを同じスコープで両方使う必要がある。ここまで明示的に書いて問題を起こすのはもう故意ではないのか。これはfalse positiveだと言って過言ではあるまい。

というわけで、「お前を消す方法」の出番だ。主要コンパイラは、コード内の特定の箇所においてコンパイラオプションをコード側から制御する方法を持っている。コードを読んでいるとたま〜にでくわすことがある。

Diagnostic Pragmas (Using the GNU Compiler Collection (GCC))

// その時点でのオプション状態の情報をコンパイラの内部スタックにpush
#pragma GCC diagnostic push
// オプション状態をいじる(-Wshadowingを無視)
#pragma GCC diagnostic ignored "-Wshadow"

// この範囲のコードは`-Wshadow`の影響を受けない

// 前にpushしたオプション状態に戻る。
// ちなみに何もpushしていないと、コマンドライン引数で与えられた状態に戻るらしい。
#pragma GCC diagnostic pop

これによって様々な違法行為をしつつ表面上は質の高いコードを保っているかのような悪事が可能になる。

とはいえ、「プログラマがここ(だけ)は大丈夫であるということを示す」という意味で、これはRustのunsafeに近いものだろう。むしろ、「このコードで-Wshadowを付けないでください」と言うよりも、「ここだけどうにもならないけれど、そこ以外は-Wshadowで検査してくれて大丈夫です」の方が安全ですらある。のでこれは使い方によってはいいものだ。ところで、一般的にあらゆる道具は使い方によってはいいものである。

そういうわけで、ここはどうしようもないというところではpragma GCC diagnostic ignoredを使い、あとでpopして元の状態に復帰させておこう。

ちなみにこれをいろいろなコンパイラで使う方法は以下の記事にまとめられている。

https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/

あと、-Wshadowとこの辺の挙動が両方gcc 4.8とgcc 5以降で結構違ったので困った。えっgcc 4.8のサポート? ええ、まだしてますよ……でももうそろそろ切ってもいいだろうか……

ビルドもできるヘッダオンリーライブラリ

長いことまともな記事を書いていないのでリハビリをします。

ヘッダオンリーライブラリとして使えるものの、必要な場合はコンパイルできる、というライブラリはチラホラ見かける。例えば spdlog はその一つで、 "header only/compiled library" を名乗っている。

GitHub - gabime/spdlog: Fast C++ logging library.

ヘッダオンリーライブラリは、単にダウンロードしてきてインクルードパスに入れさえすれば使えるという手軽さがいいところだが、当然コンパイル時間が伸びてしまう。ビルドと環境構築にある程度慣れており、何度もビルドするためにコンパイル時間を短縮したい人には、ビルド済みバイナリとして使える機能があると尚良い。

仕組みを簡単に書いておく。しかしこれは説明をする必要がないくらい単純だ。

  1. まず、普通にコンパイルできるライブラリを書く。
  2. MYLIBNAME_HEADER_ONLYもしくはMYLIBNAME_COMPILEDのようなマクロを作り、それによって MYLIBNAME_INLINEを空にしたりinlineにしたりする。

以上。

しかしながら、template関数は基本的にはヘッダにしか書けない。インスタンス化する時に中身が見えないと困るからだ。ただし、ここで先んじていくつかのよく使う型に関するインスタンスを陽に定義しておいて、ビルド時間を少々削減することもできる。extern templateだ。

// .hpp
template<typename T>
void func(const T&) {/*...*/}

#if !defined(MYLIBNAME_HEADER_ONLY)
extern template void func(const int&);
extern template void func(const double&);
#endif
// .cpp
template void func(const int&);
template void func(const double&);

これによってテンプレート関数もビルド済み関数として提供できる。

とはいえ、これはある程度要求されるインスタンス化が決まっている状況でないとあまりビルドは早くならない。

toml11でもビルド済みバイナリを提供する機能を書いてみていたが、かなり労力をかけないとコンパイル時間がほぼ変わらなさそうだったので、今のところお蔵入りになっている。でもtoml11のコンパイル時間が激遅なのは自分でも悩みの種なのでまた書いてみよう。多分、単純にコードが長いということが問題っぽいんだが。

最近書いている数値計算用のライブラリにはこの機能を実装している。そちらはgslにも依存しているので単にビルド済みだけでよかったかもしれないが、単にやってみたかったのでやった。

以上、モジュール時代にはそぐわなくなるであろう話でした。年単位で前にモジュールとして使えるようなライブラリを作ってみようとしたらモジュールが使えるコンパイラがMSVCしか出てこなくて諦めたことがあった(当方個人ではwindowsマシンを所有しておりません)。今はgccでもclangでも-fmodules-tsを使えばいいのか。今度やってみます。

その当時はモジュールとしてもモジュール以前としても使えるようなライブラリをどうやって作るかみたいなことを調べていて、そういえば boost-ext/ut がまさにそういうライブラリだったなと思い出した。

github.com

今度もう一回やってみてまとめようと思う。リハビリ終わり。