• Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

Hiển thị các bài đăng có nhãn Project2. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Project2. Hiển thị tất cả bài đăng

Thứ Ba, 26 tháng 11, 2019

[UVM] Bài 8 - Tạo báo cáo coverage và phân tích kết quả Coverage

Bài viết này trình bày về cách tạo báo cáo coverage (coverage report) và cách phân tích kết quả coverage trên một môi trường mô phỏng với phần mềm QuestaSim.


1) Coverage là gì?
Coverage, tạm dịch là mức độ bao phủ, là tỷ lệ phần trăm các mục tiêu kiểm tra đã được đáp ứng.
Ví dụ, mục tiêu kiểm tra của một thiết kế được xác định là 5 điểm. Mỗi mục tiêu kiểm tra sẽ ứng với một hoặc nhiều hoạt động, gọi là các chức năng (function), được thực hiện bởi một hoặc nhiều đoạn code. 5 mục tiêu được kỳ vọng sẽ giúp kiểm tra hết các chức năng và các đoạn code của thiết kế. Bạn sẽ cần tạo các testcase để kiểm tra 5 mục tiêu đã đặt ra. Để ước lượng kết quả kiểm tra, một số vấn đề sau cần được xác minh:
  • Làm thế nào để biết chắc rằng các chức năng và các đoạn code của thiết kế đã được kiểm tra toàn bộ hay chưa?
  • Làm thế nào để khẳng định số lượng testcase được tạo ra đã đáp ứng được 5 mục tiêu kiểm tra? Số lượng testcase đã đầy đủ hay chưa?
  • Làm thế nào để đánh giá tiến độ kiểm tra (verification progress) hiện tại?
*testcase là code dùng để thực thi kiểm tra thiết kế trên một môi trường mô phỏng. Mỗi testcase thường thực thi một trường hợp hoặc mục đích kiểm tra cụ thể.
Hình 1: Một thiết kế cần được kiểm tra (DUT) với 5 mục tiêu kiểm tra được xác định, mỗi mục tiêu sẽ kiểm tra một phần chức năng và code
Coverage là một thước đo hỗ trợ đánh giá các vấn đề trên. Ví dụ, coverage là 13/15, ứng với 86,667%, thể hiện:
  • Các chức năng và các đoạn code có thể chưa được kiểm tra toàn bộ. Các phần chưa được bao phủ cần được xem xét và đánh giá để xác định là cần được kiểm tra hay không.
  • Nếu các phần chưa được bao phủ cần phải kiểm tra, nghĩa là số lượng testcase đang bị thiếu và cần tạo thêm các testcase mới.
  • Việc lấy kết quả coverage theo từng giai đoạn có thể dùng làm thông số để ước lượng tiến độ kiểm tra.
Hình 2: Minh họa một kết quả coverage 13/15 = 86,667%
Việc dùng kết quả coverage để đánh giá tiến độ của quá trình kiểm tra thiết kế được thực hiện như sau:
  1. Xác định thời gian thực hiện kiểm tra, thời điểm bắt đầu và thời điểm kết thúc
  2. Xác định các mốc thời gian lấy kết quả coverage
  3. Thực thi các testcase trong từng giai đoạn và lấy kết quả coverage khi đến mốc thời gian đã xác định ở bước 2
Mốc thời gian
Tuần 1
Tuần 2
Tuần 3
Tuần 4
Tuần 5
Kết quả coverage
30%
40%
70%
95%
100%

Trong ví dụ trên, quá trình kiểm tra một thiết kế được xác định là 5 tuần, kết quả coverage sẽ được lấy vào cuối mỗi tuần. Thông qua kết quả coverage, tiến độ công việc kiểm tra thiết kế được thể hiện.
2) Phân loại coverage?
Covearge được chia làm hai nhóm chính là:
  • Code coverage là các thông tin coverage có thể được trích xuất tự động từ RTL code hoặc gate netlist, gọi chung là code, của thiết kế.
  • Function coverage là loại coverage được định nghĩa bởi người kiểm tra (user-defined) gắn liền với mục đích và chức năng thiết kế.
3) Code coverage
Code coverage là chức năng được hỗ trợ bởi hầu hết các phần mềm các phần mềm mô phỏng. Các phần mềm sẽ giúp trích xuất kết quả coverage từ RTL code hoặc gate netlist thông qua việc thực thi các testcase. Nguyên tắc cơ bản của code coverage là thống kê lại giá trị của các thành phần được mô tả trong code khi các testcase được thực thi.
Code coverage không được sử dụng để đánh giá:
  • Tính đầy đủ trong việc kiểm tra thiết kế. Một thiết kế có code coverage là 100% không có nghĩa là thiết kế này đã được kiểm tra toàn bộ các chức năng.
  • Tính đúng đắn của một thiết kế. Một thiết kế có code coverage 100% không có nghĩa là thiết kế này hoạt động chính xác 100%.
Code coverage không thể dùng để đánh giá 2 điểm trên vì việc thống kê được thực hiện độc lập trên các biến và các tín hiệu mà không xem xét đến chức năng và hoạt động của thiết kế. Nghĩa là không xem xét đến mối liên hệ của tất cả các thành phần trong thiết kế. Ví dụ, trong toggle coverage, ngõ vào của một khối trong thiết kế được báo là đạt 100%, điều này chỉ thể hiện là có sự chuyển trạng thái "từ 0 thành 1" và "từ 1 thành 0" trên tín hiệu, chứ không giúp khẳng định sự chuyển trạng thái này xảy ra đúng như chức năng mong muốn. 
Code coverage chỉ được dùng để đánh giá các điểm sau:
  • Hỗ trợ xác định các trường hợp test (testcase) bị thiếu. Khi coverage không đạt 100% thì phần code chưa được bao phủ có thể là phần code không được sử dụng (unused) hoặc phần code chưa được kiểm tra. Nếu là phần code chưa được kiểm tra, nghĩa là trường hợp test đang thiếu và cần tạo thêm testcase để kiểm tra phần code này.
  • Thống kê tác động của một testcase hoặc một nhóm testcase lên code cần kiểm tra. Ví dụ, sau khi chạy một testcase, thông qua báo cáo coverage, bạn có thể biết testcase này tác động lên những phần code nào của thiết kế.
Code coverage bao gồm các loại sau:
  • Statement coverage - mức độ bao phủ của các phát biểu, xác định xem một phát biểu (statement) trong code đã được thực thi hay chưa. Ví dụ về một phát biểu:
assign y = sel? a: b;
  • Branch coverage - mức độ bao phủ của các nhánh, dùng để đánh giá sự thực thi của cấu trúc rẽ nhánh if/else và case. Một cấu trúc rẽ nhánh chỉ được bao phủ 100% khi tất cả các nhánh đều đã được thực thi. Ví dụ, cấu trúc if/else sau đây được bao phủ 100% nếu cả 3 nhánh if, else if và else đều đã được thực thi.
if (a == 1)
  y = 0;
else if (b = 1)
  y = 2;
else
  y = 1;
  • Condition coverage - mức độ bao phủ của các điều kiện, dùng để đánh giá và phân tích các điều kiện mô tả trong các nhánh của phát biểu if và toán tử điều kiện ?. Tùy và phần mềm, nó có thể là một phần của branch coverage hoặc là một báo cáo riêng. Một điều kiện chỉ được bao phủ 100% khi tất cả các giá trị làm cho điều kiện đó đúng (TRUE) và sai (FALSE) đều xuất hiện. Ví dụ, điều kiện state == IDLE sau đây chỉ được bao phủ 100% nếu state bằng IDLE và state khác IDLE đã xảy ra.
if (state == IDLE)
  • Expression coverage - mức độ bao phủ của các biểu thức, dùng để đánh giá biểu thức bên vế phải của một phép gán. Một biểu thức được bao phủ 100% khi tất cả tổ hợp giá trị của các biến trong biểu thức đã xuất hiện. Ví dụ, biểu thức sau chỉ được xem là bao phủ 100% nếu tất cả các tổ hợp giá trị của {a, b} là 00, 01, 10 và 11 đều đã xuất hiện.
assign y = a & b;
  • Toggle coverage - mức độ bao phủ của sự thay đổi trạng thái, dùng để đánh giá sự thay đổi trạng thái trên các tín hiệu. Một tín hiệu chỉ được bao phủ 100% khi nó đã xuất hiện sự chuyển mức logic từ 0 thành 1 và ngược lại. Đối với tín hiệu 3 trạng thái (tri-state) thì có thể cần phải thêm sự chuyển đổi từ 0 và 1 thành Z và từ Z thành 0 và 1.
Ngoài ra một số phần mềm còn hỗ trợ các loại coverage khác như:
  • FSM coverage - mức độ bao phủ của FSM, dùng để đánh giá riêng máy trạng thái
  • Class Coverage -  mức độ bao phủ của class, dùng để đánh giá riêng các class của System Verilog
Hình 3: Phân loại coverage
4) Function coverage
Fucntion coverage thuộc về người dùng định nghĩa chứ không thể được trích xuất tự động bằng phần mềm. Người kiểm tra sẽ phải mô tả cụ thể điểm cần được bao phủ bằng System Verilog. Dựa trên mô tả này, phần mềm sẽ giám sát và báo cáo kết quả coverage.  Các điểm quan trọng của function coverage là:
  • Người kiểm tra phải định nghĩa điểm cần bao phủ
  • Điểm cần bao phủ được định nghĩa dựa trên tài liệu mô tả kỹ thuật (specification) và đặc điểm mong muốn của thiết kế chứ không phụ thuộc vào cấu trúc hoặc code của thiết kế
  • Function coverage có thể hỗ trợ đánh giá tính đầy đủ và đúng đúng đắn của thiết kế. Điều mà code coverage không hỗ trợ được.
