Menu

Timescale và độ trễ trong Verilog: thời gian mô phỏng

Cách chỉ thị `timescale đặt đơn vị cho #delay, quy tắc kết hợp các đơn vị khác nhau giữa các tệp, và cách độ trễ tương tác với logic theo xung clock.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

"Thời gian" trong Verilog nghĩa là gì

Verilog không có sẵn khái niệm giây. Trình mô phỏng tiến theo "đơn vị thời gian", tức các nhịp số nguyên tùy ý, và code của bạn dùng #N để chờ N nhịp. Chỉ thị `timescale là thứ ánh xạ các nhịp đó sang thời gian thực.

`timescale 1ns / 1ps

module test;
    initial begin
        $display("t = %0t", $time);   // 0
        #5;
        $display("t = %0t", $time);   // 5 ns
        #1.5;
        $display("t = %0t", $time);   // 6500 ps (or 6.5 ns)
        $finish;
    end
endmodule

Hai tham số:

  • Đơn vị (số thứ nhất): #1 nghĩa là gì. 1ns nói rằng một nhịp là một nano giây.
  • Độ chính xác (số thứ hai): mô phỏng theo dõi thời gian mịn đến mức nào bên trong đơn vị đó. 1ps nói rằng các độ trễ thập phân được làm tròn đến pico giây.

Độ chính xác không được thô hơn đơn vị (1ps / 1ns là không hợp lệ). Các lựa chọn phổ biến:

  • 1ns / 1ps: chuẩn trên thực tế. Mọi thứ tính bằng nano giây, độ chính xác dưới nano giây cho mọi độ trễ cổng logic.
  • 1ps / 1ps: khi mô hình hóa mạch cực nhanh hoặc khi mọi độ trễ đều dưới nano giây.
  • 1us / 1ns: cho mô phỏng chậm ở quy mô nhúng (thời gian truyền một byte UART, các giao thức chậm).

Đặt nó ở đâu

`timescale là một chỉ thị trình biên dịch, không phải cấu trúc ở cấp module. Nó nằm ở ngay đầu tệp, trước mọi module:

`timescale 1ns / 1ps

module foo(...);
    // ...
endmodule

Phạm vi của nó là "từ điểm này trở đi theo thứ tự biên dịch, cho đến `timescale kế tiếp hoặc khi hết quá trình biên dịch". Điều này có một hệ quả khó thấy: tệp không khai báo `timescale rõ ràng sẽ thừa hưởng giá trị mà tệp được biên dịch trước đó khai báo, và điều này phụ thuộc vào thứ tự trình biên dịch đọc các tệp. Tránh bất ngờ bằng cách đặt `timescale ở đầu mọi tệp.

Trình soạn thảo trên trình duyệt trong bộ tài liệu này đặt sẵn một timescale mặc định cho bạn (thường là 1ns / 1ps), vì thế các ví dụ ở những bài trước chạy được mà không cần khai báo. Trong dự án thật, bạn nên khai báo rõ ràng.

#delay trong thực tế

Sau `timescale 1ns / 1ps:

#5            // wait 5 ns
#100          // wait 100 ns
#1.5          // wait 1.5 ns (precision allows it)
#0.001        // wait 1 ps (just barely above precision)
#0            // zero-delay; useful for ordering events at the same time

Bên trong khối initial hoặc always, #N chặn luồng thủ tục trong N đơn vị thời gian. Trình mô phỏng tạm dừng khối này (các khối chạy đồng thời khác vẫn tiếp tục) và chạy tiếp sau khoảng trễ đó.

Bạn có thể đặt độ trễ trước một phép gán:

#10 a = 1;        // wait 10 ns, then assign
data <= #2 new_value;   // schedule the non-blocking assignment 2 ns from now

Dạng thứ nhất là thứ không thể thiếu trong testbench. Dạng thứ hai (non-blocking có trễ) được dùng trong mô phỏng mức cổng để mô hình hóa độ trễ lan truyền.

Tạo xung clock

Mẫu kinh điển:

`timescale 1ns / 1ps

module test;
    reg clk = 0;
    always #5 clk = ~clk;
    // ...
endmodule

Với timescale 1ns / 1ps, #5 là 5 ns. Xung clock đảo mỗi 5 ns, cho chu kỳ 10 ns, tức xung clock 100 MHz. Để đổi tần số, hãy đổi độ trễ:

Nửa chu kỳChu kỳTần số
#12 ns500 MHz
#2.55 ns200 MHz
#510 ns100 MHz
#1020 ns50 MHz
#2550 ns20 MHz
#50100 ns10 MHz

Nếu bạn muốn nửa chu kỳ là số thập phân (#2.5), độ chính xác phải hỗ trợ được: độ chính xác 1ps xử lý được mọi giá trị nhỏ đến 0,001 ns, nên mọi tần số hợp lý đều ổn.

Trộn timescale giữa các tệp

Một thiết kế thật có nhiều tệp, và chúng có thể khai báo timescale khác nhau. Trình mô phỏng dùng timescale của chính từng tệp để hiểu các độ trễ trong tệp đó. Nếu module_a.v khai báo `timescale 1ns / 1ps và dùng #5, đó là 5 ns. Nếu module_b.v khai báo `timescale 1us / 1ns và dùng #5, đó là 5 us.

Điều này phần lớn không lộ ra vì trình mô phỏng dù sao cũng hiển thị một trục thời gian chung, nhưng nó có nghĩa là cùng một #N ở hai tệp có thể mang ý nghĩa khác xa nhau. Cách khắc phục: chọn một timescale (chuẩn trong ngành là 1ns / 1ps) và đặt nó ở đầu mọi tệp. Đừng trộn lẫn.

Độ trễ không tổng hợp được

Điểm then chốt: #delay chỉ tồn tại cho mô phỏng. Công cụ tổng hợp đọc:

// In synthesizable RTL - WRONG
always @(posedge clk) begin
    out <= #2 in;
end

…và hoặc bỏ qua #2 (phần lớn công cụ) hoặc từ chối cấu trúc đó (các linter khắt khe hơn). Thời gian trong phần cứng thật được quyết định bởi xung clock và độ trễ lan truyền của cổng logic, cả hai đều không hiện ra trong mã nguồn.

Quy tắc: chỉ dùng # trong testbench. RTL tổng hợp được không có độ trễ #. Nếu bạn thấy mình muốn một độ trễ trong code tổng hợp được, thứ bạn thực sự cần là một bộ đếm lùi theo xung clock: đó là cách phần cứng thật "chờ".

$time và $realtime

Có hai cách đọc thời gian mô phỏng hiện tại:

  • $time trả về số nguyên 64 bit tính theo đơn vị của timescale hiện tại.
  • $realtime trả về giá trị real tính theo đơn vị của timescale hiện tại, nhưng với đầy đủ độ chính xác.

Để ghi log trong testbench, $time gần như luôn là đủ. Chỉ dùng $realtime khi bạn cần độ chính xác dưới một nhịp trong các câu lệnh in.

Mẹo thực hành

  • Luôn khai báo `timescale ở đầu mọi tệp. 1ns / 1ps là mặc định an toàn.
  • Chỉ dùng #delay trong testbench. Coi việc không có nó trong code tổng hợp được là một quy tắc cố định.
  • Khớp chu kỳ clock với tần số mục tiêu. Nếu bạn đang mô phỏng một thiết kế 50 MHz, hãy dùng chu kỳ clock 20 ns: chu kỳ không khớp có thể che giấu các bug nhạy cảm về thời gian.
  • Với tín hiệu kích thích đếm theo chu kỳ, dùng @(posedge clk) thay cho #. Cách này vẫn đúng khi chu kỳ clock thay đổi.

Tiếp theo là gì

Bạn đã đi qua mọi bài trong bộ hướng dẫn Verilog này. Từ nền tảng ngôn ngữ (wire và reg, module, toán tử), qua các khối thủ tục và luồng điều khiển, đến thiết kế đồng bộ và máy trạng thái, và cuối cùng là công cụ testbench để chứng minh mọi thứ hoạt động. Đã đến lúc xây dựng một thứ gì đó: playground bên cạnh bộ tài liệu này dùng chính trình mô phỏng chúng ta đã dùng suốt từ đầu, sẵn sàng cho bất kỳ module nào bạn phác thảo.

Câu hỏi thường gặp

`timescale 1ns / 1ps trong Verilog nghĩa là gì?

Nó nói với trình mô phỏng: 'một đơn vị thời gian trong tệp này là một nano giây, và thời gian được theo dõi với độ chính xác pico giây.' Sau chỉ thị đó, #5 chờ 5 ns, #1.5 chờ 1,5 ns (làm tròn đến pico giây), và $time được báo theo nano giây. Số thứ nhất là đơn vị; số thứ hai là độ chính xác.

Có cần `timescale trong mọi tệp Verilog không?

Cách làm tốt nhất: có. Phạm vi của chỉ thị kết thúc ở `timescale kế tiếp hoặc khi hết quá trình biên dịch, nên tệp nào không có sẽ thừa hưởng giá trị mà tệp được biên dịch trước đó khai báo. Điều này khiến thời gian không còn tất định giữa các lần build. Đặt `timescale 1ns / 1ps ở đầu mọi tệp nguồn (đây là quy ước phổ biến nhất) và bạn sẽ không bao giờ gặp bất ngờ.

#5 trong Verilog nghĩa là gì?

#5 đẩy thời gian mô phỏng tiến thêm 5 đơn vị thời gian. Đơn vị lấy từ chỉ thị `timescale đang có hiệu lực. Với `timescale 1ns / 1ps, #5 là 5 nano giây. Với `timescale 1us / 1ns, #5 là 5 micro giây. Con số có thể là số thập phân: #1.5 dùng được nếu độ chính xác mịn hơn đơn vị.

#delay trong Verilog có tổng hợp được không?

Không. #delay chỉ ảnh hưởng đến mô phỏng: công cụ tổng hợp bỏ qua hoặc từ chối nó. Thời gian trong phần cứng thật đến từ tín hiệu clock và độ trễ lan truyền của các cổng logic, không phải từ câu lệnh #. Cứ thoải mái dùng # trong testbench; đừng bao giờ viết nó trong RTL tổng hợp được.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu