カーネルモジュールのロードに失敗した話(insmod が Operation not permittedでコケる)。

結論

セキュアブート環境下*1では、カーネルモジュールに署名をつけましょう

経緯

卒論の関係でカーネルモジュールを書いていて、「さて実機で動かすぞ!」という段階でコケました。 自分が悪いコードを書いたせいかと思い、よく転がっていそうな単純なモジュールを書いても以下のようにコケる...

$ sudo insmod hello.ko
 insmod: ERROR: could not insert module hello.ko: Operation not permitted

解決策を調べると、insmodでうまくいかないなら、depmodしてmodprobeを使うといいとか書いてあって試しても、うまく行かない...

最終的には How to sign things for Secure Boot に書いてあるとおり、署名をつけたら成功しました。

(結局カーネルモジュールは動いたものの、思うような結果が得られず卒論には使えなかった...)

*1:厳密な条件は違いそう

gccで本当に使われているデフォルトリンカスクリプトを手に入れる。

結論

ld --verboseではなく、gcc -Wl,--verboseを使いましょう。

環境

$ gcc --version
gcc (Ubuntu 8.2.0-7ubuntu1) 8.2.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ ld --version                 
GNU ld (GNU Binutils for Ubuntu) 2.31.1
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.

$ ld --verbose

というようなコマンドを実行するとデフォルトリンカスクリプトが手に入る。というような記述をしばしば見かけます。(リンカ・ローダ実践開発テクニックとか)

このスクリプトを指定して生成した実行バイナリと、リンカスクリプトを指定しないで生成した実行バイナリは等しいものになると思いますよね?

という訳で、得られたリンカスクリプト*1 (default.ldsとします。)を用いて、以下を実行します。

$ gcc -o hoge hoge.c
$ gcc -o hoge_with_lds hoge.c -T default.lds
$ diff hoge hoge_with_lds 
Binary files hoge and hoge_with_lds differ 

実行結果が期待通りにならない...

恐らくld --verboseで得たリンカスクリプトが違うんだろう... *2

ではどうすれば実際に使われているスクリプトを得られるかというと、コンパイル時に-Wlオプションを使って、直接リンカに--verboseオプションを渡すとうまくいきます。

$ gcc  -Wl,--verbose

これで得られたスクリプトを用いて再度コンパイル&差分を確認すると、無事差分がなくなりました。

ちなみに実際のスクリプトの差を確認したら以下のようになりました。

$ diff default.lds default_gcc.lds
1c1
< /* Script for -z combreloc -z separate-code: combine and sort reloc sections with separate code segment */
---
> /* Script for -pie -z combreloc -z now -z relro -z separate-code: position independent executable, combine & sort relocs with separate code segment */
14c14
<   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
---
>   PROVIDE (__executable_start = SEGMENT_START("text-segment", 0)); . = SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
155,157c155,156
<   .got            : { *(.got) *(.igot) }
<   . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
<   .got.plt        : { *(.got.plt)  *(.igot.plt) }
---
>   .got            : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) }
>   . = DATA_SEGMENT_RELRO_END (0, .);
240a240
> 

どうやら オプションごとにデフォルトリンカスクリプトは異なるらしい。

gccコマンドを使用して入手した方は -pie -z combreloc ... 向けみたいですね。

そういえば、ある時から(確かUbuntuでは)位置独立実行形式(PIE)がデフォルトになったと聞いた気がするな...

*1:実際の ld --verbose の出力はメタ情報等を含むのでいい感じに整形してください

*2:gccは内部?で色々なコマンドを実行しているので、この情報だけでは ld --verbose で得たリンカスクリプトが間違っていることが原因とは断定できない気はします

RISC-Vについて(CPU実験その2)

この記事はIS17er Advent Calendar 2017 - Adventarの3日目の記事として書かれました。 「日本語で書かれたRISC-V命令一覧が欲しいな」と思ったのがこの記事を書いたモチベーションなので、基本的にはCPU実験の班員向けに書いたRISC-Vの基本命令RV32Iのまとめの流用です。 おもしろみのない記事になってしまいました...

