Thứ Ba, 30 tháng 4, 2019

[SystemC][High Level Design]Bài 6 - Hướng dẫn mô phỏng model code cơ bản

Bài viết này là một hướng dẫn cơ bản về việc sử dụng SystemC để tạo testbench, chạy mô phỏng và tạo file waveform VCD cho model code trên ví dụ Simple CPU (SCPU) đã trình bày ở các bài viết trước đây.
Trước khi đọc bài này, các bạn cần đọc lại bài 1, bài 2, bài 3, bài 4bài 5 để nắm rõ các nội dung liên quan.
1) Tổng quan
Lưu ý, "model code" và "SystemC code" là hai thuật ngữ được sử dụng qua lại trong bài viết với cùng ý nghĩa là "code được viết bằng ngỗn ngữ SystemC".
Sau khi đã hoàn thiện một thiết kế, bước tiếp theo là thực hiện kiểm tra thiết kế. Với model code cũng không ngoại lệ, việc kiểm tra model code không khác biệt nhiều so với việc kiểm tra RTL code (VHDL hoặc Verilog code). Một môi trường kiểm tra cũng gồm các thành phần cơ bản là:
  • Stimulus: Tác nhân kích thích tạo các giá trị cho đầu vào DUT
  • DUT (Design Under Test) hoặc UUT (Unit Under Test): là thiết kế cần kiểm tra
  • Monitor: Giám sát, kiểm tra đầu ra và các thành phần trong DUT để xác nhận tính đúng đắn của DUT
Hình 1: Cấu trúc cơ bản của một môi trường mô phỏng
Điểm khác biệt duy nhất là chúng ta sẽ viết code C/C++ hoặc SystemC thay vì sử dụng Verilog hoặc System Verilog.
Một phương pháp mô phỏng chung là UVM-SystemC, giúp nâng cao khả năng tái sử dụng (re-use) khi xây dựng các môi trường mô phỏng cũng được Accellera công bố chính thức lần đầu năm 2016 và đang được tiếp thục phát triển. Tháng 11/2018, Accellera đã công bố thư viện UVM-SystemC bản 1.0-beta2. Các bạn có thể tham khảo ở đây https://www.accellera.org/downloads/drafts-review
Trong bài viết này, nhóm tác giả không trình bày về việc xây dựng môi trường UVM-SystemC mà chỉ hướng dẫn cách xây dựng một môi trường test "tự do" theo một DUT cụ thể.
2) Mô tả tại cấu trúc DUT
DUT là thiết kế SCPU gồm 3 khối là FETCH, DECODER và EXECUTE. Mỗi khối là một module được mô tả trong 2 file, một file header (.h) và một file body (.cpp). Các bạn hãy xem lại các bài trước để biết "File header mô tả gì?" và "File body mô tả gì?". Các file code mô tả DUT gồm:
  • scpu_fetch.h - file header của khối FETCH
  • scpu_fetch.cpp - file body của khối FETCH
  • scpu_decoder.h - file header của khối DECODER
  • scpu_decoder.cpp - file body của khối DECODER
  • scpu_execute.h - file header của khối EXECUTE
  • scpu_execute.cpp - file body của khối EXECUTE
  • scpu_define.h - chứa các thông số define chung của DUT. Trong ví dụ này, nó chứa mã lệnh (opcode) và được gọi trong scpu_decoder.h
  • scpu_top.cpp - TOP module của DUT gọi tất cả các sub-module (FETCH, DECODER và EXECUTE) để tạo thành một SCPU hoàn chỉnh

Hình 2: Cấu trúc DUT SCPU
3) Môi trường mô phỏng Model code
3.1) Phân tích cấu trúc môi trường của SPCU
Để xây dựng một môi trường mô phỏng, chúng ta cần xây dựng stimulus, monitor và kết nối chúng với DUT để thực thi quá trình kiểm tra. Hai câu hỏi cần được giải đáp khi xây dựng môi trường mô phỏng:
  • Các đầu vào (input) và điều kiện hoạt động sẽ được cung cấp cho DUT như thế nào?
  • Các đầu ra (output) và trạng thái hoạt động của DUT sẽ được kiểm tra như thế nào?
Đối với thiết kế SCPU, một CPU hoạt động cần các điều kiện sau:
  • Cung cấp xung clock
  • Cung cấp reset ban đầu
  • Cung cấp chương trình cần test và nạp chương trình này trong bộ nhớ chương trình của CPU. Trong SCPU, chương trình cần test sẽ phải nạp trong khối bộ nhớ MEMORY của khối FETCH.
Các thành phần trên sẽ thuộc stimulus của môi trường.
Để kiểm tra SCPU, các thành phần trong SCPU phải được giám sát để đảm bảo kết quả hoạt động của SCPU luôn chính xác trong từng chy kỳ hoạt động của các lệnh, ví dụ như giá trị PC, giá trị các thanh ghi tính toán R0/R1/R2/R3, giá trị các ô nhớ trong bộ nhớ, ... Một môi trường test chuyên nghiệp sẽ tự động kiểm tra các bộ giá trị cần thiết trong suốt quá trình SPCU hoạt động và báo "PASS" hoặc "FAIL" tự động. Tuy nhiên, nhóm tác giả sẽ không xây dựng một môi trường phức tạp như vậy. Trong hướng dẫn cơ bản này, chúng tôi chỉ tạo file waveform để kiểm tra các tín hiệu cần thiết. Như vậy, monitor của môi trường này là một file VCD + trình xem waveform, nghe có vẻ "chuối" nhưng các bạn hãy bắt đầu với những điều cơ bản như vậy trước khi tiếp tục nâng cấp thêm.
Hình 3: Cấu trúc môi trường mô phỏng SCPU
Môi trường mô phỏng ho SCPU sẽ gồm các thành phần:
  • Testbench: là một thành phần gồm một file header (scpu_testbench.h) và một file body (scpu_testbench.cpp) chứa các phần sau đây:
    • scpu_top_inst: tạo một instance của DUT cần kiểm tra
    • RESET_GEN: là một method lái tín hiệu reset rst_n
    • scpu_load_mem.h: là file chương trình sẽ nạp cho bộ nhớ MEMORY trong khối FETCH của DUT. Đây là một file độc lập được gọi (include) trong Constructor của scpu_testbench.h. Tất nhiên, bạn có thể viết nội dung file này trực tiếp trong scpu_testbench.h mà không cần tạp file riêng nhưng tùy vào nội dung cần kiểm tra, chương trình nạp cho SCPU có thể thay đổi làm cho nội dung phần này thay đổi. Vì vậy, việc tạo một file riêng giúp bạn chỉ cần chỉnh sửa file này, hoặc bạn có thể viết một script tạo ra code của file này.
      • Trên thực tế, chương trình của CPU là một file binary được nạp đến bộ nhớ chương trình thông qua một giao thức quy định trước, ví dụ như JTAG, UART, ... và bộ nhớ chương trình là loại bộ nhớ không bay hơi như Flash, ROM, ... Trong SCPU, bộ nhớ chương trình là một mô hình bộ nhớ được tạo bằng SystemC và việc "nạp" thực chất là gán giá trị mong muốn vào từng ô nhớ của bộ nhớ này.
    • clk: là chân xung clock sẽ được cấp từ file TOP của môi trường.
  • File TOP của môi trường: là file sẽ gọi và tạo tất cả các thành phần cần thiết cho việc chạy môi phỏng sử dụng class sc_main.
    • scpu_testbench_inst: tạo instance testbench
    • CLOCK_GEN: thành phần tạo lái xung clock clk bằng cách dùng sc_clock.
    • scpu_trace.h: chứa code tạo file VCD và các thành phần cần quan sát waveform. File này được gọi (include) trong test_top.cpp. Tương tự file scpu_load_mem.h, nội dung file này dễ bị thay đổi tùy vào mục tiêu debug của người kiểm tra nên nó được viết trong một file riêng để có thể dễ dàng chỉnh sửa mà không làm thay đổi nội dung test_top.cpp.
    • sc_start: một function của SystemC được dùng để khởi động và điều khiển thời gian chạy mô phỏng.
3.2) Quy trình thực thi mô phỏng model code
Sau khi phân tích rõ cấu trúc môi trường mô phỏng, chúng ta cần thực hiện các bước (thao tác) của một quá trình mô phỏng model code như sau:
  1. Viết code cho timulus và monitor - dùng bất kỳ trình editor nào (Notepad++, EmEditor, ...)
    • Kết quả: tạo ra các file code C/C++ (.cpp)
  2. Tổng hợp môi trường - sử dụng compiler g++
    • Kết quả: tạo ra file thực thi. Trên hệ điều hành Windows, file này là file (.exe)
  3. Thực thi mô phỏng - chạy file *.exe sau khi tổng hợp thành công
    • Kết quả:
      • In ra kết quả "PASS/FAIL" và các thông tin kiểm tra trên terminal, ví dụ như Cygwin terminal trong bài viết này, hoặc trong một file text gọi là file log nếu có.
      • Tạo file waveform VCD nếu có. 
  4. Debug
    • Kết quả: Khi nhận lại các hiện tượng và lý do lỗi bằng cách phân tích waveform hoặc file log
  5. Điều chỉnh lại DUT hoặc các thành phần môi trường và lặp lại từ bước 1 hoặc bước 2
    • Kết quả: các file code mới đã được điều chỉnh là sửa các lỗi phát sinh khi mô phỏng
Hình 4: Quá trình thực hiện mô phỏng model code
4) Xây dựng môi trường
4.1) scpu_testbench
scpu_testbench là thành phần sẽ gọi TOP module của DUT và tạo các thành phần lái ngõ vào của DUT, ngoại trừ clock.
Hình 5: File scpu_testbench.h
Hình 6: File scpu_testbench.cpp
Cũng theo nguyên tắc tạo một module thông thường:
  • file header (.h) chứa những khai báo
  • file body (.cpp) chứa code của các process
Tín hiệu reset được tạo ra trong ví dụ này sẽ giữ mức "0" trong 8ns tính từ khi "xuất hiện cạnh lên đầu tiên của xung clock clk" vì process RESET_GEN lái rst_n theo cạnh lên clk quy định bởi dòng code "sensitive << clk.pos()". Trong ví dụ này, nhóm tác giả sẽ tạo xugn clock có chu kỳ 2ns với duty cycle là 50% để mô phỏng. Như vậy, tín hiệu rst_n sẽ giữ mức "0" trong 4 chu kỳ clock.
Hình 7: Giản đồ định thời tín hiệu reset rst_n
Một lưu ý khác, Destructor được sử dụng để giải phóng bộ nhớ của đối tượng đã cấp phát bởi new trong constructor, ví dụ như đối tượng scpu_top_inst trong trường hợp này. SystemC không tự động xóa đối tượng đã cấp phát, việc destructor là cần thiết để giải phóng bộ nhớ sau khi đối tượng đã được sử dụng xong. Tuy việc kết thúc mô phỏng bằng "return 0" trong sc_main sẽ làm hệ điều hành giải phóng bộ nhớ nhưng sử dụng destructor vẫn là một cách viết code tốt để chủ động kiểm soát việc tạo/xóa các đối tượng.
4.2) scpu_load_mem.h
Trong scpu_testbench.h, file này được gọi để gán giá trị cho bộ nhớ chương trình MEMORY trong khối FETCH. Cú pháp chung của file này là:
<vị trí ô nhớ trong memory> = <binary code>
Ví dụ:
scpu_top_inst->scpu_fetch_inst.mem_array[0] = 0b11100000;
scpu_top_inst->scpu_fetch_inst.mem_array[1] = 0b10101010;
Trong đó:
  • scpu_top_inst là một instance. Đây con trỏ của đối tượng được tạo ra trong scpu_testbench.h
  • scpu_fetch_inst là một instance được tạo ra trong scpu_top.cpp
  • mem_array[*] là một ô nhớ của biến "sc_uint<8> mem_array [256]"
  • Trong ví dụ này, hai ô nhớ lưu mã lệnh của "LI R0, haa"
    • 0b11100000: Từ bit 7 đến bit 4 là 1110, mã binary của lệnh LI. Từ bit 3 đến bit 2 là 00, mã thanh ghi R0. Bit 1 và bit 0 không sử dụng trong lệnh này.
    • 0b10101010: là giá trị IMM haa sẽ nạp vào R0. Chú ý, lệnh LI là lệnh có độ dài 16 bit và lưu trong 2 ô nhớ của memory
Các bạn có thể thay thế các mã lệnh mong muốn trong file này để chạy test SCPU. Như đã trình bày trước đó, việc tạo một file riêng giúp bạn dễ dàng chỉnh sửa và có thể viết script để tạo ra file này theo kịch bản test mong muốn.
4.3) test_top
Đây là file chứa chương trình chính điều khiển quá trình mô phỏng. Phần chính của file này là một khai báo sc_main bắt buộc. Một thành phần các bạn cần chú ý trong file này là:
  • sc_clock: tạo clock với chu kỳ 2ns và duty cycle 50%
  • sc_start: chạy mô phỏng với thời gian mong muốn. Trong ví dụ này, nhóm tác giả chạy liên tiếp 100 lần mỗi lần chạy 2ns (một chu kỳ xung clock). Các bạn có thể khai báo tương đương bằng 1 dòng lệnh "sc_start(200, SC_NS)" thay cho vòng lặp for.
  • sc_close_vcd_trace_file: đóng file VCD đã tạo trong scpu_trace.h
  • return 0: thoát khỏi quá trình mô phỏng, hệ điều hành giải phóng bộ nhớ cho các đối tượng đã tạo khi chạy mô phỏng.
Hình 8: Cấu trúc file test_top
Chú ý, như đã trình bày, nhóm tác giả không tạo ra các thành phần tự kiểm tra kết quả PASS/FAIL mà chỉ tạo waveform bằng cách gọi file scpu_trace.h trong trường hợp này.
4.4) Tạo file waveform VCD (scpu_trace.h)
File này chứa một số từ khóa quan trọng sau:
  • sc_trace_file: tạo con trỏ cho đối tượng file VCD
  • sc_create_vcd_trace_file: tạo file VCD với tên file mong muốn
  • Đoạn code kiểm tra lại việc tạo file VCD
  • sc_trace: Khai báo các tín hiệu và biến cần xem waveform
    • scpu_waveform là tên con trỏ khai báo bởi sc_trace_file
    • Vị trí chính xác của tín hiệu/biến trong cấu trúc môi trường, ví dụ "scpu_testbench_inst.scpu_top_inst->scpu_fetch_inst.clk" là chân clock clk của khối FETCH
    • Khai báo tên hiển thị. Chú ý, việc hiển thị có thể được thể hiện theo cấu trúc mong muốn bằng cách sử dụng dấu "." để phân định. Ví dụ, "fetch.clk" sẽ hiển thị một tín hiệu clk trong nhóm tên fetch.
Hình 9: Nội dung file scpu_trace.h dùng để tạo file VCD khi chạy mô phỏng
5) Thực thi mô phỏng và kết quả
Chúng ta sẽ thực thi mô phỏng bằng cách thực hiện theo thứ tự các lệnh sau trên Cygwin terminal. Những cài đặt cần thiết để thực hiện công đoạn này đã được nhóm tác giả trình bày ở bài 5.
  • Khởi động Cygwin và cd đến thư mục chứa code môi trường
  • Compile môi trường với g++
g++ -I. -I/cygdrive/d/20.Project/1.ModelBaseDesign/SystemC_lib32_233/include -L. -L/cygdrive/d/20.Project/1.ModelBaseDesign/SystemC_lib32_233/lib-cygwin -o test_top *.cpp -lsystemc -lm
  • Chạy mô phỏng bằng file "test_top.exe"
./test_top.exe
Hình 10: Kết quả hiển thị việc chạy simulation thành công và file scpu_waveform.vcd được tạo
  • Mở phần mềm gtkwave để kiểm tra waveform các tín hiệu mong muốn bằng cách mở file "scpu_waveform.vcd" được tạo khi chạy "test_top.exe"
Hình 11: Debug trên waveform với gtkwave
Source code liên quan:
1. SCPU DUT và environment (pass: vlsi_technology)

Lịch sử cập nhất:
1. 2019.May.01 - Tạo lần đầu

1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

1 bình luận: