Chào các bạn, hôm nay tiếp tục cùng mình và Học Vi Mạch Cùng ICTC tìm hiểu một chủ đề rất quan trọng trong Design Verification: Functional Coverage. Đây là một khái niệm giúp chúng ta đánh giá liệu các chức năng quan trọng của thiết kế đã được kiểm tra đầy đủ trong quá trình simulation hay chưa. Hy vọng bài viết sẽ giúp các bạn hiểu rõ hơn về Functional Coverage cũng như cách áp dụng nó trong quá trình verify một thiết kế.
Trong quá trình verification, có một câu hỏi rất quan trọng mà bất kỳ DV engineer nào cũng từng tự hỏi:
“Làm sao biết chúng ta đã test đủ chưa?”
Một thiết kế RTL thường có rất nhiều trạng thái và trường hợp hoạt động khác nhau: nhiều mode hoạt động, nhiều loại transaction, và rất nhiều corner case. Khi viết testbench và chạy simulation, chúng ta chỉ biết một điều: test pass hoặc fail. Tuy nhiên, việc test pass không có nghĩa là tất cả các chức năng của thiết kế đã được kiểm tra.
Trong thời kỳ sử dụng Verilog, verification chủ yếu dựa vào code coverage. Đây là các metric do tool cung cấp để đo mức độ RTL đã được thực thi trong simulation. Một số loại code coverage phổ biến gồm:
- Line coverage – dòng code đã chạy chưa
- Branch coverage – các nhánh if/else đã được đi qua chưa
- Toggle coverage – các bit đã toggle chưa
Tuy nhiên, code coverage có một hạn chế lớn: nó chỉ đo mức độ thực thi của code, chứ không đo được các chức năng của thiết kế đã được test đầy đủ hay chưa. Để giải quyết vấn đề này, SystemVerilog đã bổ sung một cơ chế rất quan trọng trong verification là Functional coverage. Điều đó cho phép verification engineer tự định nghĩa những feature hoặc scenario quan trọng của thiết kế cần được theo dõi. Thay vì chỉ hỏi “dòng code này đã chạy chưa?”, functional coverage giúp trả lời câu hỏi mang tính verification hơn:
“Chức năng này của thiết kế đã được test chưa?”
Khác với code coverage được tool tự động thu thập, functional coverage được định nghĩa bởi chính verification engineer dựa trên hiểu biết về thiết kế và các scenario cần kiểm tra. Functional coverage cũng đóng vai trò quan trọng trong flow Coverage Driven Verification (CDV). Thông thường quá trình verification sẽ diễn ra theo vòng lặp như sau:
Random stimulus
↓
Simulation
↓
Collect coverage
↓
Find uncovered cases
↓
Improve stimulus
Sau mỗi lần chạy regression, DV engineer sẽ kiểm tra coverage report để xem những scenario nào chưa được test. Từ đó có thể điều chỉnh constraint hoặc bổ sung thêm test để bao phủ các trường hợp còn thiếu. Để thấy rõ sự khác biệt giữa code coverage và functional coverage, hãy xem một ví dụ đơn giản.
Giả sử chúng ta đang verify một ALU đơn giản với 4 loại operation:
bit[3:0] result;
bit[3:0] a;
bit[3:0] b;
bit[1:0] opcode;
always_comb begin
case(opcode)
0: result = a + b;
1: result = a – b;
2: result = a * b;
3: result = a / b;
endcase
end
Giả sử testbench cho opcode lần lượt bằng 0,1,2,3,0; a = 5,9,7,8,11 và b = 3,4,6,2,9 thì simulation đã tạo được đầy đủ bốn loại operation. Khi đó báo cáo từ tool có thể cho thấy Code coverage đạt 100%.
Nhìn vào kết quả này, ta có thể dễ dàng kết luận rằng ALU đã được test đầy đủ. Nhưng hãy thử dừng lại một chút và tự hỏi vài câu hỏi sau:
Ví dụ với phép SUB: result = a – b
Chúng ta có thể đặt câu hỏi:
Trường hợp a > b đã được test chưa?
Trường hợp a = b đã được test chưa?
Trường hợp a < b đã được test chưa? (result có thể bị âm hoặc underflow)
Tương tự với phép DIV: result = a / b
Một số câu hỏi quan trọng:
Trường hợp b ≠ 0 đã được test chưa?
Trường hợp b = 0 đã được test chưa?
Nếu b = 0, thiết kế mong muốn behavior là gì?
Ở đây ta bắt đầu thấy một vấn đề:
Mặc dù code coverage đã đạt 100%, nhưng chúng ta không hề biết những scenario quan trọng này đã xảy ra trong simulation hay chưa. Code coverage chỉ cho chúng ta biết rằng dòng code a / b đã được thực thi. Nhưng nó không nói được rằng trường hợp b = 0 đã từng xảy ra hay chưa.
Đây chính là lý do vì sao trong verification hiện đại, DV engineer cần đến Functional Coverage. Thay vì chỉ theo dõi code, Functional coverage cho phép chúng ta định nghĩa rõ ràng các scenario cần được kiểm tra
