アーキテクチャを選ぶ

CPU実験では自分たちで作るプロセッサ及びコンパイラが対応するISA*1も自分たちで策定します。
課題であるレイトレーシング用プログラムmin-rtがOCamlのサブセットであるmin-camlで書かれているので、既存のmin-camlコンパイラが対応するPowerPCSPARCを参考にする班*2や、コピュータアーキテクチャの名著であるパタヘネ本で引用されているMIPSアーキテクチャをベースとする班が多いようですが 、 僕の所属するIS17er第1班では主に僕の独断と偏見でRISC-VというアーキテクチャをベースとしたISAを使用することにしました。

なぜRISC-Vなのか

RISC-VはRISCという言葉の生みの親でもありパタヘネ本の著者でもあるパターソン博士の属するUCバークレー発ののRISCアーキテクチャです。

などの理由でRISC-Vアーキテクチャを採用しました。

RISC-Vの基本構成

今回の実験では32bitの基本命令及び単精度FPU命令からなるRV32IFをベースにしました。

RV32Iの各命令について

RV32Iの命令エンコーディングは下記表に示す5つのタイプに分けられます。
f:id:progrunner17:20171203211238p:plain

上記の様に即値が複雑になっているのは、できるだけ同じビット位置に同じデータを入れることでハードウェアが簡単になるからだそうです。*5

命令はopcodeによって処理を行う演算器が指定され、さらにfunct3、及びfunct7によって細かい演算が指定されます。
例えばadd命令はオペコード 0110011でALUを用いてrs1とrs2の演算が行われることを指定し、funct3:000で ALUに加減演算を指定し、funct7で減算と区別されるという感じです。

RISC-V 命令一覧(算術論理演算まで)

短時間で書いたので抜けが多くミスもあると思います。 あくまで公式の仕様であるSpecifications - RISC-V Foundationを読む参考にしていただけたらと思います。

lui

  • オペコード 0110111
  • funct3: 即値として使用されるためなし
  • 命令形式: lui rd, imm
  • 命令意味: load upper immediate
  • 疑似コード rd = imm << 12,pc++
  • 即値タイプ: U

レジスタの上位20bitに即値を入れ、下位12bitは0埋め。 oriやaddiと組み合わせて任意の32bitの即値を得るのに使える。

auipc

  • オペコード 0010111
  • funct3: 即値として使用されるためなし
  • 命令形式: auipc rd, imm
  • 命令意味: add upper immediate to pc
  • 疑似コード rd = pc + (imm<<12),pc++
  • 即値タイプ: U

luiの結果をpcに足した値を得る。jalやbranch系命令では即値の範囲が狭いので、遠くへ相対ジャンプをしたい時に使える。

jal

  • オペコード 1101111
  • funct3: 即値として使用されるためなし
  • 命令形式: jal rd, imm
  • 命令意味: jump and link
  • 疑似コード rd = pc + 1 ,pc += imm
  • 即値タイプ: J

専用のリンクレジスタがないので、rdに指定したレジスタに戻りアドレスを入れてPC相対ジャンプをする。 基本的にx1が使われることがRISC-Vのspecの推奨らしい。戻りアドレスが不要な場合はx0を使うとよい

jalr

  • オペコード 1100111
  • funct3: 000
  • 命令形式: jalr rd, rs1, imm
  • 命令意味: jump and link register
  • 疑似コード rd = pc + 1 ,pc = rs1 + imm
  • 即値タイプ: I

rs1で指定したレジスタの示すアドレスへジャンプする。 luiやaddi、で即値を生成して絶対ジャンプや、auipcの結果を用いて相対ジャンプする際に使える。

BRANCH 系命令

  • オペコード 1100011
  • 即値タイプ: B

beq

  • funct3: 000
  • 命令形式: beq rs1, rs2, pc + imm
  • 命令意味: branch equal
  • 疑似コード if(rs1 == rs2)then pc += imm else pc++

