Rẽ nhiều nhánh
case là cấu trúc rẽ nhánh phẳng của Verilog. Bạn đưa vào một biểu thức, nó chọn nhánh khớp:
case (expression)
pattern_1: statement_1;
pattern_2: statement_2;
pattern_3: begin
statement_3a;
statement_3b;
end
default: default_statement;
endcase
Về cấu trúc, nó giống switch trong C, nhưng:
- Không có
break: mỗi nhánh tự kết thúc khi nhánh tiếp theo bắt đầu. - Mẫu so khớp có thể là vector, không chỉ là số nguyên.
- Công cụ tổng hợp biến toàn bộ khối thành một mux phẳng (hoặc một bộ giải mã one-hot khi các mẫu cho phép).
Ví dụ hoàn chỉnh: mux 4 sang 1
Thân case có bốn mẫu tường minh cùng một nhánh default. Công cụ tổng hợp thấy một đầu vào 2 bit được ánh xạ sang một trong bốn giá trị và tạo ra một bộ ghép kênh 4 sang 1. Gọn, phẳng, nhanh.
Vì sao cần default
Với case tổ hợp, bỏ default là cái bẫy giống hệt if thiếu else: bất kỳ giá trị đầu vào nào không khớp đều để out không được gán, và công cụ tổng hợp suy ra một latch.
Với sel 2 bit, các mẫu ở trên đã phủ cả bốn giá trị có thể, nên về lý thuyết default là thừa. Trên thực tế:
- Công cụ tổng hợp không phải lúc nào cũng chứng minh được các nhánh đã đầy đủ.
- Bộ chọn có thể mang giá trị
xhoặczkhi mô phỏng, và giá trị đó không khớp với bất kỳ nhánh tường minh nào. - Thêm một nhánh mới sau này có thể khiến hành vi mặc định không còn được xác định.
Luôn viết default. Với máy trạng thái và logic mux mà bạn biết nhánh mặc định không bao giờ xảy ra, việc gán 'x:
default: out = 8'bx;
…báo cho công cụ tổng hợp rằng "đây là don't-care, cứ tối ưu thoải mái" và hiện một giá trị x đỏ rực khi mô phỏng nếu nhánh tưởng như không thể xảy ra lại bằng cách nào đó xảy ra. Đó là lợi cả đôi đường.
Máy trạng thái với case
Cách dùng kinh điển của case là phần logic chuyển trạng thái của một máy trạng thái hữu hạn:
Khối case (state) chính là logic chuyển trạng thái. Mỗi nhánh quyết định trạng thái kế tiếp là gì và ở lại trong đó bao lâu. Ở đây default không bao giờ xảy ra (ta đã phủ hết RED/GREEN/YELLOW trong không gian 2 bit), nhưng nó vẫn có mặt như một lưới an toàn: nếu state bằng cách nào đó thành 2'd3, FSM quay về RED một cách gọn gàng thay vì tạo ra latch.
Bài Máy trạng thái hữu hạn đi sâu hơn vào mẫu thiết kế này.
Nhiều mẫu cho một nhánh
Bạn có thể liệt kê nhiều mẫu dùng chung một câu lệnh, cách nhau bằng dấu phẩy:
case (opcode)
4'h0, 4'h1, 4'h2: result = a + b;
4'h3, 4'h4: result = a - b;
4'h8: result = a & b;
default: result = 8'd0;
endcase
Ở đây có hai opcode cùng mang nghĩa "trừ" và ba opcode cùng mang nghĩa "cộng". Công cụ tổng hợp OR các mẫu lại với nhau cho bộ so sánh.
casez và casex: so khớp don't-care
Đôi khi bạn muốn so khớp một mẫu có vài bit không xác định, chẳng hạn "mọi opcode bắt đầu bằng 010":
casez (opcode)
8'b010?_????: instruction = ALU_OP;
8'b110?_????: instruction = LOAD_OP;
8'b1110_????: instruction = JUMP_OP;
default: instruction = UNKNOWN;
endcasez
casez coi ? (và z) trong mẫu là don't-care. Mỗi ? khớp với cả 0 lẫn 1. Cách này hữu ích khi giải mã các định dạng lệnh mà một số vị trí bit không được dùng ở vài nhóm opcode.
casex mở rộng thêm, coi cả x là don't-care. casex nguy hiểm vì tín hiệu chưa khởi tạo (mang giá trị x khi mô phỏng) khớp với mọi nhánh, dẫn đến hành vi bất ngờ. Phần lớn các bộ quy tắc viết code hiện đại khuyên dùng casez và cấm casex.
Trong SystemVerilog còn có case inside, phiên bản gọn gàng nhất (chấp nhận cả khoảng giá trị lẫn danh sách), nhưng Verilog thuần chỉ dừng ở casez.
case so với chuỗi if/else if
Cả hai cấu trúc đều diễn đạt được quyết định nhiều nhánh, nhưng chúng được tổng hợp thành phần cứng khác nhau:
caselà rẽ nhánh phẳng. Công cụ tổng hợp có thể tạo ra bộ giải mã one-hot, cây mux cân bằng hoặc các cấu trúc phẳng khác. Thời gian tính toán không đổi.if/else iflà chuỗi ưu tiên. Công cụ tổng hợp tạo ra một dãy tầng nối tiếp, mỗi tầng thêm độ trễ. Chậm hơn theo hàm logarit.
Về chức năng, hai cách này chồng lên nhau. Về phong cách: dùng case mỗi khi điều kiện xoay quanh giá trị của một biểu thức duy nhất. Dùng if/else if khi có thứ tự ưu tiên thật sự hoặc khi các điều kiện liên quan đến những tín hiệu khác nhau.
// Better as case:
if (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else out = d;
// Better as if/else if:
if (urgent_event) next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready) next_state = PROCESS;
else next_state = state;
Ba điều kiện đầu đều hỏi "sel bằng bao nhiêu?": case đọc tự nhiên hơn và được tổng hợp phẳng hơn. Ba điều kiện sau là các sự kiện độc lập với thứ tự ưu tiên rõ ràng: if/else if phù hợp hơn.
Tiếp theo
Bài cuối của chương này, Vòng lặp for, nói về for trong Verilog và điều bất ngờ xảy ra khi bạn dùng nó trong code tổng hợp được. Sau đó chúng ta sẽ thật sự bước vào logic tuần tự và FSM.
Câu hỏi thường gặp
Câu lệnh case trong Verilog là gì?
case (expr) ... endcase là cấu trúc rẽ nhiều nhánh của Verilog. Nó tính biểu thức một lần rồi chuyển đến nhánh nào khớp. Đây là lựa chọn quen thuộc cho máy trạng thái, bộ giải mã opcode, bộ chọn mux và mọi trường hợp phải chọn một trong nhiều phương án loại trừ lẫn nhau.
case, casex và casez trong Verilog khác nhau thế nào?
case so khớp chính xác từng bit, kể cả giá trị x và z. casez coi z (và ?) trong các mẫu là don't-care. casex coi cả x lẫn z là don't-care. So khớp don't-care hữu ích với các mẫu opcode có vài vị trí bit không quan trọng, nhưng casex nguy hiểm khi mô phỏng vì tín hiệu chưa khởi tạo (x) có thể vô tình khớp với mọi nhánh.
Vì sao câu lệnh case trong Verilog cần default?
Không có default, công cụ tổng hợp thấy khả năng không nhánh nào khớp, kết luận tín hiệu đầu ra phải giữ giá trị cũ và suy ra một latch ngoài ý muốn. Nhánh default xử lý mọi giá trị không khớp, thường bằng cách gán đầu ra một giá trị an toàn hoặc đánh dấu nhánh đó không thể xảy ra bằng một phép gán x. Luôn viết nhánh này.
Khi nào nên dùng case, khi nào dùng if-else trong Verilog?
Dùng case khi các điều kiện loại trừ lẫn nhau và cùng dựa vào giá trị của một biểu thức: máy trạng thái, bộ giải mã opcode, bộ chọn mux. Dùng if/else khi có thứ tự ưu tiên thật sự hoặc các điều kiện liên quan đến những tín hiệu khác nhau. case được tổng hợp thành phần cứng phẳng hơn và nhanh hơn một chuỗi else if dài.