Module và instance: nối hai bộ cộng mà không nhầm cổng
Tiên quyết
- Module A2: Logic tổ hợp: Đang tải
- Module A4: Tuần tự & timing: Đang tải
Sau bài này bạn sẽ
- Phân biệt tên module, tên instance và tên tín hiệu nối cổng
- Đọc kết nối .port(signal) từ parent sang child
- Tính số bit cần cho tổng và phát hiện mất carry ở biên module
- Đọc đường dẫn hierarchy và giải thích giới hạn của hierarchy sau synthesis
Một bản thiết kế, nhiều instance
Bạn có một khối cộng hai số 4 bit. Hệ thống cần cộng hai cặp số cùng lúc: cặp bên trái và cặp bên phải. Viết lại hai bản code phép cộng sẽ khó bảo trì: sửa bên trái rồi quên bên phải là một lỗi rất thực tế.
Tách phép cộng thành module nibble_sum, rồi tạo hai instance từ cùng định nghĩa. Có ba loại tên cần tách bạch:
- Tên module
nibble_sum: định nghĩa giao diện và logic của khối. - Tên instance
u_left,u_right: hai chỗ dùng cụ thể trong parenttwin_sum. - Tên tín hiệu
a0,b0,a1,b1: các dây phía parent được nối tới cổng child.
Đừng đọc instantiation như lời gọi hàm chạy lần lượt. Hai instance của ví dụ là hai nhánh logic tổ hợp trong mô hình phần cứng; không cần gọi u_left rồi mới gọi u_right. Module này không có clock và không giữ kết quả của lần cộng trước.
Đọc cổng theo hai phía
Trong khai báo nibble_sum, input và output mô tả hướng nhìn từ chính child. a, b đi vào child; total đi ra child. Một kết nối ở parent là:
nibble_sum u_left (.a(a0), .b(b0), .total(total0));
Đọc .a(a0) thành “cổng a của instance này nối với tín hiệu a0 ở parent”. Phía trước dấu ngoặc là tên cổng trong định nghĩa child; phía trong ngoặc là tín hiệu của parent. nibble_sum chọn loại module, còn u_left đặt tên cho instance.
Đổi tên dây parent từ a0 sang left_operand thì kết nối thành .a(left_operand); không tự đổi cổng child thành .left_operand(...). Đổi tên cổng child là thay đổi giao diện và phải cập nhật những nơi dùng nó.
Đây là cách giải thích quy tắc ngôn ngữ cần tiếp tục đối chiếu với [2]. Bài đang ở draft; kết quả tool ở phần sau chỉ là bằng chứng của các chương trình cụ thể, không thay thế toàn bộ đặc tả ngôn ngữ.
Hai bộ cộng, kiểm tra toàn bộ đầu vào
Mỗi đầu vào 4 bit unsigned nhận từ 0 đến 15. Tổng lớn nhất là 15 + 15 = 30, cần 5 bit. Trong child, phép cộng mở rộng rõ ràng cả hai toán hạng bằng {1'b0, a} và {1'b0, b} để giữ carry.
Trước khi chạy tool, tính tay ba kỳ vọng:
- Bên trái
15 + 1 = 16, bên phải3 + 4 = 7. - Bốn đầu vào độc lập, mỗi đầu vào có 16 giá trị:
16 × 16 × 16 × 16 = 65536trường hợp. - Ở tổ hợp cuối, cả hai cặp là
15 + 15, nên cả hai output là 30.
Mở tb_modules.sv để đọc cách tự kiểm tra. Nó không chỉ in kết quả: sau khi đổi đầu vào, #1 cho logic tổ hợp có thời gian cập nhật trong mô phỏng, rồi so cả hai output với phép cộng số nguyên của testbench. Sai bất kỳ trường hợp nào thì $fatal; chỉ khi đi hết vòng lặp mới in dòng kết quả cuối.
Synthesis xác nhận ví dụ không có flip-flop. Không dự đoán chính xác số cổng hoặc khẳng định chip giữ nguyên hai hộp u_left/u_right: tối ưu có thể biến đổi hoặc flatten hierarchy.
Đường đi trong hierarchy
Testbench tạo instance dut của twin_sum. Bên trong dut có u_left và u_right:
tb_modules
└─ dut (twin_sum)
├─ u_left (nibble_sum)
│ └─ total
└─ u_right (nibble_sum)
└─ total
dut.u_left.total đi theo tên instance, rồi tới tín hiệu total trong child. Testbench thực sự đọc đường dẫn này ở tổ hợp 15+1 và quan sát 16. Nó không viết dut.nibble_sum.total: hai instance cùng kiểu module nên tên kiểu không xác định bạn muốn nhánh nào.
Nếu dây nhận thiếu một bit?
Child đã có output 5 bit, nhưng parent vẫn có thể nối sai độ rộng:
wire [3:0] short_total;
nibble_sum dut (.a(a), .b(b), .total(short_total));
Với a=15, b=1, tổng 5 bit là 10000. Dây nhận chỉ giữ bốn bit thấp 0000, tức là 0. “Trong child đã khai báo đủ bit” chưa đủ để bảo đảm kết quả đi tới nơi nhận còn đủ bit.
Biến thể narrow trong metadata chạy chính tb_narrow.sv chứa kết nối này. Kỳ vọng tính tay là NARROW RESULT short_total=0; output thực của biến thể được lưu trong verification.json. [4]
Nếu chỉ thử các cặp có tổng nhỏ hơn 16, lỗi mất carry có thể bị bỏ qua. Vì vậy cần thử biên: 0+0, 15+0, 15+1, 15+15, và so sánh trên đủ độ rộng. Compiler có thể cảnh báo lệch width; hãy đọc warning, không chỉ nhìn việc tạo được executable.
Cách sửa là dùng dây nhận 5 bit, như total0/total1 trong bản chính. Không sửa bằng cách đổi đáp án mong đợi thành 0 nếu yêu cầu là giữ đủ tổng.
Lỗi dễ gặp và cách debug
- Nhầm tên module với instance. Tìm dòng định nghĩa
module nibble_sumrồi dòng dùngnibble_sum u_left (...). Khi đọc hierarchy, đi theou_left. - Đúng tên cổng nhưng sai tín hiệu. Kiểm từng
.port(signal)theo hai phía. Test với hai cặp đầu vào cho kết quả khác nhau; hai nhánh cùng nhận một cặp số có thể che lỗi nối nhầm. - Chỉ child đủ rộng. Lần theo cả đường đi: toán hạng, kết quả trong child, cổng output và dây parent. Ở ví dụ này mọi tổng cần đi qua đường 5 bit.
- Compile thành công là đủ. Kiểm tra lint, rồi chạy self-checking testbench với trường hợp biên. Parser không đọc được ý định thiết kế.
- Coi mỗi module là một thanh ghi. Ví dụ không có clock; tên module và số instance không tự tạo ra trạng thái. Không dùng kết quả synthesis của ví dụ để kết luận mọi module đều là logic tổ hợp.
Tự kiểm tra
Checklist khi ghép module
- Tên module chọn định nghĩa; tên instance xác định một chỗ dùng trong hierarchy.
- Với
.port(signal), kiểm riêng tên cổng child và tín hiệu parent. - Kiểm hướng, độ rộng và chức năng của cả đường nối, không chỉ câu lệnh cộng.
- Dự đoán output trước khi chạy; dùng các giá trị làm hai nhánh cho kết quả khác nhau.
- Giữ bản chất của bằng chứng: mô phỏng 0/1, synthesis không có FF, chưa đối chiếu đầy đủ LRM và không kiểm tra timing vật lý.
Nguồn và mức độ đối chiếu
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, Kết quả thử nghiệm. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, LRM: modules và port connections. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, Bằng chứng mô phỏng. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, Biến thể narrow. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, Modules; module instantiation and port connections. Chưa đối chiếu với văn bản nguồn (chưa mở).
- IEEE 1800-2023 SystemVerilog LRM, Module instantiation and port connections. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, verification.json: variants.narrow.observed. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, verification.json: observed PORTS RESULT. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm module/ports: content/code/ex.a6.modules/verification.json, content/code/ex.a6.modules/verification.json. Trích dẫn; nguồn đã đọc toàn văn.