bne

  • funct3: 001
  • 命令形式: bne rs1, rs2, pc + imm
  • 命令意味: branch not equal
  • 疑似コード if(rs1 != rs2)then pc += imm else pc++

blt

  • funct3: 100
  • 命令形式: blt rs1, rs2, pc + imm
  • 命令意味: branch less than
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bge

  • funct3: 101
  • 命令形式: bge rs1, rs2, pc + imm
  • 命令意味: branch greater equal
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

bltu

  • funct3: 110
  • 命令形式: bltu rs1, rs2, pc + imm
  • 命令意味: branch less than unsigned
  • 疑似コード if(rs1 < rs2) then pc += imm else pc++

bgeu

  • funct3: 111
  • 命令形式: bgeu rs1, rs2, pc + imm
  • 命令意味:branch greater equal unsigned
  • 疑似コード if(rs1 >= rs2)then pc += imm else pc++

LOAD系命令

  • オペコード 0000011
  • 即値タイプ: I

lb

  • funct3: 000
  • 命令形式: lb rd, offset(rs1)
  • 命令意味: load byte
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトを符号拡張した値をrdで指定したレジスタにセットする.

lh

  • funct3: 001
  • 命令形式: lh rd, offset(rs1)
  • 命令意味: load half word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある2バイトを符号拡張した値をrdで指定したレジスタにセットする.

lw

  • funct3: 010
  • 命令形式: lw rd, offset(rs1)
  • 命令意味: load word
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1ワードを符号拡張した値をrdで指定したレジスタにセットする.

lbu

  • funct3: 100
  • 命令形式: lbu rd, offset(rs1)
  • 命令意味: load byte unsigned
  • 疑似コード rd = mem[rs1+offset] ,pc++

メモリのrs1+offset番地にある1バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

lhu

  • funct3: 101
  • 命令形式: lhu rd, offset(rs1)
  • 命令意味: load half word unsigned
  • 疑似コード rd = mem[rs1+offset]

メモリのrs1+offset番地にある2バイトをゼロ拡張した値をrdで指定したレジスタにセットする.

STORE系命令

  • オペコード 0100011
  • 即値タイプ: S

sb

  • funct3: 000
  • 命令形式: sb rs2, offset(rs1)
  • 命令意味: store byte
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地にLSBをセットする。

sh

  • funct3: 001
  • 命令形式: sh rs2, offset(rs1)
  • 命令意味: store half word
  • 疑似コード mem[rs1+offset] = rs2,pc++

メモリのrs1+offset番地に下位2バイトをセットする

sw

  • funct3: 010
  • 命令形式: sw rs2, offset(rs1)
  • 命令意味: store word
  • 疑似コード mem[rs1+offset] = rs2 ,pc++

メモリのrs1+offset番地に1wordをセットする。

OP-IMM系命令(即値算術論理演算)

  • オペコード 0010011
  • 即値タイプ: I

addi

  • funct3: 000
  • 命令形式: addi rd, rs1, imm
  • 命令意味: add immediate
  • 疑似コード rd = rs1 + imm ,pc++

即値を負の値に設定することでsubiも兼ねる。

slti

  • funct3: 010
  • 命令形式: slti rd, rs1, imm
  • 命令意味: set less than imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

sltiu

  • funct3: 011
  • 命令形式: sltiu rd, rs1, imm
  • 命令意味: set less than unsigned imm
  • 疑似コード rd = (rs1 < imm) ? 1 : 0 ,pc++

xori

  • funct3: 100
  • 命令形式: xori rd, rs1, imm
  • 命令意味: xor immediate
  • 疑似コード rd = rs1 ^ imm ,pc++

ori

  • funct3: 110
  • 命令形式: ori rd, rs1, imm
  • 命令意味: or immediate
  • 疑似コード rd = rs1 |imm ,pc++

andi

  • funct3: 111
  • 命令形式: andi rd, rs1, imm
  • 命令意味: and immediate
  • 疑似コード rd = rs1 & imm ,pc++

slli

  • funct3: 001
  • 命令形式: slli rd, rs1, imm
  • 命令意味: shift left logical imm
  • 疑似コード rd = rs1 << imm ,pc++
  • 即値タイプ: I(5bit)

srli

  • funct3: 101
  • 命令形式: srli rd, rs1, imm
  • 命令意味: shift rifht logical imm
  • 疑似コード rd = rs1 >> imm ,pc++
  • 即値タイプ: I(5bit)

srai

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: srai rd, rs1, imm
  • 命令意味: shift right arithmetic imm
  • 疑似コード rd = rs1 >>> imm ,pc++
  • 即値タイプ: I(5bit)

OP系命令

  • オペコード 0110011
  • 即値タイプ: R

add

  • funct3: 000
  • 命令形式: add rd, rs1, rs2
  • 命令意味: add
  • 疑似コード rd = rs1 + rs2 ,pc++

sub

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sub rd, rs1, rs2
  • 命令意味: sub
  • 疑似コード rd = rs1 - rs2 ,pc++

sll

  • funct3: 001
  • 命令形式: sll rd, rs1, rs2
  • 命令意味: shift left logical
  • 疑似コード rd = rs1 << rs2 ,pc++

slt

  • funct3: 010
  • 命令形式: slt rd, rs1, rs2
  • 命令意味: set less than
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

sltu

  • funct3: 011
  • 命令形式: sltu rd, rs1, rs2
  • 命令意味: set less than unsigned
  • 疑似コード rd = (rs1 < rs2) ? 1:0 ,pc++

xor

  • funct3: 100
  • 命令形式: xor rd, rs1, rs2
  • 命令意味: xor
  • 疑似コード rd = rs1 ^ rs2 ,pc++

srl

  • funct3: 101
  • 命令形式: srl rd, rs1, rs2
  • 命令意味: shift right logical
  • 疑似コード rd = rs1 >> rs2 ,pc++

sra

  • funct3: 同上
    • funct7: 0100000
  • 命令形式: sra rd, rs1, rs2
  • 命令意味: shift right arithmetic
  • 疑似コード rd = rs1 >>> rs2 ,pc++

or

  • funct3: 110
  • 命令形式: or rd, rs1, rs2
  • 命令意味: or
  • 疑似コード rd = rs1 |rs2 ,pc++

and

  • funct3: 111
  • 命令形式: and rd, rs1, rs2
  • 命令意味: and
  • 疑似コード rd = rs1 & rs2 ,pc++

最後に

今回も適当に書いてしまって反省...
時間ができたらまた詳しく書き直そうかと思います。

*1:Instruction Set Architecture: CPUが扱う命令のニーモニックや、エンコーディング、動作等

*2:min-camlはx86にも対応していますが、x86CISCでありハードウェアが複雑になるので採用する班は少ないようです。中にはx86コンパイラOCamlフルスクラッチしている同期もいますが(笑).

*3:オープンソースが優れているというより、僕がオープンソースが好きなので

*4:x0は0にハードワイヤード

*5:簡単の意味の名言は避けさせてもらいます。

Vivado入門1(CPU実験その1)

この記事は IS17er Advent Calendar 2017 - Adventarの1日目の記事として書かれました。  

CPU実験とは

東大理学部情報科学科では名物講義のひとつにCPU実験というものがあります。

CPU実験の説明は歴代の先輩方が様々説明を書いてくださっているので説明は割愛します。

CPU Experiment
CPU実験2016年度D班コア係 - sueki743's blog
adventar.org

なぜVivado入門なのか

CPU実験ではFPGAを使ってCPUを作ります。

その際に用いるボードが昨年度からXilinx社製のものに更新され 、その統合開発環境であるVivadoに悩まされたので、本記事を書くことにしました。

各種バージョン

  • IDE :Vivado v2016.4(64bit) Ubuntu

  • ボード: Xilinx社製Kintex Ultrascale(KCU105)

 

今回やること

LEDをチカチカさせよう!!

  • Vivadoプロジェクトの作成
  • IPインテグレーター(ブロックデザイン)及びIPコアの使用*1
  • クロックの生成
  • LEDの使用*2
  • 合成(シンセサイズ)、配置配線(インプリメンテーション)、及びビットストリームの生成*3
  • FPGAボードのコンフィギュレーション*4

Vivadoプロジェクトの作成

f:id:progrunner17:20171201223735p:plain

 Vivadoを起動すると以下のようなウィンドウが表示されるので、
まずはプロジェクトを作成します。

f:id:progrunner17:20171201155144p:plain プロジェクト名とプロジェクトの保存場所を指定します.
この時 Create project subdirectoryのチェックを外すと指定したフォルダにVivadoが生成する大量のファイルが広がることになるので、外さないことを推奨します。

f:id:progrunner17:20171201223658p:plain

プロジェクトの種類を指定します。
ここでは基本的なプロジェクトであるRTL Projectを選択してください。 Do not specify sources at this timeのチェックを外すと使用するソースファイルを指定できますが、後から指定できるのでチェックをつけたままにしてください。

f:id:progrunner17:20171201223714p:plain
Boardsタブから使用するFPGAボードを指定します。 *5

これで設定は終わりです。最後に確認ウィンドウが表示されるのでfinishを選択するとプロジェクトが生成されます。

f:id:progrunner17:20171201224058p:plain

プロジェクトができました。

IPインテグレーター(ブロックデザイン)の使用

f:id:progrunner17:20171201224118p:plain
Vivadoには様々な機能がありますが、ウィンドウ左側に表示されているFlow Navigatorに表示されているコマンドを上から下に実行して行くことがプロジェクトの基本的な流れになります。

まずはじめに、Create Block Design*6を実行してください。 f:id:progrunner17:20171201225436p:plain
プロジェクト名は基本的にデフォルトで大丈夫なのですが、今回はtest_block_designと名付けました。

f:id:progrunner17:20171201225700p:plain

こんな感じでブロックデザインが生成されます。

クロックの生成

f:id:progrunner17:20171201225910p:plain
画面中央にあるboard タブからsystem differential clockのいづれかをドラック&ドロップでブロックデザインに追加します。*7

f:id:progrunner17:20171201230117p:plain

これでクロックを生成するIPコア(Clocking Wizard)が生成されました
クロック周波数はデフォルトで100MHzに設定されています。

IPコアの使用

さて、今回の目標であるLチカのためにはクロックの周波数は速すぎて人の目に追えません。
そこでバイナリカウンターを用いていい感じに遅い周波数を取り出したいと思います。
verilog等で直接書いてもいいですが、今回はXilinxが提供しているモジュール(IPコア)を使用したいと思います。

f:id:progrunner17:20171201230321p:plain

IPコアは右クリックかブロックデザインサイドバーから追加できます。 検索ウィンドウにbinaryと入力すればバイナリカウンターが見つかると思うので追加してください。

f:id:progrunner17:20171201231045p:plain

追加できたら上記のようにドラッグしてバイナリカウンターにクロックをつなげてください。

f:id:progrunner17:20171201231310p:plain デフォルトでは0~216-1までカウントできますが、これでもまだ速すぎるのでバイナリカウンターをクリックして設定ウィンドウ開いてIPコアに変更を加えます。

f:id:progrunner17:20171201231544p:plain とりあえず28bit分くらい使えば228/100MHz > 1Hzとなりいい感じでチカチカが見えると思います。

LEDの使用(物理制約及びポートの使用)

それではLEDに繋がるポートを追加します。
ブロックデザイン上で右クリックしてポートの追加を選択してください. f:id:progrunner17:20171201231758p:plain

今回使用するKCU105のボードにはユーザー用に8個のLEDがあるので上記のように設定してください。

f:id:progrunner17:20171201231904p:plain

led用のポートが生成されました。 ただ、このままではバイナリカウンターの出力が28bitに対しledの入力が8bitなのでsliceと呼ばれるIPコアを追加して出力の一部を取り出します。