Function coverage có thể được dùng để giám sát và xác nhận các điểm sau:
  • Một đặc điểm hoặc chức năng nào đó của thiết kế hoạt động đúng. Ví dụ, một biến trong thiết kế phải thay đổi giá trị tuần tự 1 -> 3 -> 5 -> 8. Code coverage chỉ cho biết biến này đã xuất hiện những giá trị nào chứ không cho biết các giá trị đã xuất hiện có theo đúng thứ tự như yêu cầu thiết kế hay không. Function coverage sẽ giúp giám sát điều này.
  • Một điều kiện hoặc trường hợp kiểm tra đã xảy ra hay chưa. Trong một số trường hợp, điều kiện kiểm tra tạo ra từ testcase cần được xác nhận để đảm bảo rằng testcase thực thi đúng trường hợp như mong muốn.
SV có hỗ trợ các thành phần như covergroup, coverpoint, cross, các tùy chọn, task và function giúp người kiểm tra có thể mô tả function coverage.
5) Mô tả fucntion coverage với SV
Phần  này không nhằm mục đích trình bày chi tiết cách thức mô tả function coverage với SV mà chỉ thực hiện một ví dụ nhỏ về function coverage giúp bạn đọc hiểu rõ hơn function coverage là gì?
Trong môi trường mô phỏng đã xây dựng, một ví dụ về function coverage được thêm vào file ./checker/uart_protocol_checker.sv để kiểm tra trường hợp truyền back-to-back của giao thức UART. Back-to-back là truyền liên tiếp các gói dữ liệu UART mà không có khoảng thời gian rảnh giữa các gói. Nghĩa là bit START của gói sau nối tiếp ngay sau bit STOP của gói trước.
Hình 4: Truyền back-to-back của giao thức UART
Để đảm bảo chức năng này xảy ra, một điểm bao phủ sẽ được mô tả để kiểm tra frame_start và frame_end cùng tích cùng mức 1 trong khhi checker đang ở trạng thái CHK_UART_CHECK. Ở đây,
  • frame_end báo kết thúc một khung UART, ngay sau bit STOP
  • frame_start phát hiện một cạnh xuống của bit START
  • checker đang ở trạng thái CHK_UART_CHECK là để đảm bảo nó đang trong một trạng thái kiểm tra khung UART
Hình 5: Vị trí đặt điểm bao phủ (coverpoint) để đảm bảo có sự truyền nhận back-to-back trên giao thức UART
Ví dụ 1 - SV code mô tả một function coverage
 covergroup back_2_back @ (posedge pclk);
    coverpoint chk_uart_state {
      bins pass[] = {1} iff (frame_start && frame_end) ;
    }
    option.per_instance = 1;
  endgroup
  back_2_back b2b = new();
  initial begin
    b2b.sample();
  end
Trong đoạn code trên:
  • Tên covergroup là back_2_back, việc lấy mẫu kiểm tra giá trị cần bao phủ được thực hiện tại cạnh lên pclk
  • Tín hiệu cần kiểm tra là chk_uart_state phải bằng 1, pass[] = {1}, trong khi frame_start=1 và frame_end=1, iff (frame_start && frame_end)
  • option.per_instance = 1 để cho phép thông tin của một instance tạo từ covergroup này được thống kê trong dữ liệu coverage và báo cáo coverage.
  • Tạo một instance covergroup bằng new()
  • sample() là một hàm hỗ trợ bở SV dùng để kích hoạt việc lấy mẫu covergae
6) Cách tạo báo cáo coverage với QuestaSim
Phần mềm được sử dụng là QuestaSim 10.2c. Việc tạo một báo cáo coverage trên phần mềm này được thực hiện như sau:
  • Lệnh vlog: Tổng hợp source code với tùy chọn "+cover". Tùy chọn này cho phép thu thập các kết quả coverage khác nhau trên tất cả các thành phần trong thiết kế. Tùy chọn này đi kèm với các giá trị sau:
    • b - cho phép branch coverage
    • c - cho phép condition coverage, chỉ thu thập loại ocverage FEC (Focused Expression Coverage)
    • e - cho phép expression coverage, chỉ thu thập loại ocverage FEC
    • s - cho phép statement coverage
    • t - cho phép toggle coverage, tùy chọn này có thể bị ghi đè bởi tùy chọn "x"
    • x - cho phép toggle coverage mở rộng, thêm việc giám sát chuyển trạng thái từ 0,1 thành Z và ngược lại.
    • f - cho phép FSM coverage
Ví dụ 2 - Lệnh tổng hợp source code cho phép coverage (chú ý dòng bôi đỏ, đây là đường dẫn thư viện UVM)
my $vlog = "$VLog -work work \\
+define+UVM_CMDLINE_NO_DPI \\
+define+UVM_REGEX_NO_DPI \\
+define+UVM_NO_DPI \\
+define+INTERRUPT_COM \\
+incdir+C:/questasim64_10.2c/uvm-1.2/src \\
-sv \\
../dut/uart_apb_if.v \\
../dut/uart_receiver.v \\
../dut/uart_transmitter.v \\
../dut/uart_top.v \\
../dut/dut_top.v \\
../checker/apb_protocol_checker.sv \\
../checker/apb_protocol_checker_top.sv \\
../checker/uart_protocol_checker.sv \\
../checker/uart_protocol_checker_top.sv \\
../uvm_comp/ifDut.sv \\
testTop.sv \\
-timescale 1ns/1ns \\
-l vlog.log \\
+cover=bcestf \\
    -coveropt 1
";
  • Lệnh vsim - Chạy mô phỏng và tạo cở sở dữ liệu coverage (coverage database). Cơ sở dữ liệu coverage được lưu trong file .ucdb. UCDB là viết tắt của Unified Coverage Database. với các tùy chọn
    • -coverage : cho phép thu thập các thống kê của code coverage trong quá trình chạy mô phỏng
    • -coveranalysis : lưu các điểm toggle vào file UCDB
    • -cvgperinstance :  thiết lập giá trị của  option.per_instance của tất cả các covergroup bằng 1.
    • -do : dùng tùy chọn này để thực thi một lệnh lưu cơ sở dữ liệu coverage.
  • Lệnh lưu cơ sở dữ liệu trong tùy chọn "-do" của vsim "coverage save -codeAll -cvg -onexit $ARGV[0].ucdb". Ý nghĩa của lệnh này như sau:
    • coverage save : lệnh lưu kết quả coverage với các loại coverage được mô tả trong một file .ucdb
    • -codeAll : Tất cả các loại coverage, tương được với tùy chọn " -code bcestf " 
    • -cvg : lưu lại dữ liệu của covergroup, cái dùng để đặc tả function coverage
    • -onexit : tự động lưu lại dữ liệu coverage khi thoát trình mô phỏng
    • $ARGV[0].ucdb : tên file cơ sở dữ liệu coverage. Trong script này, tên file là <tên testcase>.ucdb
Ví dụ 3 - Lệnh chạy mô phỏng và tạo cơ sở dữ liệu coverage
my $vsim = "$VSim -c -novopt work.testTop \\
+UVM_TESTNAME=cTest \\
+UVM_VERBOSITY=UVM_LOW \\
-do \"coverage save -codeAll -cvg -onexit $ARGV[0].ucdb; run -all;\" \\
-coverage \\
    -coveranalysis \\
-cvgperinstance \\

-l vsim.log";
  • Tạo báo cáo coverage cho một testcase
    • vcover report : Lệnh tạo báo cáo coverage
    • -html : tùy chọn tạo file báo cáo dạng HTML. File này được mở dễ dàng bằng các trình duyệt web như chrome, firefox, ...
    • -htmldir : chỉ định thư mục lưu báo cáo coverage
    • -code : chọn các loại code coverage sẽ có trong báo cáo
    • -cvg : hiển thị coverage group trong báo cáo
    • $ARGV[0].ucdb : tên file cơ sở dữ liệu coverage được tạo ra ở bước chạy mô phỏng phía trên
Ví dụ 4 - Lệnh tạo báo cáo coverage cho một testcase
my $vcover = "$VCov report -html -code bcestf -testhitdata -cvg $ARGV[0].ucdb";
  • Tạo báo cáo coverage cho toàn bộ kết quả chạy mô phỏng gồm nhiều testcase (merge coverage)
    • Lưu cơ sở dữ liệu coverage của mỗi testcase đến thư mục ./cov sau mỗi lần chạy mô phỏng bằng cách sao chép file cở dữ liệu sau khi chạy một testcase đến thư mục ./cov.
Ví dụ 5 - Lưu lại cơ sở dữ liệu coverage của mỗi testcase
system "cp -rf $ARGV[0].ucdb ../cov/";
    • Thực thi hợp nhất (merge) các cơ sở dữ liệu coverage. Lệnh sau đây sẽ hợp nhất tất cả các cơ sở dữ liệu của từng testcase lưu trong ./cov thành một file cơ sở dữ liệu tên mergedCov.ucdb
Ví dụ 6 - Lệnh hợp nhất các cơ sở dữ liệu
my $vmerge = "$VCov merge mergedCov.ucdb ../cov/*.ucdb";
  • Tạo file báo cáo coverage từ cơ sở dữ liệu đã được hợp nhất. Lệnh sau đây sẽ tạo file báo cáo tên index.html (tên mặc định) trong thư mục ./sim từ cơ sở dữ liệu đã hợp nhất trong file mergedCov.ucdcb.
Ví dụ 7 - Tạo file báo cáo
my $vcover = "$VCov report -html -htmldir ./ -code bcestf -cvg mergedCov.ucdb";
Chi tiết được thể hiện trong một script Perl tên ./sim/run_qsim.pl có thể tải cuối bài viết. Với môi trường này, các bạn chạy từng testcase bằng lệnh:
./run_qsim.pl <tên testcase>
Sau khi chạy toàn bộ hoặc một vài testcase, báo cáo coverage tổng hợp có thể được tạo bằng lệnh
./run_qsim.pl MERGE_COVERAGE
Mở file ./sim/index.html để kiểm tra kết quả coverage. Một số điểm quan trọng cần lưu ý:
  1. run_qsim.pl là Perl script sử dụng các biến đại diện cho các lệnh của QuestaSim như sau:
    • $VLog ứng với vlog
    • $VSim ứng với vsim
    • $VCov ứng với vcover
  2. Script chạy với Cygwin terminal trên hệ điều hành Windows
  3. Các tùy chọn của từng lệnh đã giới thiệu trên đây có thể khác nhau trên các phiên bản phần mềm khác nhau. Vì vậy, bạn cần kiểm tra lại các lệnh và các tùy chọn với tài liệu tên "Command Reference Manual" kèm theo phần mềm đang sử dụng.
  4. Báo cáo coverage với định dạng .html có thể chỉ được mở trên một số trình duyệt web và phiên bản trình duyệt nhất định
  5. Định dạng của báo cáo coverage có thể khác nhau trên các phiên bản phần mềm khác nhau.
  6. Phần mềm không có license thương mại sẽ bị hạn chế một số chức năng coverage, ví dụ như báo cáo coverage không được tạo ra chi tiết và đầy đủ.
7) Phân tích kết quả coverage
7.1) Báo cáo tổng quan
Tác giả chạy lệnh sau đây để lấy kết quả coverage cho testcase tên trialPat.
./run_qsim.pl trialPat
Sau khi chạy, dùng một trình duyệt web như Firefox, Chrome, ... để mở file ./sim/index.html, các bạn sẽ quan sát thấy như sau:
Hình 6: Báo cáo coverage
Kết quả coverage của từng thành phần trong môi trường được thể hiện bằng chỉ số % mức độ bao phủ. Mỗi loại coverage được sắp xếp thành từng cột, ứng với các tùy chọn đã trình bày ở mục trên.
  • Phần bên trái ngoài cùng liệt kê tất cả các thành phần được lấy coverage theo cấu trúc môi trường mô phỏng
  • Phần bên phải là báo cáo tổng hợp từng loại coverage
Bên dưới báo cáo tổng hợp này sẽ có báo cáo chi tiết. Báo cáo chi tiết này sẽ liên kết đến các báo cáo phân tích giúp người kiểm tra xác định các thành phần code hoặc chức năng (function) chưa được kiểm tra. Báo cáo chi tiết gồm các cột sau:

  • Coverage Type: Liệt kê loại coverage
  • Bins: Tổng số điểm kiểm tra được thống kế
  • Hits: Số điểm đã được bao phủ 100%
  • Misses: Số điểm chưa được bao phủ. Các điểm này có thể được bao phủ một phần hoặc hoàn toàn chưa được bao phủ.
  • Weight: Trọng số bao phủ, mặc định là 1
  • %Hit: là tỷ số của Hits/Bins
  • Coverage: là kết quả coverage

Hình 7: Báo cáo chi tiết xuất hiện bên dưới báo cáo tổng hợp
Đây cũng là cấu trúc chung của một một báo cáo coverage trên QuestaSim, nó gồm:
  • Một báo cáo tổng hợp bên trên giúp đánh giá nhanh kết quả
  • Một báo cáo chi tiết bên dưới giúp liên kết đến các báo cáo phân tích kết quả
Người kiểm tra có thể truy xuất kết quả coverage của các thành phần nằm sâu hơn trong cấu trúc môi trường, các sub-instance, bằng các click vào các liên kết trên báo cáo coverage. Sau đây, tác giả sẽ phân tích cụ thể báo cáo của từng loại coverage.
Hình 8: Báo cáo coverage của intance uart0_chk trong testTop.uart_protocol_checker_top
7.2) Function coverage
Như đã trình bày ở trên function coverage là do người kiểm tra dịnh nghĩa bằng SV. Phần mềm sẽ thống kê lại các instance của covergroup mà người dùng định nghĩa.
Trong ví dụ này, chúng ta sẽ xem kết quả của của instance b2b, được tạo từ covergroup back_2_back, trong testTop.uart_protocol_checker_top.uart0_chk.
Hình 9: Báo cáo coverage của covergroup trong function coverage
Báo cáo trên có 3 bảng thể hiện 3 thông tin:
  • Bảng 1 thống kê số instance của các covergroup
  • Bảng 2 thống kê số coverpoint của các covergroup
  • Bảng 3 thống kê số bins của các coverpoint
7.3) Statements coverage
Báo cáo cho các loại code coverage sẽ được minh họa trên instance testTop.dut_top.uart_0.transmitter.
Hình 10: Báo cáo coverage của instance testTop.dut_top.uart_0.transmitter
Click vào các loại coverage tương ứng trong cột "Coverage Type" sẽ xem được báo cáo chi tiết của từng loại. Để xem báo cáo chi tiết của kết quả coverage, click vào Statements.
Hình 11: Báo cáo statement coverage
Trong báo cáo chi tiết của Statements coverage:
  • Các dòng code màu đen không thuộc phạm vi thống kê của statement coverage. Chúng sẽ được thống kê trong các loại báo cáo coverage khác.
  • Các dòng code được tô xanh là các phát biểu đã được thực thi, ứng với đã bao phủ.
  • Các dòng code được tô đỏ là các trường hợp code chưa được thực thi, ứng với chưa bao phủ. Các điểm chưa bao phủ cần được xem xét để thêm testcase kiểm tra hoặc bỏ qua dựa trên mô tả của thiết kế.
Hình 12: Báo cáo thể hiện chi tiết các dòng code đã được bao phủ và chưa được bao phủ
Trong báo cáo trên, ở phát biểu case(ctrl_txt[1:0]), code chỉ mới thực thi trường hợp đầu tiên, ứng với ctrl_txt=2'b00, ba trường hợp còn lại và nhánh default chưa được thực thi. Như vậy, người kiểm tra phải tạo thêm testcase để kiểm tra trường hợp 2'b01, 2'b10 và 2'b11. Nhánh default không cần phải bao phủ vì ctrl_txt[1:0] chỉ có 2 bit nên chỉ cần bao phủ đủ 4 trường hợp.
7.4) Branches coverage
Báo cáo thể hiện rõ nhánh điều kiện nào đã bao phủ, ứng với Status là Covered và nhánh nào chưa bao phủ, ứng với Status là ZERO.
Nếu đã bao phủ, cột Hits sẽ cho biết số lần nhánh này được thực thi trong testcase hiện tại. Nếu chưa bao phủ, cột Hits sẽ là 0.
Hình 13: Báo cáo branch coverage
Cột Branch sẽ cho biết nhánh nào của điều kiện chưa được thực thi. Người kiểm tra có thể click vào tiêu đề của bảng báo cáo để xem RTL code tương ứng.
Hình 14: Dòng 68 là RTL code của bảng cáo cáo 3
7.5) Expressions coverage
Expressions coverage của QuestaSim dùng cơ chế FEC (Focused Expression Coverage) để đánh giá kết quả coverage.
FEC là một cơ chế đánh giá kết quả coverage trên các ngõ vào của một biểu thức. Biểu thức là vế phải của một phép gán hoặc một biểu thức điều kiện. Nguyên tắc của FEC là một biểu thức gọi là được bao phủ 100% nếu tất cả các ngõ vào của nó được bao phủ đầy đủ. Một ngõ vào được bao phủ đầy đủ bằng cách áp dụng phương pháp sau đây:
  • Tất cả các ngõ vào khác phải trong trạng thái cho phép ngõ vào này điều khiển giá trị ngõ ra
  • Ngõ ra phải đạt cả hai giá trị 0 và 1 khi ngõ vào này điều khiển
