Họ printf của Verilog
Ba lệnh hệ thống cho phép bạn in ra stdout của trình mô phỏng:
$display: in một lần, tự thêm xuống dòng.$write: in một lần, không xuống dòng.$monitor: tự động in mỗi khi một tín hiệu được theo dõi thay đổi.
Cả ba đều nhận một chuỗi định dạng và danh sách đối số, giống hệt printf. Các định dạng tương tự nhưng có nét riêng của Verilog.
$display: lựa chọn mặc định
$display là lệnh bạn dùng nhiều nhất:
Bạn sẽ thấy kết quả kiểu như:
Hello, world.
byte_val = ab
byte_val = 10101011
byte_val = 171
byte_val = 171 (no padding)
multi: nibble=1010 count=42
Lưu ý:
%dđệm theo độ rộng mặc định dựa trên kích thước toán hạng. Vớireg8 bit, đó là 3 ký tự (đủ chỗ cho255). Các khoảng trắng phía trước trông khá xấu trong kết quả dạng bảng: dùng%0dđể bỏ chúng.%h,%b,%omặc định cũng đệm tương tự. Phần lớn code testbench dùng các biến thể%0khi việc căn lề không có ích.- Ký tự xuống dòng ở cuối được thêm tự động. Đừng đặt
\nở cuối chuỗi định dạng của$display: bạn sẽ có thêm một dòng trống.
Các định dạng
Danh sách Verilog hỗ trợ:
| Định dạng | Ý nghĩa |
|---|---|
%b | nhị phân |
%d | thập phân (có dấu nếu tín hiệu là signed) |
%h hoặc %x | thập lục phân |
%o | bát phân |
%c | một ký tự ASCII (8 bit thấp) |
%s | chuỗi |
%t | thời gian mô phỏng |
%m | tên phân cấp của phạm vi hiện tại |
%% | ký tự % |
%0X | không đệm phía trước, áp dụng cho %b, %d, v.v. |
%b, %d, %h, %o là bốn định dạng bạn dùng trong 95% trường hợp. %t phổ biến kế tiếp: dùng bất cứ khi nào bạn muốn một dòng log có dấu thời gian.
$write: không xuống dòng
$write giống hệt $display, chỉ khác là không tự thêm xuống dòng:
Kết quả:
abc
done
Hữu ích khi ghép một dòng duy nhất từ thân vòng lặp:
$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");
$monitor: tự in khi có thay đổi
$monitor đăng ký một danh sách theo dõi. Trình mô phỏng tính lại và in ra mỗi khi bất kỳ tín hiệu nào nhắc đến trong chuỗi định dạng thay đổi:
Bạn sẽ thấy ba dòng, mỗi dòng ứng với một lần đầu vào thay đổi. Không cần tự gọi $display sau mỗi lần đổi kích thích: $monitor làm việc đó cho bạn.
Hai giới hạn:
- Chỉ một
$monitorhoạt động tại một thời điểm. Gọi lại sẽ thay thế danh sách theo dõi trước. Dùng$monitoroffvà$monitoronđể tạm tắt rồi bật lại. - Các thay đổi trong cùng một bước thời gian gộp thành một lần in. Nếu
avàbcùng thay đổi ở thời điểm 5, monitor chỉ in một lần với cả hai giá trị mới, không phải hai lần.
Khi nào dùng lệnh nào
$display: phần lớn kết quả testbench. Gọi nó một cách tường minh sau khi đổi kích thích, sau các lần chuyển trạng thái quan trọng, hoặc trong một khối lấy mẫualways @(posedge clk).$write: khi bạn muốn ghép một dòng từ vòng lặp hoặc từ nhiều mẩu nhỏ.$monitor: khi bạn muốn theo dõi liên tục một nhóm nhỏ tín hiệu và chỉ thấy kết quả khi chúng thay đổi. Hữu ích lúc mới bắt đầu gỡ lỗi; khó dùng hơn trong các script kiểm thử hồi quy vì tổng số dòng in ra không cố định.
Với hầu hết quy trình làm việc, $display là đủ. Chỉ dùng $monitor khi bạn thật sự muốn kết quả in ra liên tục theo từng thay đổi.
Làm việc với thời gian
$time trả về thời gian mô phỏng hiện tại dưới dạng số nguyên 64 bit. Kết hợp nó với %0t:
$display("at %0t: signal flipped", $time);
Kết quả trông như at 25: signal flipped (đơn vị phụ thuộc vào timescale của bạn).
Nếu cần độ chính xác nhỏ hơn một đơn vị (hiếm khi), hãy dùng $realtime: nó trả về kiểu real.
%t tự định dạng thời gian với độ rộng mặc định do trình mô phỏng chọn. %0t bỏ phần đệm đó.
Lấy mẫu ở sườn clock
Một cách viết gọn để theo dõi thiết kế tuần tự: một khối always @(posedge clk) riêng in ra một lần mỗi chu kỳ:
Mẫu lấy mẫu này bảo đảm mỗi clock có đúng một dòng log, rất hợp với các bài kiểm thử hồi quy so khớp theo kết quả in ra.
Ghi log ra tệp
Mở một tệp bằng $fopen, ghi log bằng $fdisplay (hoạt động như $display nhưng ghi vào một handle tệp):
integer fd;
initial begin
fd = $fopen("results.txt", "w");
$fdisplay(fd, "test=%s status=%s", test_name, status);
$fclose(fd);
end
$fopen trả về một handle 32 bit; truyền nó làm đối số đầu tiên cho $fdisplay, $fwrite, $fstrobe và các lệnh tương tự. Ngoài ra, các lệnh này giống hệt các lệnh in ra console tương ứng.
Tiếp theo
$display và các lệnh cùng họ cho bạn log dạng văn bản. Để gỡ lỗi trực quan, tức xem tín hiệu như điện áp theo thời gian, bạn cần dạng sóng VCD. Bài tiếp theo, Dumpfile và VCD, nói về $dumpfile và $dumpvars, hai lệnh biến mô phỏng thành dạng sóng đồ họa mà bạn có thể tua qua lại.
Câu hỏi thường gặp
$display và $monitor trong Verilog khác nhau thế nào?
$display in ra một lần, ngay khi được thực thi, giống printf trong C. $monitor đăng ký một danh sách theo dõi; mỗi khi bất kỳ tín hiệu nào trong danh sách thay đổi, thông điệp đã định dạng được tự động in ra. Mỗi lúc chỉ có một $monitor hoạt động; gọi lại sẽ thay thế danh sách theo dõi trước đó.
$display trong Verilog hỗ trợ những định dạng nào?
Các định dạng phổ biến: %b (nhị phân), %d (thập phân), %h (thập lục phân), %o (bát phân), %c (một ký tự lấy từ byte thấp), %s (chuỗi), %t (thời gian mô phỏng), %m (tên phân cấp của instance). Dùng dạng %0d để bỏ khoảng đệm phía trước: %d đệm theo độ rộng mặc định, còn %0d không đệm gì.
$write trong Verilog là gì?
$write giống $display nhưng không tự thêm ký tự xuống dòng. Hữu ích khi bạn muốn ghép một dòng kết quả từ nhiều lần gọi. Một lệnh $display ở cuối (không có đối số hoặc có ký tự xuống dòng ở cuối) sẽ kết thúc dòng.
Làm sao in thời gian mô phỏng trong Verilog?
Dùng $time (hoặc $realtime khi cần độ phân giải nhỏ hơn một đơn vị) với định dạng %t: $display("at time %t: ...", $time);. Dùng %0t để bỏ khoảng đệm mặc định. Nếu chỉ cần số nguyên đơn vị thời gian, %0d với $time cũng được: $display("t=%0d", $time);.