f:id:progrunner17:20171201232057p:plain

上記のように設定してください。

f:id:progrunner17:20171201232243p:plain

こんな感じに各モジュールを繋いでブロックデザイン上部にあるrun connection automationを実行してブロックデザインでの基本的な作業は終了です 。

ソースファイル(制約)の追加

先ほどブロックデザインに追加したledポートですがこのままでは実際のLEDに紐付けされていないので使用できません。 それを解決するために制約ファイルと呼ばれるファイルを追加してポートを指定します。 Flow Navigator(左サイドバー)からadd sources を実行してください。

f:id:progrunner17:20171201232906p:plain

add or crate constraints をチェックしてください。

f:id:progrunner17:20171201233000p:plain

create fileを選択し制約ファイルを作ります。 ここでの制約ファイル名は特に重要ではないですが今回はled.xdc*8とします。

f:id:progrunner17:20171201233024p:plain

制約ファイルを追加したらウィンドウ中央からled.xdcをダブルクリックしてテキストエディタを開き下記の設定項目を入力してください。*9

set_property PACKAGE_PIN AP8 [get_ports "led[0]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[0]"]
set_property PACKAGE_PIN H23 [get_ports "led[1]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[1]"]
set_property PACKAGE_PIN P20 [get_ports "led[2]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[2]"]
set_property PACKAGE_PIN P21 [get_ports "led[3]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[3]"]
set_property PACKAGE_PIN N22 [get_ports "led[4]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[4]"]
set_property PACKAGE_PIN M22 [get_ports "led[5]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[5]"]
set_property PACKAGE_PIN R23 [get_ports "led[6]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[6]"]
set_property PACKAGE_PIN P23 [get_ports "led[7]"]
set_property IOSTANDARD LVCMOS18 [get_ports "led[7]"]

これで制約ファイルがの設定が終わりました。

HDLwrapperの生成

f:id:progrunner17:20171201233325p:plain

最後に、ウィンドウ中央のSources - Hierarchyタブを開きブロックデザインを右クリックしてcreat HDL wrapper を実行してください。

合成、配置配線及びビットストリームの生成

FPGAに書き込むために合成、配置配線及びビットストリームの生成*10を行います。

f:id:progrunner17:20171201233705p:plain

Flow Navigatorから run synthesizeを実行

f:id:progrunner17:20171201234009p:plain

しばらく待つとこの様なポップアップが現れるのでrun implementation を実行します。

f:id:progrunner17:20171201234409p:plain

generate bitstreamを実行
これでFPGAに書き込むファイルが生成できました。

f:id:progrunner17:20171201234435p:plain

ボードに電源をつなぎ、JTAGポートとPCをマイクロusbケーブルでつなぎFlownavigatorからハードウェアマネージャーを開き auto connectを行うとPCとFPGAが繋がります。 右クリックでprogram deviceを実行するとこんな感じでLEDがチカチカ光り始めるはずです。

f:id:progrunner17:20171201235140g:plain

最後に

もしやる気があれば次はBlock memoryやverilogなどの使い方を書きます。

*1:今回はclock wizard,binary counter, sliceを用いた

*2:ポート及び物理制約の使用

*3:要はFPGAボードへ書き込む準備

*4:実際にFPGAに書き込みを行うこと

*5:おそらく基本設定でボードを指定するとこの選択ができるようになると思うのですが、その設定方法を知らないので割愛します。

*6:ブロクデザインはGUIを使用した設計ツールです

*7:clocking wizardもIPコアの一つなのでadd IPから追加することも可能ですが、この方法で追加することで制約ファイルを自分で書くことなくクロックを簡単に生成することができます。

*8:xdcは制約ファイルの拡張子

*9:制約ファイルの物理制約はボード毎に異なりますKCU105の場合このリンクのPDFの末尾の方に設定例が乗っています。

*10:かなり適当に例えると推論、リンク、コンパイルみたいな感覚です。