Giá trị coverage được tính bằng số ngõ vào được bao phủ đầy đủ chia tổng số ngõ vào.
Hình 15: Báo cáo expression coverage theo cơ chế FEC
Với mỗi biểu thức, kết quả sẽ gồm 2 bảng. Ví dụ trên đây là báo cáo cho biểu thức:
tx_shift_complete = ctrl_d9? (shift_tx_counter[3:0]==4'd10): (shift_tx_counter[3:0]==4'd9)
  • Bảng đầu tiên: báo cáo trên từng ngõ vào của biểu thức, trường hợp này là 3 ngõ vào là ctrl_d9, shift_tx_counter[3:0]==10 và shift_tx_counter[3:0]==9. Báo cáo gồm các cột sau:
    • Covered: Ngõ vào đã được bao phủ hay chưa
      • No: Chưa bao phủ
      • Yes: Đã bao phủ
    • Reason For No Coverage: Lý do của trường hợp chưa bao phủ (No). Trong ví dụ này, lý do gồm:
      • ctrl_d9 đã xảy ra trường hợp bằng 0 ('_0' hit) nhưng chưa xảy ra trường hợp bằng 1 (but '_1' not hit)
      • shift_tx_counter[3:0]==10 chưa xảy ra trường hợp nào (No hits) tác động đến kết quả ngõ ra  tx_shift_complete. Chú ý, "No hits" nghĩa là ngõ vào này chưa được tác động để điều khiển ngõ ra chứ không phải có ý nghĩa "chưa xảy ra trường hợp bộ đếm bằng 10". Bạn hãy xem lại giải thích về FEC. Ở đây, vì ctrl_d9 chưa bằng 1 nên nhánh này chưa được chọn, vì vậy, chưa được bao phủ.
    • Hint hướng dẫn làm thế nào để bao phủ trường hợp này. Trong ví dụ này, để bao phủ các trường hợp No, chúng ta cần thực hiện:
      • Đối với ngõ vào ctrl_d9: Lái ctrl_d9=1 (Hit '_1') và làm cho ngõ ra xuất hiện giá trị 0 hoặc 1 (for output ->0 or ->1)
      • Đối với shift_tx_counter[3:0]==10: Biểu thức này phải được lái giá trị ngõ ra bằng 0 và 1.

*Ghi chú: '_0 và '_1' ứng với phần hậu tố (suffix) được thêm vào các ngõ vào ở bảng chi tiết thứ 2.
  • Bảng thứ 2 sẽ chi tiết hóa từng trường hợp bao phủ của ngõ vào.
    • FEC Target là tên tín hiệu với hậu tố thể hiện giá trị cần được bao phủ. Ví dụ, ctrl_d9_0 là trường hợp ctrl_d9=0.
    • Hits (->0) là trường hợp bao phủ mà ngõ vào lái ngõ ra biểu thức bằng 0. Nếu giá trị cột này là 1 thì trường hợp này đã xảy ra, bằng 0 là chưa xảy ra. Ví dụ, cột này của ctrl_d9_0=1 chứng tỏ đã xảy ra trường hợp ctrl_d9=0 và làm ngõ ra tx_shift_complete=0
    • Hits (->1) là trường hợp bao phủ mà ngõ vào lái ngõ ra biểu thức bằng 1. Nếu giá trị cột này là 1 thì trường hợp này đã xảy ra, bằng 0 là chưa xảy ra. Ví dụ, cột này của ctrl_d9_0=1 chứng tỏ đã xảy ra trường hợp ctrl_d9=0 và làm ngõ ra tx_shift_complete=1
    • Matching Input Patterns (->0) thể hiện giá trị của 3 ngõ vào theo thứ tự đã làm cho ngõ ra bằng 0. Ví dụ, cột này của ctrl_d9_0 là {0-0} chứng tỏ trường hợp sau đã xuất hiện làm ngõ ra bằng 0:
      • ctrl_d9=0
      • shift_tx_counter[3:0]==10 không được thống kê giá trị trong trường hợp này, ứng với dấu "-"
      • shift_tx_counter[3:0]==9 bằng 0, ứng với shift_tx_counter khác 9
    • Matching Input Patterns (->1) thể hiện giá trị của 3 ngõ vào theo thứ tự đã làm cho ngõ ra bằng 1. Ví dụ, cột này của ctrl_d9_0 là {0-1} chứng tỏ trường hợp sau đã xuất hiện làm ngõ ra bằng 1:
      • ctrl_d9=0
      • shift_tx_counter[3:0]==10 không được thống kê giá trị trong trường hợp này, ứng với dấu "-"
      • shift_tx_counter[3:0]==9 bằng 1, ứng với shift_tx_counter đã bằng 9
Trong bảng trên, ký hiệu "E-Miss" và "Not Testable" thể hiện một trường hợp không thể kiểm tra. Ví dụ, trường hợp (shift_tx_counter[3:0]==9)_0, đây là trường hợp ngõ vào phải bằng 0, tức shift_tx_counter[3:0] khác 9. Trong biểu thức này, khi ctrl_d9=0 mà shift_tx_counter[3:0] khác 9 thì chỉ có thể làm ngõ ra bằng 0, không bao giờ "shift_tx_counter[3:0] khác 9" mà làm ngõ ra bằng 1. Vì vậy, cột Hits (->1) là "E-Miss" và cột Matching Input Patterns (->1) là "Not Testable".
Chú ý, bạn có thể click vào biểu thức ở đầu bảng báo cáo để xem file code chứa biểu thức này.
7.6) Conditions coverage
Báo cáo của condition coverage gần tương tự như expression coverage.Báo cáo này cũng theo cơ chế thống kế FEC. Bạn đọc có thể tự phân tích chi tiết. Mỗi điều kiện sẽ được thống kê kết quả bằng 2 bảng. Ở hình minh họa dưới đây, điều kiện sau đây được thể hiện:
else if ((state == IDLE) & load_data)
Điều kiện này không được bao phủ trường hợp (state==IDLE) bằng 0, ứng với state khác IDLE.
Hình 16: Báo cáo condition coverage
Trong báo cáo condition coverage và expression coverage, cột "Matching ..." thay bằng cột "Non-Masking Conditions(s)" trong các phiên bản mới hơn của QuestaSim. Cột này chỉ cung cấp tổ hợp giá trị của các ngõ vào còn lại trong biểu thức giúp ngõ vào cần bao phủ có thể lái được ngõ ra.
Hình 17: Báo cáo condition coverage của phiên bản QuestaSim mới hơn bản 10.2c
Xét lại điều kiện ((state == IDLE) & load_data), quan sát cột Non-Masking Condition(s) ứng với Row 1, giá trị cột này là load_data, điều này nghĩa là load_data phải bằng 1 thì biểu thức state == 0 (0 là IDLE) mới có thể ảnh hưởng (quyết định) đến giá trị ngõ ra.
7.7) Toggles coverage
Kết quả của báo cáo này được tính bằng công thức:
Mức độ bao phủ (Status) = số hit / tổng số trường hợp chuyển trạng thái được kiểm tra 
Trong ví dụ này, toggle coverage chỉ kiểm tra 2 trường hợp:
  • Chuyển từ 0 sang 1
  • Chuyển từ 1 sang 0
Không kiểm tra các trường hợp:
Chuyển từ 0 sang Z
Chuyển từ 1 sang Z
Chuyển từ Z sang 0
Chuyển từ Z sang 1
Muốn kiểm tra toàn bộ trường hợp, trong các lệnh tổng hợp và chạy mô phỏng. Bạn cần chọn "x" thay vì "t". Ví dụ, lệnh vlog sẽ dùng tùy chọn sau:
+cover=bcesfx
thay cho tùy chọn:
+cover=bcestf
Hình 18: Báo cáo condition coverage
Bài viết đã giới thiệu cơ bản về coverage và phân tích các báo cáo coverage với phần mềm QuestaSim 10.2c. Bạn có thể tải môi trường đầy đủ về để kiểm tra thử.

Dữ liệu có thể tải: 

Lịch sử cập nhật:
1/ 2019.11.26 - Tạo lần đầu

Danh sách tác giả:
1. Phạm Thanh Trâm
2. Đoàn Đức Hoàng
3. Trương Công Hoàng Việt
4. Nguyễn Sinh Tơn
5. Nguyễn Hùng Quân

Chủ Nhật, 22 tháng 9, 2019

[UVM] Bài 7 - Mô tả về các checker của môi trường

Bài viết này sẽ mô tả về các checker được sử dụng trong môi trường UVM là "apb_protocol_checker" và "uart_protocol_checker".
Các bài viết cùng chủ đề có thể tìm thấy ở đây.

1) Tổng quan về checker
Checker là một thành phần hỗ trợ kiểm tra một vài chức năng nào đó của DUT hoặc các thành phần khác trong môi trường test. Checker có nhiệm vụ giám sát điểm cần kiểm tra và đưa ra các cảnh báo nếu điểm cần kiểm tra vi phạm các điều kiện nhất định.
Với ý nghĩa giám sát và kiểm tra, tùy vào từng trường hợp, chức nằng của checker có thể là một thành phần độc lập viết bằng System Verilog đơn thuần hoặc tích hợp trong Monitor hay Scoreboard. 
Như vậy, việc có hay không có checker là một tùy chọn của người build môi trường.
Trong thực tế, đối với các thiết kế không thể được tiếp cận sâu hơn vào RTL code mà chỉ có thể test (kiểm tra) thông qua các port (input và output), thì việc thực hiện một thành phần checker độc lập, phần lớn là không cần thiết vì chức năng giám sát và kiểm tra được tích hợp vào Monitor và Scoreboard. Ví dụ, một thiết kế của một nhà cung cấp lõi IP được mua về để sử dụng nhưng bị đóng gói (mã hóa) RTL code.
Đối với các thiết kế tự phát triển, ví dụ như lõi IP UART-APB trong loạt bài viết này, checker có thể giúp giám sát và kiểm tra hoạt động của các tín hiệu nội nằm bên sâu bên trong thiết kế thông qua việc sự dụng đường dẫn cấu trúc, ví dụ như:
testTop.dut_top.uart_0.receiver.<tên tín hiệu>
Trong ví dụ trên, chúng ta lấy một tín hiệu chứa trong instance "receiver" của "uart_0".
Việc sử dụng checker giúp kiểm tra và xác thực hoạt động chính xác của các tín hiệu nội trong các điều kiện test. Bên cạnh đó, checker có thể hỗ trợ phát hiện lỗi bên trong thiết kế một cách hiệu quả.
Hình 1: Minh họa kết nối tín hiệu cần kiểm tra đến checker
Đối với môi trường này, hai checker được sử dụng để kiểm tra một số điều kiện lỗi trên giao thức APB và UART. Việc kiểm tra này có thể thực hiện bằng cách cách khác. Tuy nhiên, các bạn hãy xem đây như một tham khảo về việc thực hiện checker.
2) Mô tả các thực hiện checker
Trong môi trường, các checker được đặt trong thư mục "UvmEnvUartApb/checker". Mỗi checker gồm 2 phần:
  • Lõi checker: là một module thực thi giám sát và kiểm tra chức năng các input của nó. Các input là các điểm cần test.
    • <tên checker>.sv
  • Kết nối checker (Top checker): là một module gọi (instance) lõi checker và kết nối các điểm cần check của môi trường đến lõi checker.
    • <tên checker>_top.sv
Trong đó, Top checker sẽ được instance ở file top của môi trường, file testTop.sv, và cùng cấp với instance của top DUT là module dut_top.
Hình 2: Vị trí của checker trong môi trường
Môi trường hiện tại có hai checker là:
  • APB protocol checker
  • UART protocol checker
Hai checker này được gọi thành 4 instance trong môi trường là:
  • APB protocol checker có 2 instance:
    • apb0_chk nối với APB interface giữa Driver của coApbMasterAgentTx và uart_0 trong dut_top
    • apb1_chk nối với APB interface giữa Driver của coApbMasterAgentRx và uart_1 trong dut_top
  • UART protocol checker có 2 instance:
    • uart0_chk nối với:
      • APB interface giữa Driver của coApbMasterAgentTx và uart_0 trong dut_top
      • đường truyền UART uart_0to1, đường kết nối từ port truyền nối tiếp uart_tx của uart_0 đến port nhận nối tiếp của uart_1
    • apb1_chk nối với:
      • APB interface giữa Driver của coApbMasterAgentRx và uart_1 trong dut_top
      • đường truyền UART uart_1to0, đường kết nối từ port truyền nối tiếp uart_tx của uart_1 đến port nhận nối tiếp của uart_0
Hình 3: Vị trí các checker apb_protocol_checker_top và uart_protocol_checker_top trong đường bao số 2
Mỗi checker sẽ có 3 cấp cảnh báo lỗi, gọi là SEVERITY, là:
  • ERROR - Lỗi phải sửa
  • WARNING - Cảnh báo phải kiểm tra
  • INFO - Thông tin giúp việc debug và trace dễ dàng hơn
Mỗi cấp độ cảnh báo lỗi sẽ được quy định có hiển thị hay không thông qua các `define sau khi chạy mô phỏng:
  • APB_ERROR_SEVERITY - Chỉ hiện thị các thông điệp ERROR
  • APB_WARNING_SEVERITY - Chỉ hiện thị các thông điệp ERROR và WARNING
  • APB_INFO_SEVERITY - Hiện thị các thông điệp ERROR, WARNING và INFO
Mặc định là APB_ERROR_SEVERITY, nghĩa là chỉ có các thông điệp ở mức ERROR được hiển thị nếu có.
2) APB protocol checker
APB protocol checker là checker giám sát kiểm tra giao thức APB giữa Driver và DUT (dut_top).
Checker này sẽ dùng một biến trạng thái apb_state[1:0] để giám sát trạng thái SETUP và ACCESS của một APB transfer.
Hình 4: Các trạng thái hoạt động của APB protocol, tham khảo mục Operating states trong tài liệu  AMBA™ 3 APB Protocol specification của ARM 
Máy trạng thái của checker hoạt động như sau:
  • Luôn giữ trong trạng thái IDLE khi reset đang xảy ra, preset_n=0
  • Chuyển từ IDLE sang trạng thái giám sát SETUP, SETUP_CHECK, ngay khi hết reset, preset_n=1
  • Chuyển từ trạng thái SETUP_CHECK sang trạng thái ACCESS_CHECK khi psel=1
  • Chuyển từ trạng thái ACCESS_CHECK về SETUP_CHECK khi pready=1

Hình 5: Máy trạng thái (FSM) của APB protocol checker
Ví dụ 1 - Máy trạng thái của APB protocol checker
always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n) apb_state <= `DLYCHK IDLE;
    else apb_state[1:0] <= `DLYCHK apb_next_state[1:0];
  end
  always @ (*) begin
    case (apb_state[1:0])
      IDLE: begin
        apb_next_state[1:0] = SETUP_CHECK;
      end
      SETUP_CHECK: begin
        if (psel)
          apb_next_state[1:0] = ACCESS_CHECK;
        else
          apb_next_state[1:0] = SETUP_CHECK;
      end
      ACCESS_CHECK: begin
        if (pready)
          apb_next_state[1:0] = SETUP_CHECK;
        else
          apb_next_state[1:0] = ACCESS_CHECK;
      end
      default: begin
        apb_next_state[1:0] = apb_state[1:0];
      end
    endcase
  end
Checker sẽ báo lỗi trong hai trường hợp sau:
  • [APB_ERROR] psel và penable cùng tích cực trong trạng thái SETUP_CHECK
  • [APB_ERROR] penable không tích cực trong trạng thái ACCESS_CHECK
Ví dụ 2 - Code báo lỗi APB protocol
always @ (posedge pclk) begin
    case (apb_state[1:0])
      SETUP_CHECK: begin
        if (psel) begin
          //Check 1
          if (penable) begin
            $display ("[APB_ERROR][%s][%t] PSEL and PENABLE are asserted 1 in the SETUP phase\n", INST_NAME, $time);
          end
        end
      end
      ACCESS_CHECK: begin
        //Check 1
        if (~penable) begin
          $display ("[APB_ERROR][%s][%t] PENABLE is not asserted 1 in the ACCESS phase\n", INST_NAME, $time);
        end
      end
    endcase
  end
Bên cạnh đó, checker còn hỗ trợ cảnh báo giá trị "x" và "z" trên các tín hiệu APB trong khi hoạt động, bao gồm:
  1. [APB_ERROR] Báo lỗi psel bị "x" hoặc "z"
  2. [APB_ERROR] Báo lỗi penable bị "x" hoặc "z"
  3. [APB_ERROR] Báo lỗi pwrite bị "x" hoặc "z"
  4. [APB_ERROR] Báo lỗi paddr bị "x" hoặc "z"
  5. [APB_ERROR] Báo lỗi pstrb bị "x" hoặc "z" trong Write transfer
  6. [APB_ERROR] Báo lỗi pready bị "x" hoặc "z"
  7. [APB_ERROR] Báo lỗi pslverr bị "x" hoặc "z"
  8. [APB_WARNING] Báo lỗi pwdata bị "x" hoặc "z"
  9. [APB_WARNING] Báo lỗi prdata bị "x" hoặc "z"
Ví dụ 3 - Code kiểm tra giá trị "x" và "z" trên các tín hiệu APB
  assign paddr_or  = |paddr;
  assign pstrb_or  = |pstrb;
  assign pwdata_or = |pwdata;
  assign prdata_or = |prdata;
  always @ (posedge pclk) begin
    if (preset_n & psel) begin
      //Check 2
      case (penable)
        1'bx: $display ("[APB_ERROR][%s][%t] PENABLE is x\n", INST_NAME, $time);
        1'bz: $display ("[APB_ERROR][%s][%t] PENABLE is z\n", INST_NAME, $time);
      endcase
      //Check 3
      case (pwrite)
        1'bx: $display ("[APB_ERROR][%s][%t] PWRITE is x\n", INST_NAME, $time);
        1'bz: $display ("[APB_ERROR][%s][%t] PWRITE is z\n", INST_NAME, $time);
      endcase
      //Check 4
      case (paddr_or)
        1'bx: $display ("[APB_ERROR][%s][%t] PADDR is x\n", INST_NAME, $time);
        1'bz: $display ("[APB_ERROR][%s][%t] PADDR is z\n", INST_NAME, $time);
      endcase
      //Check 5
      if (pwrite) begin
        case (pstrb_or)
          1'bx: $display ("[APB_ERROR][%s][%t] PSTRB is x\n", INST_NAME, $time);
          1'bz: $display ("[APB_ERROR][%s][%t] PSTRB is z\n", INST_NAME, $time);
        endcase
      end
      //Check 7
      case (pready)
        1'bx: $display ("[APB_ERROR][%s][%t] PREADY is x\n", INST_NAME, $time);
        1'bz: $display ("[APB_ERROR][%s][%t] PREADY is z\n", INST_NAME, $time);
      endcase
      //Check 8
      if (pready) begin
        case (pslverr)
          1'bx: $display ("[APB_ERROR][%s][%t] PSLVERR is x\n", INST_NAME, $time);
          1'bz: $display ("[APB_ERROR][%s][%t] PSLVERR is z\n", INST_NAME, $time);
        endcase
      end
      //Check 6
      `ifdef APB_WARNING_SEVERITY
        if (pwrite) begin
          case (pwdata_or)
            1'bx: $display ("[APB_WARNING][%s][%t] PWDATA is x\n", INST_NAME, $time);
            1'bz: $display ("[APB_WARNING][%s][%t] PWDATA is z\n", INST_NAME, $time);
          endcase
        end
        //Check 9
        if (pready) begin
          case (prdata_or)
            1'bx: $display ("[APB_WARNING][%s][%t] PRDATA is x\n", INST_NAME, $time);
            1'bz: $display ("[APB_WARNING][%s][%t] PRDATA is z\n", INST_NAME, $time);
          endcase
        end
      `endif
    end
    //Check 10
    case (psel)
      1'bx: $display ("[APB_ERROR][%s][%t] PSEL is x\n", INST_NAME, $time);
      1'bz: $display ("[APB_ERROR][%s][%t] PSEL is z\n", INST_NAME, $time);
    endcase
  end
Cuối cùng, checker hỗ trợ hiển thị thông tin các Read/Write transfer trên các APB interface hỗ trợ bedug khi cần.
Ví dụ 4 - Code in thông tin read/write transfer của APB
always @ (posedge pclk) begin
  if ((apb_state[1:0] == ACCESS_CHECK) & pready) begin
          if (pwrite) begin
            $display ("[APB_INFO][%s][%t] A WRITE transaction PADDR=%8h PWDATA=%8h PSTRB=%4h PSLVERR=%b\n", INST_NAME, $time, paddr, pwdata, pstrb, pslverr);
          end
          else begin
         $display ("[APB_INFO][%s][%t] A READ transaction PADDR=%8h PRDATA=%h PSLVERR=%b\n", INST_NAME, $time, paddr, prdata, pslverr);
          end
        end
      end
    end

3) UART protocol checker
UART protocol checker là checker giám sát độ rộng bit truyền trên đường truyền UART, từ chân TX của UART này đến chân RX của UART kia. Để thực hiện được điều này, checker sẽ cần biết các thông tin cấu hình liên quan đến khung truyền UART. Vì vậy, ngoài UART interface, checker còn được kết nối đến APB interface để cập nhật thông tin cấu hình của DUT khi thông tin này bị thay đổi.
Các thông tin liên quan đến khung truyền UART bao gồm:
  • UART có được enable hay không? bit 0 của thanh ghi SE, địa chỉ offset 0x04
  • UART có truyền parity hay không? bit 1 của thanh ghi SE, địa chỉ offset 0x04
  • Tốc độ baud được thiết lập là bao nhiêu? thanh ghi BR, địa chỉ offset 0x08
Ví dụ 5 - Code lưu lại thông tin cấu hình của UART
  assign reg_sel = psel & penable & pready;
  assign reg_we = pwrite & reg_sel & (&pstrb[3:0]);
  assign se_we  = reg_we & (paddr[4:0] == 5'b0_0100);
  assign br_we  = reg_we & (paddr[4:0] == 5'b0_1000);
  //Enable register
  always @(posedge pclk)begin
    if(~preset_n) apb_chk_se_info[2:0] <= `DLYCHK 3'd0;
    else if(se_we) apb_chk_se_info[2:0] <= `DLYCHK pwdata[2:0];
  end
  //Baud rate register
  always @(posedge pclk)begin
    if(~preset_n) apb_chk_br_info[7:0] <= `DLYCHK 8'd0;
    else if(br_we) apb_chk_br_info[7:0] <= `DLYCHK pwdata[7:0];
  end
Về phía đường truyền UART, một bit trạng thái được sử dụng để giám sát khi nào đường truyền cần được kiểm tra. Bit này được tích cực 1 nếu phát hiện một cạnh xuống trên đường truyền UART và xóa khi kết thúc của bit STOP.
Ví dụ 6 - Code của FSM
// Detect the falling edge
  always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n) uart_net_sync <= `DLYCHK 1'b1;
    else uart_net_sync <= `DLYCHK uart_net;
  end
  assign uart_net_rising  = ~uart_net_sync & uart_net;
  assign uart_net_falling = uart_net_sync & ~uart_net;
  //Select the number of bits of UART frame
  always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n)
      chk_uart_state <= `DLYCHK CHK_UART_IDLE;
    else if (frame_start)
      chk_uart_state <= `DLYCHK CHK_UART_CHECK;
    else if (frame_end)
      chk_uart_state <= `DLYCHK CHK_UART_IDLE;
  end
  assign frame_end   = next_bit & apb_chk_se_info[0]
                       & (bit_count[3:0] == uart_bit_num[3:0]);
  assign frame_start = uart_net_falling & (~chk_uart_state | frame_end);
