ChiselはScalaをベースにしたハードウェア記述言語で、より高位な記法を使ってハードウェアを設計しようという考え方を持っている。
しかし、どうしてもVerilogを使いたい場合、あるいはある部品は既存のVerilogで記述されたものを使いたいと思うときがある。 例えば、Chiselを使えばメモリも記述することができるが、実際にはSRAMに置き換えたい場合であったり、FPGAで推論しやすくするためにVerilogで書いたモジュールに置き換えたい場合がある。このような場合に、どのようにChiselを記述するのかについて調査した。
ChiselにVerilogを埋め込むためのBlackBoxモジュール
ChiselにVerilogを埋め込むための方法は2つ存在する。
どちらの方法を使うかは、その状況下で使い分ければいいと思う。 例えば、今回は以下のようなメモリモジュールをVerilogで置き換えるためにはどのようにしたらよいのか考えた。
class Memory [Conf <: RVConfig](conf: Conf) extends Module { val io = new MemoryIo(conf) ...
インラインでVerilogを記述する方法
Memoryモジュールを置き換えるために、内部にインラインでVerilogを記述するためにはBlackBox
モジュールを継承し、HasBlackBoxInline
トレイトを追加する。
class MemoryBlackBox [Conf <: RVConfig](conf: Conf) extends BlackBox with HasBlackBoxInline { val io = IO(new Bundle { val clock = Input(Clock()) val mem = new MemoryIo(conf) })
ここで、I/Oにクロックを追加している。理由は、このブラックボックスのモジュールは明示的にクロックとリセットを追加しないと自動的に挿入されないためだ。 つまり、明示的にポートを追加しないと内部でクロックを使用できない。
BlackBoxes · freechipsproject/chisel3 Wiki · GitHub
Unlike Module, BlackBox has no implicit clock and reset. Ports declared in the IO Bundle will be generated with the requested name (ie. no preceding io_).
そして、内部にVerilogコードをそのまま埋め込んでいく。これにはsetInline
を使用する。
setInline("MemoryBlackBox.v", s""" |module MemoryBlackBox | ( | input logic clock, | input logic mem_inst_bus_req, ... | |endmodule // MemoryBlackBox """.stripMargin) }
これで完了だ。クロックポートを追加したので、メモリのインタフェースはmem
という変数でラップされてしまった。オリジナル(Chiselで記述した)Memoryモジュールとのポート互換性を合わせたければ、Memoryモジュール側も同じようにI/Oポートをラップさせるのが良いと思う。
class Memory [Conf <: RVConfig](conf: Conf) extends Module { val io = IO(new Bundle { val mem = new MemoryIo(conf) })
これでVerilogを生成すると、CpuTop.v
というCPU本体のモジュールと、MemoryBlackBox.v
という別にファイルが生成される。
さらに、ブラックボックスのファイル一覧を示すblack_box_verilog_files.f
というファイルも生成される。
ls -lt MemoryBlackBox.v black_box_verilog_files.f CpuTop.v
- MemoryBlackBox.v
module MemoryBlackBox ( input logic clock, input logic mem_inst_bus_req, input logic [15:0] mem_inst_bus_addr, output logic mem_inst_bus_ack, output logic [31:0] mem_inst_bus_rddata, input logic mem_data_bus_req, input logic [1:0] mem_data_bus_cmd, input logic [15:0] mem_data_bus_addr, ...
外部のVerilogファイルを指定する方法
こちらの方がリソースを再利用する方法としては優れていると思う。
インラインする方法とは異なるトレイト(HasBlackBoxResource
)を使用する。Verilogファイルは、setResource
で場所を指定する。
こちらもクロックは明示的に追加しないとポートとして現れないので、IOバンドルとしてラップする。
このsetResource
で指定されるVerilogファイルは、プロジェクトのルートディレクトリからsrc/{main,test}/resources/
に配置されている必要がある。
今回は、src/main/resources/memory_real.v
にVerilogファイルを配置した。
class MemoryResourceBox [Conf <: RVConfig](conf: Conf) extends BlackBox with HasBlackBoxResource { val io = IO(new Bundle { val clock = Input(Clock()) val mem = new MemoryIo(conf) }) setResource ("/memory_real.v") }
- `src/main/resources/memory_real.v``
module MemoryResourceBox ( input logic clock, input logic mem_inst_bus_req, input logic [15:0] mem_inst_bus_addr, ...
同様にVerilogファイルを生成すると、CpuTop.v
というCPU本体のモジュールと一緒に、プロジェクトのルートディレクトリにもmemory_real.v
が生成され、さらにブラックボックスのファイルリストを含んでいるblack_box_verilog_files.f
も生成された。