Khối thủ tục chạy một lần
initial begin ... end là anh em của always. Điểm khác biệt: initial chạy đúng một lần, bắt đầu từ thời điểm 0, rồi kết thúc. Không có danh sách độ nhạy và không lặp lại.
Nó tồn tại vì một lý do: thiết lập testbench. Đặt giá trị ban đầu, bắt đầu tạo kích thích, mở tệp log, gọi $finish sau một khoảng thời gian mô phỏng cố định, tức mọi việc bạn muốn làm một lần lúc bắt đầu chạy, theo một thứ tự đã biết.
Hãy đi qua từng bước trình mô phỏng thực hiện:
- Thời gian tiến đến 0.
- Khối
initialbắt đầu. Lệnh$displayđầu tiên in ra. datađược gán8'hA5.#10đẩy thời gian mô phỏng thêm 10 đơn vị.- Lệnh
$displaythứ hai in ra. $finishkết thúc mô phỏng.
Khối đã chạy một lần. Không có lượt thứ hai.
Khung testbench chuẩn
Gần như mọi testbench bạn từng viết đều dùng một khối initial như thế này:
Hãy nhìn vào hình dạng của nó, đây là mẫu testbench bạn sẽ dùng mỗi lần:
- Mở tệp dump VCD (
$dumpfile,$dumpvars), xem bài Dumpfile và VCD. - Đặt giá trị ban đầu và giữ reset trong vài chu kỳ.
- Tạo kích thích bằng một loạt thay đổi xen giữa các độ trễ
#. $finishđể kết thúc mô phỏng gọn gàng.
Chỉ vậy thôi. Mọi bài test Verilog bạn gặp, kể cả trong ví dụ của các hãng, đều theo mẫu này.
Nhiều khối initial chạy song song
Nhiều khối initial trong cùng một module đều bắt đầu tại thời điểm 0 cùng một lúc. Mỗi khối chạy các câu lệnh của mình trong lát thời gian riêng:
Trình mô phỏng khởi động cả hai khối tại thời điểm 0. Lệnh $display đầu tiên của khối B chạy ngay. Khối A chờ 5 đơn vị rồi mới in. Khối B in lần nữa tại thời điểm 2. Lần in thứ hai của khối A diễn ra tại thời điểm 15. Lệnh $finish của khối B dừng mô phỏng tại thời điểm 22.
Tách phần thiết lập, tạo clock và tạo kích thích thành các khối initial riêng là một phong cách phổ biến: mỗi khối ngắn gọn và chỉ làm một việc.
Khởi tạo ngay trong khai báo
Một cách viết gọn phổ biến: khởi tạo reg ngay lúc khai báo. Phần lớn trình mô phỏng coi cách này giống hệt một khối initial:
reg clk = 0;
reg reset = 1;
reg [7:0] count = 0;
Tương đương với:
reg clk;
reg reset;
reg [7:0] count;
initial begin
clk = 0;
reset = 1;
count = 0;
end
Dạng gọn là thứ bạn sẽ thấy trong testbench: nó đặt giá trị ban đầu ngay cạnh khai báo, dễ nhìn lướt qua.
initial chỉ dùng cho mô phỏng
Công cụ tổng hợp bỏ qua các khối initial. Phần cứng thật không có "thời điểm 0" để chạy code thiết lập: nó có lúc cấp nguồn, có tín hiệu reset và cấu hình đến từ các sự kiện đó. Nếu bạn cần một thanh ghi bắt đầu ở trạng thái đã biết trên phần cứng thật, hãy điều khiển nó bằng tín hiệu reset bên trong một khối always @(posedge clk):
always @(posedge clk) begin
if (reset) state <= IDLE;
else state <= next_state;
end
Nhánh if (reset) đó là bản tương đương tổng hợp được của initial state = IDLE. Reset chính là câu trả lời cho câu hỏi "làm sao khởi tạo một thanh ghi trên phần cứng thật?"
Một số quy trình FPGA có chấp nhận một dạng initial giới hạn để đặt giá trị khởi đầu cho thanh ghi (ví dụ công cụ của Xilinx), nhưng đó là phần mở rộng riêng của từng hãng. Đừng dựa vào nó trong code cần chạy được ở nhiều nơi.
Những thứ bạn sẽ thấy bên trong initial
Vài mẫu thường gặp ngoài khung testbench chuẩn:
Kết thúc sau một khoảng thời gian
initial begin
#1000 $finish; // safety net: kill the sim after 1000 time units
end
Một khối initial riêng chỉ có nhiệm vụ đặt giới hạn trên cứng cho thời gian mô phỏng. Kể cả khi khối kích thích chính bị treo vì chờ một tín hiệu không bao giờ đến, khối này vẫn chạy và kết thúc lần mô phỏng.
Thiết lập dạng sóng
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, test); // dump everything under the `test` scope
end
Hai dòng này yêu cầu trình mô phỏng ghi một tệp VCD chứa mọi tín hiệu trong phạm vi test. Thiếu chúng, bạn không có dạng sóng.
Nạp sẵn nội dung bộ nhớ
reg [7:0] memory [0:255];
initial begin
$readmemh("image.hex", memory);
end
$readmemh nạp một tệp định dạng hex vào mảng. Thường dùng trong testbench CPU để nạp sẵn bộ nhớ lệnh. Chỉ dùng cho mô phỏng.
Lỗi thường gặp
Dùng initial cho logic tổng hợp được. Nó sẽ không được tổng hợp. Hãy dùng tín hiệu reset.
Quên $finish. Thiếu nó, trình mô phỏng chạy cho đến khi có thứ khác dừng nó lại (giới hạn thời gian mặc định, ngắt thủ công, v.v.). Với một bài test nhanh thì không sao; với script kiểm thử hồi quy, luôn gọi $finish.
Quên #delay giữa các phép gán kích thích. Nếu bạn viết a = 0; b = 1; mà không có độ trễ, cả hai xảy ra tại cùng một thời điểm mô phỏng và DUT có thể thấy chúng đồng thời thay vì như hai sự kiện riêng. Hãy chèn #1 hoặc lâu hơn giữa các sự kiện kích thích khác nhau.
Cố điều khiển một wire từ initial. Quy tắc giống always: chỉ reg mới là đích hợp lệ.
Tiếp theo
Bạn đã gặp hai loại khối thủ tục. Bài tiếp theo nói về chủ đề gây nhầm lẫn nhiều nhất với người mới học Verilog: Phép gán blocking và non-blocking. Biết khi nào dùng = và khi nào dùng <= là khác biệt giữa một flip-flop chạy đúng và một mớ race condition.
Câu hỏi thường gặp
Khối initial trong Verilog là gì?
initial begin ... end là một khối thủ tục chạy đúng một lần khi bắt đầu mô phỏng, tại thời điểm 0. Đây là chỗ chuẩn để thiết lập trạng thái testbench: khởi tạo tín hiệu, mở tệp log, gọi $dumpfile/$dumpvars, tạo kích thích và kết thúc mô phỏng bằng $finish. Một module có thể có nhiều khối initial; tất cả cùng bắt đầu song song tại thời điểm 0.
initial và always trong Verilog khác nhau thế nào?
initial chạy một lần tại thời điểm 0 rồi kết thúc. always chạy lặp lại mãi mãi: nó có danh sách độ nhạy và thức dậy mỗi khi các tín hiệu trong danh sách thay đổi. initial gần như chỉ dùng trong testbench. always là công cụ chủ lực của cả testbench lẫn RTL tổng hợp được.
Khối initial có tổng hợp được không?
Trong Verilog thuần thì không. Công cụ tổng hợp bỏ qua các khối initial vì phần cứng thật không có 'thời điểm 0' để chạy code thiết lập. Một số bộ công cụ FPGA chấp nhận một dạng giới hạn để đặt giá trị khởi đầu cho thanh ghi, nhưng nói chung đây là cấu trúc chỉ dùng cho mô phỏng. Hãy giữ khối initial trong testbench; dùng tín hiệu reset để khởi tạo logic tổng hợp được.
Một module Verilog có thể có nhiều khối initial không?
Có. Mỗi khối initial bắt đầu tại thời điểm 0 và chạy đến hết một cách độc lập. Chia phần thiết lập thành nhiều khối là mẫu testbench phổ biến: một khối tạo clock, một khối tạo kích thích, một khối ghi dạng sóng. Chúng chạy đồng thời từ thời điểm 0; trình mô phỏng xen kẽ các câu lệnh của chúng khi thời gian trôi.