Việc phát hiện START và STOP bit của checker hoạt động độc lập với DUT. Một bộ đếm số bit truyền, bit_count, trong khung truyền UART sẽ giám sát khi nào một khung truyền UART được hoàn thành.
Ví dụ 7 - Code của bộ đếm số bit trong truyền UART
assign bit_count_clr = frame_end;
assign bit_count_inc = (chk_uart_state == CHK_UART_CHECK) & next_bit;
  always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n)
      bit_count[3:0] <= `DLYCHK 4'd0;
    else if (bit_count_clr)
      bit_count[3:0] <= `DLYCHK 4'd0;
    else if (bit_count_inc)
      bit_count[3:0] <= `DLYCHK bit_count[3:0] + 1'b1;
  end
  // Parity:    START - 8 DATA - 1 Parity - 1 STOP
  // No parity: START - 8 DATA - 1 STOP
  assign uart_bit_num[3:0] = apb_chk_se_info[1]? 4'd10: 4'd9;
  //UART bit width
  assign bit_width[12:0] = 16*(apb_chk_br_info[7:0] + 1'b1) - 1'b1;
  //Bit width counter
 assign next_bit = (width_count[12:0] == bit_width[12:0]);
Nguyên tắc kiểm tra độ rộng bit như sau:
  • Giá trị bit sẽ được lưu lại trong 1 biến, bitCheckedValue, khi bắt đầu mỗi bit. Một bộ đếm độ rộng bit, width_count, được sử dụng để giám sát khi nào kết thúc 1 bit. Bộ đếm độ rộng bit đếm số xung clock theo công thức baud rate mà DUT hỗ trợ.
  • Trong suốt qua trình kiểm tra, nếu giá trị trên đường truyền UART khác giá trị bit cần kiểm tra lưu trong  bitCheckedValue thì một lỗi sinh ra.

Ví dụ 8 - Code bộ đếm độ rộng bit
assign clr_width_count = next_bit;
assign inc_width_count = chk_uart_state;
always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n) width_count[12:0] <= `DLYCHK 13'd0;
    else if (clr_width_count)
      width_count[12:0] <= `DLYCHK 13'd0;
    else if (inc_width_count)
      width_count[12:0] <= `DLYCHK width_count[12:0] + 1'b1;
  end
Ví dụ 9 - Code của phần kiểm tra giá trị bit truyền ứng với độ rộng bit đã thiết lập
always @ (posedge pclk) begin
    if (preset_n & next_bit)
      bitCheckedValue <= `DLYCHK uart_net;
  end
assign baud_rate_error = (bitCheckedValue != uart_net_sync) & chk_uart_state;
  always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n) begin
      baud_rate_error_sync <= `DLYCHK 1'b0;
    end
    else begin
      baud_rate_error_sync <= `DLYCHK baud_rate_error;
    end
  end
  assign baud_rate_error_rising = ~baud_rate_error_sync & baud_rate_error;
  //
  always @ (posedge pclk, negedge preset_n) begin
    if (~preset_n) begin
      baud_rate_error_report <= `DLYCHK 1'b0;
    end
    else if (preset_n & baud_rate_error) begin
      baud_rate_error_report <= `DLYCHK 1'b1;
    end
  end
Cuối cùng, checker sẽ cảnh báo các lỗi sau đây:
  • Lỗi đường truyền UART bị "x" hoặc "z".
  • Lỗi nếu độ rộng bit bị vi phạm, vi phạm này xảy ra khi giá trị 1 bit bị thay đổi trước khi bit này đạt được độ rộng mong muốn.

Ví dụ 10 - Code của phần cảnh báo lỗi "x" và "z" trên đường truyền UART
always @ (posedge pclk) begin
    if (preset_n) begin
      case (uart_net)
        1'bx: $display ("[UART_ERROR][%t][%s] %s is x\n", $time, INST_NAME, INST_NET);
        1'bz: $display ("[UART_ERROR][%t][%s] %s is z\n", $time, INST_NAME, INST_NET);
      endcase
    end
  end
Ví dụ 11 - Code của phần cảnh báo lỗi độ rộng bit
always @ (posedge pclk, negedge preset_n) begin
    if (preset_n & baud_rate_error_rising) begin
      $display ("[UART_ERROR][%t][%s] %s Bit width is violated  \n-- Expected bit value: %b\n-- Actual bit value: %b", $time, INST_NAME, INST_NET, bitCheckedValue, uart_net_sync);
    end
  end

Chi tiết hơn các bạn hãy tải code của checker, chạy mô phỏng thử, xem waveform để phân tích.

Dữ liệu có thể tải:

Lịch sử cập nhật:
1/ 2019.09.22 - Tạo lần đầu

Danh sách tác giả:
1. Phạm Thanh Trâm
2. Nguyễn Sinh Tơn
3. Đoàn Đức Hoàng (email: hoangbk154@gmail.com)
4. Trương Công Hoàng Việt
5. Nguyễn Hùng Quân

Chủ Nhật, 15 tháng 9, 2019

[UVM] Bài 6 - Mô tả hoạt động của Monitor và Scoreboard

Bài viết này mô tả giao tiếp và chức năng của Monitor và Scoreboard trong môi trường UVM đã được build ở bài 5. Bài viết tập trung vào việc giải thích làm thế nào một transaction được truyền từ Monitor đến Scoreboard và làm thế nào Scoreboard kiểm tra dữ liệu truyền nhận giữa hai UART trong môi trường. Bên cạnh đó, chức năng chi tiết của Monitor cũng sẽ được trình bày.
Tham khảo các bài viết trước ở đây.

1) Kết nối của Monitor và Scoreboard
1.1) Các giao tiếp và ports
Trước khi đi vào chức năng chi tiết của Monitor và Scoreboard. Nội dung phần này mô tả chi tiết kết nối của Monitor và Scoreboard trong môi trường.
Hình 1: Sơ đồ khối của môi trường UVM
Trong môi trường này, uart_0, còn gọi là UART-TX, kết nối với Agent coApbMasterAgentTx còn uart_1, còn gọi là UART-RX, kết nối với Agent coApbMasterAgentRx. Chú ý, việc đặt tên -TX và -RX không có nghĩa là uart_0 chỉ truyền còn uart_1 chỉ nhận. Hai UART này có chức năng truyền và nhận như nhau.
Quan sát hình minh họa trên đây, bạn sẽ thấy Monitor có các kết nối sau:
  • APB interface có tên instance là vifApbMaster để giám sát tất cả các transaction đọc/ghi
  • Interrupt interface có tên instance là vifInterrupt để giám sát tất cả các tín hiệu interrupt
  • Analysis port có tên instance là ap_toScoreboard để gửi các transaction có kiểu dữ liệu là cApbTransaction trên APB interface đến Scoreboard
Bên cạnh đó, giữa Monitor và Scoreboard còn một analysis port khác tên preset_toScoreboard được dùng để gửi thông tin reset cho Scoreboard. Kiểu dữ liệu gửi trên port này là logic. Cái này không thể hiện trên hình minh họa nên các bạn chú ý khi đọc code.
Trong Monitor, cApbMasterMonitor.sv, các thành phần kết nối trên được khai báo như sau:
//Declare analysis ports
uvm_analysis_port #(logic) preset_toScoreboard;
uvm_analysis_port #(cApbTransaction) ap_toScoreboard;
//Declare the monitored interfaces
virtual interface ifApbMaster vifApbMaster;
virtual interface ifInterrupt vifInterrupt;
Hai analysis port sẽ được tạo trong build_phase
ap_toScoreboard = new("ap_toScoreboard", this); preset_toScoreboard = new("preset_toScoreboard", this);
Scoreboard có các kết nối như sau:
  • Port tên aimp_frmMonitorTX để nối với analysis port ap_toScoreboard của Monitor trên Agent coApbMasterAgentTx.
  • Port tên aimp_frmMonitorRX để nối với analysis port ap_toScoreboard của Monitor trên Agent coApbMasterAgentRx.
  • Port tên aimp_resetfrmTX để nối với analysis port preset_toScoreboard của Monitor trên Agent coApbMasterAgentTx.
Scoreboard không có port kết nối với analysis port preset_toScoreboard của Monitor trên Agent coApbMasterAgentRx vì trong môi trường này, chân preset_n của hai UART được sử dụng chung một nguồn. Khi reset xảy ra, toàn bộ DUT sẽ được reset.
Trong Scoreboard, cScoreboard.sv, các thành phần kết nối được khai báo như sau:
uvm_analysis_imp_frmMonitorTX #(cApbTransaction, cScoreboard) aimp_frmMonitorTX;
uvm_analysis_imp_frmMonitorRX #(cApbTransaction, cScoreboard) aimp_frmMonitorRX;
uvm_analysis_imp_resetfrmTX #(logic, cScoreboard) aimp_resetfrmTX;
Các port này sẽ được tạo trong build_phase của Scoreboard như sau:
imp_frmMonitorTX = new("aimp_frmMonitorTX", this);
aimp_frmMonitorRX = new("aimp_frmMonitorRX", this);
aimp_resetfrmTX = new("aimp_resetfrmTX", this);
Chú ý, phần hậu tố khi khai báo port (tô màu vàng) phải được đăng ký để sử dụng như sau:
`uvm_analysis_imp_decl(_frmMonitorTX)
`uvm_analysis_imp_decl(_frmMonitorRX)
`uvm_analysis_imp_decl(_resetfrmTX)
1.2) Kết nối
Monitor kết nối với đến APB interface và interrupt interface thông qua khai báo uvm_config_db ở testTop.sv.
//Connect APB interface
uvm_config_db#(virtual interface ifApbMaster)::set(null,"uvm_test_top.coEnv.coApbMasterAgentTx*","vifApbMaster",vifApbMaster_Tx);
uvm_config_db#(virtual interface ifApbMaster)::set(null,"uvm_test_top.coEnv.coApbMasterAgentRx*","vifApbMaster",vifApbMaster_Rx);
//Connect Interrupt interface
uvm_config_db#(virtual interface ifInterrupt)::set(null,"uvm_test_top.coEnv.coApbMasterAgentTx*","vifInterrupt",vifInterrupt_Tx);
uvm_config_db#(virtual interface ifInterrupt)::set(null,"uvm_test_top.coEnv.coApbMasterAgentRx*","vifInterrupt",vifInterrupt_Rx);
Các khai báo uvm_config_db giúp kết nối các instance của interface khai báo ở testTop.sv kết nối đến các thành phần UVM trong các Agent coApbMasterAgentTx và coApbMasterAgentRx.
Trong build_phase của Monitor, các kết nối sẽ được kiểm tra lại để đảm bảo các kết nối này đã tồn tại.
//Check the APB connection
if(!uvm_config_db#(virtual interface ifApbMaster)::get(this,"","vifApbMaster",vifApbMaster)) begin
  `uvm_error("cApbMasterDriver","Can NOT get vifApbMaster!!!")
end
//Check the interrupt connection
if(!uvm_config_db#(virtual interface ifInterrupt)::get(this,"","vifInterrupt",vifInterrupt)) begin
  `uvm_error("cVSequencer","Can NOT get vifInterrupt!!!")
end
Một thông điệp lỗi sẽ báo thông qua macro `uvm_error nếu kết nối với interface mong muốn không được tìm thấy.
2) Cấu trúc và chức năng của Monitor
Monitor (cApbMasterMonitor.sv) được mở rộng từ class uvm_monitor và xây dựng một số chức năng riêng trong hai phase:

  1. build_phase
    1. Kiếm tra kết nối của Monitor trong môi trường. Cụ thể, hai interface vifApbMaster và vifInterrupt được kiểm tra.
    2. Tạo đối tượng của các TLM analysis port. Cụ thể, hai port được tạo là ap_toScoreboard và preset_toScoreboard.
    3. Tạo đối tượng coApbTransaction để lưu lại các transaction trên APB interface. Chú ý, đối tượng này phải cùng kiểu dữ liệu với kiểu dữ liệu sẽ được gửi qua analysis port ap_toScoreboard , kiểu dữ liệu này là cApbTransaction.
  2. run_phase sẽ thực hiện các task sau song song 
    1. collect_data() giám sát APB interface, phát hiện read/write transfer trên APB interface và lưu lại trong đối tượng coApbTransaction. Sau đó, method write() được sử dụng để gửi gói coApbTransaction đến TLM analysis port ap_toScoreboard. Phía Scoreboard sẽ nhận và xử lý
    2. detect_reset() giám sát tín hiệu reset, preset_n trong testTop.sv, của môi trường, lưu lại trong biến preset_n và gửi giá trị này qua analysis port preset_toScoreboard bằng method write().
    3. monitor_ifEn() giám sát các APB transaction ghi vào thanh ghi interrupt enable của DUT, DUT gồm 2 UART là UART-TX và UART-RX. Nếu phát hiện transaction ghi vào thanh ghi interrtupt enable, địa chỉ offset 16'h0010, giá trị ghi vào các bit interrupt enable sẽ được lau lại trong biến ifEn[4:0]. Biến này sẽ được dùng để điều khiển task detect_intf() với mục đích xác định xem một tín hiệu interrupt tích cực, chuyển từ mức 0 sang mức 1, có đúng hay không.
    4. detect_intf() giám sát tất cả các tín hiệu interrupt thông qua interface vifInterrupt. Nếu một interrupt tích cực nhưng không được enable, `uvm_error sẽ thông báo. Nếu một interrupt tích cực và được enable, một `uvm_warning sẽ thông báo cho người test biết để kiểm tra lại xem có đúng như mong muốn hay không. Một biến ifSta được sử dụng để đảm bảo các thông điệp `uvm_error và `uvm_warning chỉ in ra một lần khi interrupt bắt đầu tích cực. Nếu không có biến này, các thông điệp sẽ được in liên tục trong suốt quá trình tín hiệu interrupt tích cực sau mỗi cạnh lên xung clock.
Hình 2: Cấu trúc và chức năng chính của Monitor (cApbMasterMonitor.sv)
3) Cấu trúc và chức năng của Scoreboard
Scoreboard (cScoreboard.sv) được mở rộng từ class uvm_scoreboard và thêm các chức năng riêng như sau:

  1. build_phase tạo ra các đối tượng của analysis implementation port gồm:
    1. aimp_frmMonitorTX kết nối với analysis port ap_toScoreboard của Monitor trong agent coApbMasterAgentTx, xem cEnv.sv.
    2. aimp_frmMonitorRX kết nối với analysis port ap_toScoreboard của Monitor trong agent coApbMasterAgentRx, xem cEnv.sv.
    3. aimp_resetfrmTX kết nối với analysis port preset_toScoreboard của Monitor trong agent coApbMasterAgentTx, xem cEnv.sv. Như đã trình bày ở trên, do reset của hệ thống là chung nên chỉ cần kết nối một port để giám sát trạng thái reset, không cần kết nối port đến agent coApbMasterAgentRx.
  2. run_phase thực thi các task write<suffix> với suffix được khai báo bởi `uvm_analysis_imp_decl. Các task này gọi là các "analysis implementation" dùng để xử lý các transaction nhận trên analysis implementation port. Các bạn xem lại mục TLM trong bài viết số 3.
    1. write_resetfrmTX() giám sát trạng thái reset được Monitor gửi đến là tích cực cờ trạng thái rst_flg=1 nếu reset tích cực mức 0.
    2. write_frmMonitorTX() giám sát việc đọc dữ liệu từ thanh ghi dữ liệu của UART-TX (uart_0), địa chỉ offset 16'h000C. Nếu có một tranasaction đọc thanh ghi dữ liệu, dữ liệu đọc trên prdata[7:0] sẽ so sánh với dữ liệu truyền phía UART-RX (uart_1). Nếu giá trị dữ liệu đọc trùng khớp thì thông điệp báo SUCCESS sẽ được in ra, nếu dữ liệu đọc bị sai thì thông điệp báo lỗi FAIL sẽ được in ra.
    3. write_frmMonitorRX() giám sát việc đọc dữ liệu từ thanh ghi dữ liệu của UART-RX (uart_1), địa chỉ offset 16'h000C. Nếu có một tranasaction đọc thanh ghi dữ liệu, dữ liệu đọc trên prdata[7:0] sẽ so sánh với dữ liệu truyền phía UART-TX (uart_0). Nếu giá trị dữ liệu đọc trùng khớp thì thông điệp báo SUCCESS sẽ được in ra, nếu dữ liệu đọc bị sai thì thông điệp báo lỗi FAIL sẽ được in ra.
  3. report_phase sẽ kiểm tra lại số lượng dữ liệu mong muốn truyền trên UART-TX và UART-RX. Nếu vẫn còn dữ liệu cần truyền ở UART-TX nhưng chưa được đọc và kiểm tra trên UART-RX hoặc ngược lại thì Scoreboard sẽ cảnh báo với `uvm_warning. Chú ý, đây không phải là một lỗi (error) vì nó phụ thuộc vào mục đích test của người viết testbench (testcase).
Hình 3: Cấu trúc và chức năng của Scoreboard (cScoreboard.sv)
Để kiểm tra dữ liệu truyền nhận giữa 2 UART, Scoreboard thực hiện như sau:
  1. Lưu lại dữ liệu cần truyền trên mỗi UART trong một hàng đợi (queue), nó tương tự như một FIFO
  2. Tại UART phía đối diện, UART nhận, mỗi dữ liệu đọc ra sẽ được so sánh với dữ liệu trong FIFO đã lưu ở bước trên.
  3. Sau mỗi lần so sánh, dữ liệu đã so sánh trong FIFO sẽ được xóa.
Thuật toán chi tiết để kiểm tra dữ liệu truyền từ UART-TX đến UART-RX sẽ được trình bày sau đây. Chiều từ UART-RX đến UART-TX thực hiện tương tự nên sẽ không trình bày chi tiết.
Đầu tiên, phần xử lý cập nhật dữ liệu truyền vào hàng đợi (FIFO) như sau:
1. Kiểm tra trạng thái reset thông qua cờ rst_flg Nếu cờ này bằng 1, tức là có reset, thì Scoreboard sẽ khởi động lại và xóa toàn bộ FIFO lưu dữ liệu truyền
queueTransTX.delete();
uartEnTX = 1'b0;
2. Nếu không phải trong trạng thái reset, rst_flg=0, bit báo trạng thái enable của UART-TX sẽ luôn được cập nhật đầu tiên nếu có bất cứ transaction ghi đến thanh ghi SE, offset là 16'h0004.
if (TransOnTX.pwrite && (TransOnTX.paddr[15:0] == 16'h0004)) begin
  uartEnTX = TransOnTX.pwdata[0];
end
 3. Nếu UART-TX được enable, uartEnTX=1, và có transaction ghi vào thanh ghi dữ liệu, địa chỉ offset là 16'h000C thì dữ liệu này là dữ liệu cần truyền nên sẽ lưu vào cuối FIFO. Chú ý, chỉ lưu 8 bit LSB, 24 bit đầu là 0.
else if (TransOnTX.pwrite && (TransOnTX.paddr[15:0] == 16'h000C) && uartEnTX) begin  queueTransTX.push_back(TransOnTX.pwdata & 32'h0000_00ff);
end
Phần xử lý lưu dữ liệu truyền vào FIFO của UART-TX được thực hiện bởi method write_frmMonitorTX. Method này lấy APB transaction từ Monitor cảu agent coApbMasterAgentTx.
Hình 4: Giai thuật cập nhật dữ liệu truyền của Scoreboard
Tiếp theo, phần xử lý kiểm tra dữ liệu đọc, dữ liệu truyền từ UART-TC đến UART-RX, trên UART-RX được thực hiện như sau:
1. Kiểm tra trạng thái reset thông qua cờ rst_flg Nếu cờ này bằng 1, tức là có reset, thì Scoreboard sẽ không thực thi quá trình kiểm tra dữ liệu đọc.
2. Nếu không phải trong trạng thái reset, rst_flg=0, bit báo trạng thái enable của UART-RX sẽ luôn được cập nhật đầu tiên nếu có bất cứ transaction ghi đến thanh ghi SE, offset là 16'h0004.
if (TransOnRX.pwrite && (TransOnRX.paddr[15:0] == 16'h04)) begin
  uartEnRX = TransOnRX.pwdata[0];
end
 3. Nếu UART-RX được enable, uartEnRX=1, và có transaction đọc từ thanh ghi dữ liệu, địa chỉ offset là 16'h000C thì dữ liệu này là dữ liệu sẽ được kiểm tra.
4. Lấy phần tử đầu tiên từ FIFO lưu dữ liệu truyền của UART-TX, queueTransTX.
queueCompRX = queueTransTX[0];
4. So sánh 8 bit dữ liệu đọc được với phần tử đầu tiên lấy từ FIFO.
  • Nếu hai giá trị bằng nhau, in ra thông điệp báo SUCCESS
  • Nếu hai giá trị khác nhau, in ra thông điệp báo FAIL
if ((TransOnRX.prdata & 32'h0000_00ff) == queueCompRX) begin  `uvm_info("SB INFO", $sformatf("[%t] SUCCESS on UART-RX: transfer data = %2h, queueTransTX size = %d", $time, TransOnRX.prdata, queueTransTX.size()), UVM_LOW);
endelse begin  `uvm_error("SB ERROR", $sformatf("[%t] FAIL on UART-RX: read data = %2h, expected data =%2h, queueTransTX size = %d", $time, TransOnRX.prdata, queueCompRX, queueTransTX.size()))
end
5. Kiểm tra lại số phần tử trong FIFO queueTransTX.
  • Nếu FIFO rỗng, in ra thông điệp báo dữ liệu đọc được không phải là dữ liệu được truyền từ UART-TX. Điều này xảy ra khi phát một transaction đọc thanh ghi dữ liệu khi nó rỗng.
  • Nếu FIFO không rỗng, thực hiện xóa phần tử đầu tiên của FIFO, phần tử vừa dùng để so sánh ở bước trên
if (queueTransTX.size() != 0) begin
  queueTransTX.delete(0);
endelse begin  uvm_warning("SB UNFINISH-RX", "Read data but do NOT have any transmitted data");
end
Phần xử lý kiểm tra dữ liệu đọc trên UART-RX được thực hiện trong method write_frmMonitorRX.
Hình 5: Giải thuật kiểm tra dữ liệu nhận trên UART của Scoreboard
Các bạn hãy tải source code trên Github để vừa đọc vừa so sánh. Mọi góp ý, các bạn có thể comment dưới bài viết.

Dữ liệu có thể dowload:
Môi trường UVM bản Draff trên Github

Lịch sử cập nhật:
1/ 2019.09.15 - Tạo lần đầu

Danh sách tác giả:
1. Phạm Thanh Trâm
2. Đoàn Đức Hoàng (email: hoangbk154@gmail.com)
3. Trương Công Hoàng Việt
4. Nguyễn Sinh Tơn
5. Nguyễn Hùng Quân