Thời gian của phần mềm và thời gian của phần cứng
Trong một chương trình, thời gian trôi theo từng lệnh. CPU làm xong dòng 1 rồi mới chạy dòng 2. Muốn hai việc xảy ra cùng lúc, bạn phải dùng đến thread, async hoặc một máy thứ hai.
Trong phần cứng, mọi thứ xảy ra cùng lúc. Mạch điện không làm việc theo lượt. Bộ cộng lúc nào cũng đang cộng, bộ ghép kênh lúc nào cũng đang chọn, flip-flop lúc nào cũng đang canh clock. Không có bộ đếm chương trình. Không có "dòng hiện tại".
Verilog phải mô tả thế giới đó bằng văn bản. Cách nó làm điều này chính là nguồn gốc của mọi sự bối rối trong chương này.
Hai lớp đồng thời
Khi đọc một module Verilog, bạn đang nhìn cùng lúc hai thứ khác nhau:
- Mô tả tĩnh của mạch. Wire, thanh ghi, instance cổng logic, instance module con, phép gán liên tục. Tất cả đều tồn tại đồng thời. Thứ tự của chúng trong tệp không quan trọng.
- Các khối thủ tục
initialvàalways, trông như những chương trình nhỏ mà trình mô phỏng chạy qua từng bước. Bên trong một khối như vậy, các câu lệnh có chạy theo một kiểu thứ tự nào đó. Nhưng nhiều khốialwayscó thể cùng hoạt động một lúc, mỗi khối là một luồng nhỏ riêng trong thời gian mô phỏng.
module example(input wire a, input wire b, output wire y, output wire z);
assign y = a & b; // exists at all times
assign z = a | b; // also exists at all times, in parallel
always @(posedge a) begin
// a separate "always-on" reactor that wakes up on each
// rising edge of `a`
end
endmodule
Hai dòng assign không phải một chuỗi tuần tự. Chúng mô tả hai mảnh logic tổ hợp mà công cụ tổng hợp có thể đặt cạnh nhau. Khối always là thứ thứ ba diễn ra song song.
"Tín hiệu" nghĩa là gì
Trong phần mềm, một biến giữ giá trị cho đến khi bạn thay đổi nó. Trong Verilog, một tín hiệu giữ giá trị liên tục, và tại mỗi thời điểm, giá trị đó phụ thuộc vào thứ đang điều khiển nó.
Một wire được điều khiển từ bên ngoài (một assign, cổng đầu ra của một module con, một kết nối inout). Một reg được điều khiển từ bên trong một khối thủ tục. Cả hai lúc nào cũng có giá trị. Không có khái niệm "chưa khởi tạo" theo nghĩa của C: tín hiệu hoặc được điều khiển về một giá trị xác định, hoặc mang giá trị không xác định đặc biệt x, hoặc ở trạng thái trở kháng cao z. Hai giá trị sau được nói đến trong bài Giá trị X và Z.
Clock thay đổi mọi thứ
Khi có clock, thời gian bắt đầu có ý nghĩa. Flip-flop là một linh kiện phần cứng nhỏ chốt giá trị đầu vào ở sườn lên (hoặc sườn xuống) của clock và giữ nguyên cho đến sườn kế tiếp. Thứ cho phép bạn dựng bộ đếm, máy trạng thái, pipeline, tức mọi thứ có bộ nhớ, chính là clock.
Để ý là q <= d chứ không phải q = d. Đó là phép gán non-blocking, công cụ chủ lực của logic theo clock. Nó nói rằng "ở sườn clock kế tiếp, hãy lên lịch để q nhận giá trị hiện tại của d". Chúng ta sẽ đi sâu vào các quy tắc trong bài Blocking và non-blocking; lúc này, chỉ cần nhận ra rằng phép gán này không giả làm câu lệnh phần mềm.
RTL: mô hình tư duy truyền thanh ghi
Phần lớn code Verilog tổng hợp được viết theo phong cách gọi là Register Transfer Level, hay RTL. Ý tưởng rất đơn giản:
- Xác định mạch cần lưu những trạng thái nào (các thanh ghi).
- Với mỗi thanh ghi, mô tả hai điều: thứ gì reset nó, và logic tổ hợp nào tính giá trị kế tiếp của nó.
- Nối đầu ra của logic tổ hợp vào đầu vào của thanh ghi, và bạn có một mạch chạy được.
always @(posedge clk) begin
if (reset) state <= IDLE;
else state <= next_state;
end
always @(*) begin
case (state)
IDLE: next_state = start ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = IDLE;
endcase
end
Đó là một máy hai trạng thái. Khối always đầu tiên chạy theo clock: nó là một flip-flop. Khối thứ hai thuần tổ hợp: nó chỉ là một phương trình. Gần như mọi máy trạng thái, bộ đếm và pipeline bạn viết đều theo hình dạng đó.
Những thói quen khiến bạn mắc kẹt
Nếu bạn đến từ phần mềm, đây là danh sách ngắn những thói quen nên bỏ lại:
- "Biến được cập nhật ngay khi tôi gán." Ở sườn clock thì không: phép gán non-blocking lên lịch cập nhật vào cuối bước thời gian.
- "Câu lệnh chạy từ trên xuống." Bên ngoài khối thủ tục thì không. Bên trong khối theo clock thì phần nào đúng, nhưng blocking hay non-blocking sẽ thay đổi ý nghĩa thật của "theo thứ tự".
- "Khi nào cần thì tôi cấp phát." Phần cứng không cấp phát. Mọi thanh ghi và cổng logic phải tồn tại ngay lúc tổng hợp. Kích thước của mọi vector là cố định.
- "Vòng lặp này nhanh, chỉ là một phép toán thôi." Vòng lặp
fortrong Verilog tổng hợp được sẽ được trải phẳng thành phần cứng song song. Một vòng lặp 64 lần thành 64 bản sao của thân vòng lặp, chứ không phải một lệnh CPU chạy 64 lần.
Bạn không học cách viết chương trình. Bạn đang học cách mô tả một mạch điện. Bản năng đọc từ trên xuống chính là bản năng sai, và cần một thời gian để luyện lại.
Tiếp theo
Các bài tiếp theo hướng dẫn cài bộ công cụ trên máy (không bắt buộc, trình soạn thảo trên trình duyệt là đủ), rồi viết module đầu tiên từ con số 0. Chúng ta sẽ quay lại sự đối lập giữa phần cứng và phần mềm mỗi khi có gì đó trông kỳ lạ, vì phần lớn những chỗ "kỳ lạ" đều có chung một gốc rễ: đây không phải phần mềm.
Câu hỏi thường gặp
Phần cứng và phần mềm khác nhau thế nào khi nói về Verilog?
Phần mềm là một chuỗi lệnh được CPU thực thi lần lượt từng lệnh một. Phần cứng, thứ mà Verilog mô tả, là một mạng lưới cổng logic và dây dẫn cùng mang tín hiệu một lúc. Một tệp Verilog mô tả mạng lưới đó. Trình mô phỏng bắt chước hành vi song song ấy; công cụ tổng hợp biến nó thành mạch silicon thật.
Code Verilog có chạy từ trên xuống như C không?
Không, và coi nó như vậy là lỗi phổ biến nhất của người mới. Các câu lệnh ở cấp cao nhất (assign, instance module, khối always) đều 'tồn tại' cùng lúc. Chỉ bên trong các khối thủ tục, initial và always, mới có thứ gì đó giống thực thi tuần tự, và ngay cả ở đó, phép gán non-blocking cũng phá vỡ ảo giác này.
'Đồng thời' trong Verilog nghĩa là gì?
Nghĩa là nhiều câu lệnh mô tả các phần của một mạch cùng hoạt động một lúc. Hai câu lệnh assign trong cùng một module không phải 'dòng 1 rồi đến dòng 2': chúng là hai mảnh phần cứng chạy song song, cả hai liên tục phản ứng với đầu vào của mình.
Thiết kế RTL là gì?
RTL là viết tắt của Register Transfer Level (mức truyền thanh ghi). Đây là cách viết Verilog mô tả mạch dưới dạng các thanh ghi (flip-flop) và logic tổ hợp tính giá trị kế tiếp cho chúng. Phần lớn code Verilog tổng hợp được là RTL. Mức trên nó là mức hành vi; mức dưới là mức cổng.