Hello Verilator—高品質&開源的 SystemVerilog(Verilog) 模擬器介紹&教學(三)

Share on:

本文內容採用創用 CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款授權.

在前面一篇文章中分享了作者如何使用 verilator 以及 C++ 來模擬一個用 SystemVerilog 模組,在這篇文章中,將會分享另外一種方法,也就是用 verilator 以及 SystemC 來達成同樣的事情。

SystemC

SystemC 說到底還是 C++ 的 library,那麼額外引入 C++ 這個額外的 library 能得到什麼好處呢?作者認為有以下兩個好處:

  • 現今 SystemC 很常拿來作硬體的 approximate timing model 或是 cycle accurate model,有可能可以直接利用這些 model 的程式碼來作驗證。
  • SystemC 內建了模擬平行化的功能如 SC_THREAD,這在硬體測試中對應到 Verilog 如 initial 的功能,這點讓 SystemC 跟純 C++ 相比,更適合拿來驗證 Verilog。

Hello Verilator (SystemC version)

上一篇中,用 verilator 以及 C++ 驗證的步驟如下:

  1. 撰寫 testbench testbench.cpp
  2. 準備可合成之待測模組 design_under_test.v
  3. 把待測試模組轉換成 C++(或是 SystemC),並指名要使用的 testbench verilator design_under_test.v --exe testbench.cpp --cc
  4. Verilator 產生的檔案都在 obj_dir 資料夾(這個是預設名稱)。
  5. 在該資料夾下面編譯產生 binary make -C obj_dir -f Vdesign_under_test.mk
  6. 執行模擬 ./obj_dir/Vdesign_under_test

用 SystemC 的話,步驟其實是一樣的,除了第三個步驟會的指令有一點不同:

verilator design_under_test.v --exe testbench.cpp --sc

唯一的不同就只是把 --cc 替換成 --sc。另外,有些系統安裝的 verilator 版本沒設定正確,例如在 ArchLinux 下,可能需要另外透過環境變數指定 SystemC 的安裝位置:

SYSTEMC=/usr verilator design_under_test.v --exe testbench.cpp --sc

SystemC testbench

實做 sc_main

我們暫時先拿一個空的 testbench.cpp 來跑整個流程。

 1#include "Vdesign_under_test.h"
 2#include <memory>
 3#include <iostream>
 4#include <systemc>
 5int sc_main(int, char**)
 6{
 7    std::unique_ptr<Vdesign_under_test> dut(new Vdesign_under_test("dut"));
 8    sc_start(100.0, SC_NS);
 9    return 0;
10}
把上面的步驟 3-6 跑一次就好了,執行完畢之後會發現出現了 port not bound 的錯誤。這是正常的現象,因為 SystemC 要求 module 的所有 port 都有接上東西,但是這個 sc_main 什麼都沒有接。

為此,我們創造 Testbench 這個 class 來幫 design_under_test 接上必要的 sc_signal

 1using namespace sc_core;
 2SC_MODULE(Testbench) {
 3    sc_clock clk;
 4    sc_signal<bool> rst;
 5    sc_signal<bool> valid;
 6    sc_signal<unsigned> data;
 7public:
 8    SC_HAS_PROCESS(Testbench);
 9    Testbench(
10        const sc_module_name &name,
11        Vdesign_under_test *dut
12    ): sc_module(name) {
13        dut->clk(clk);
14        dut->rst(rst);
15        dut->valid(valid);
16        dut->data(data);
17    }
18};
然後在 sc_main 裡面宣告 Testbench
1int sc_main(int, char**)
2{
3    std::unique_ptr<Vdesign_under_test> dut(new Vdesign_under_test("dut"));
4    std::unique_ptr<Testbench> testbench(new Testbench("testbench", dut.get()));
5    sc_start(100.0, SC_NS);
6    return 0;
7}
再執行步驟 5-6,這次沒有錯誤了,只剩下最後一個步驟讓信號動起來。

實做 SC_THREAD

最後一個步驟中,我們加入了兩個 SC_THREAD,一個是負責驅動 rstReset(),另外一個是負責監視輸出資料的 Monitor()

 1Testbench(
 2    const sc_module_name &name,
 3    Vdesign_under_test *dut
 4): sc_module(name), clk("clk", 1.0, SC_NS) {
 5    dut->clk(clk);
 6    dut->rst(rst);
 7    dut->valid(valid);
 8    dut->data(data);
 9    SC_THREAD(Reset);
10    SC_THREAD(Monitor);
11}
負責驅動 rstReset()如下:
1void Reset() {
2    rst.write(true);  // initial begin rst = 1;
3    wait(5.0, SC_NS); // #5
4    rst.write(false); // rst = 1
5    wait(5.0, SC_NS); // #5
6    rst.write(true);  // rst = 0 end
7}
監視輸出資料、檢查每個 cycle 如果 valid 是 1 則把 data 印出來,其函數實做如下:
1void Monitor() {
2    wait(rst.negedge_event());     // @(negedge rst)
3    wait(rst.posedge_event());     // @(posedge rst)
4    while (true) {                 // forever begin
5        wait(clk.posedge_event()); // @(posedge clk)
6        if (valid.read())
7            std::cout << char(data.read()) << std::endl;
8    }                              // end
9}
可以看到,這兩個函數幾乎有著對應的 Verilog 程式碼,也幾乎等價於兩個 initial block。

重新跑一次步驟 5-6 之輸出如下,也就是我們預期的結果:

1        SystemC 2.3.3-Accellera --- Jan 16 2020 17:56:30
2        Copyright (c) 1996-2018 by all Contributors,
3        ALL RIGHTS RESERVED
4H
5e
6l
7l
8o

可以看到在 SystemC 的版本中,「負責驅動 rst」跟「監視輸出資料」兩個獨立功能是放在兩個獨立函數內。因此相比純 C++ 的版本,SystemC 的版本有更好的擴展性。