Độ rộng và signedness: cùng bit, khác kết quả
Tiên quyết
- Module A2: Logic tổ hợp: Đang tải
- Module A4: Tuần tự & timing: Đang tải
- Module và instance: nối hai bộ cộng mà không nhầm cổng: Đang tải
Sau bài này bạn sẽ
- Dự đoán giá trị signed/unsigned từ cùng bit pattern
- Giải thích vì sao phép cộng mixed không giống phép cộng hai toán hạng signed
- Phân biệt kết quả tích đầy đủ với bốn bit thấp
- Phân biệt dịch phải logical/arithmetic và nối bit với cộng số
Bit pattern chưa đủ để biết giá trị
Bốn bit 1111 có thể được đọc là 15 nếu unsigned, hoặc -1 nếu signed theo biểu diễn bù hai. Trước khi cộng hoặc dịch, cần biết số bit và cách diễn giải dấu. Đọc tên tín hiệu hay chỉ nhìn dạng hex thường không đủ.
Trong bài, s là signed 4 bit, u là unsigned 4 bit. Hai tín hiệu cùng rộng nhưng được dùng trong những biểu thức khác nhau. Testbench lần lượt đưa mọi bit pattern vào cả hai đầu vào, để bạn quan sát chỗ trực giác “s đã khai báo signed nên mọi phép tính đều signed” bị sai.
Bài tập trung vào cộng, nhân, nối bit và dịch phải. Ví dụ chạy SystemVerilog với input 0/1. Những quy tắc đầy đủ về độ dài biểu thức, phép ép kiểu và context cần tiếp tục đối chiếu [1]; bài giữ trạng thái draft và không dùng simulator 2-state để kết luận về X/Z.
Đọc cả độ rộng và context
Đừng chỉ hỏi “cộng hai số 4 bit có bị tràn không?”. Hãy đọc cả biểu thức và nơi nhận kết quả. Trong chương trình của bài:
signed_totalnhận 8 bit, với cả hai toán hạng của phép cộng được diễn giải signed.mixed_totalcũng nhận 8 bit, nhưng một toán hạng là unsigned.productnhận tích 8 bit;product_lowchỉ nhận tích trong context 4 bit.
Trong những biểu thức này, độ rộng nơi nhận ảnh hưởng tới việc tính toán. Không áp dụng một mẹo chung như “luôn tính xong ở 4 bit rồi mới mở rộng”. Ngược lại, nếu kết quả đã đi qua một tín hiệu chỉ giữ 4 bit, mở rộng tín hiệu đó sau này không thể khôi phục những bit đã mất.
Thí nghiệm vét cạn 256 cặp đầu vào
Hai đầu vào 4 bit có 16*16=256 cặp bit pattern. Kỳ vọng tính tay trước khi chạy:
s=1111(-1),u=0001(1): phép cộng hai toán hạng signed trong TB cho 0; phép cộng mixed trong context 8 bit cho 16.smở rộng signed thành 8 bit vẫn mang giá trị -1.s=1000(-8): dịch phải logical một bit cho0100(hex 4), arithmetic cho1100(hex c).- Cả hai đầu vào có bit pattern
1111: tổng mixed là 30; sau khi cả hai được diễn giải signed, tổng là -2. Phép nhân unsigned là15*15=225; bốn bit thấp của tích là 1. Nối hai nibble cho hexff.
TB không dùng chính bit pattern hex làm “đáp án tự nhận đúng”. Nó giải mã signed bằng công thức số nguyên (i>=8 thì giá trị là i-16), kiểm tổng/tích bằng số nguyên, và kiểm bit dịch bằng phép tính riêng. Sai bất kỳ output nào thì $fatal, không in dòng tổng kết 256 case.
Lint của width_lab.sv có warning WIDTHEXPAND dự kiến, ở hai phép cộng nhận 8 bit từ toán hạng 4 bit và phép sign-extend. Bài giữ các phép mở rộng ngầm này để học hành vi, kiểm chính xác tập warning trong metadata; không tắt lint hoặc gọi đây là code zero-warning. Trong code sản phẩm, cần quyết định rõ ý định và xử lý warning tương ứng. TB được elaborate/mô phỏng cùng RTL; synthesis chỉ kiểm không có FF, không đo timing hoặc số cổng.
Mixed signed/unsigned và ép kiểu
Hai dòng trong width_lab dùng cùng input nhưng khác ý nghĩa:
assign signed_total = s + $signed(u);
assign mixed_total = s + u;
Với s=1111, u=0001, biểu thức đầu có hai toán hạng signed: -1 và 1, kết quả 0. Ở biểu thức mixed, u unsigned làm thay đổi cách diễn giải và mở rộng trong phép cộng của ví dụ; bốn bit 1111 được dùng như 15, cộng 1 thành 16 trong context 8 bit.
$signed(u) ở đây không tự thêm bit để giữ mọi giá trị unsigned của u. Nó diễn giải cùng bốn bit theo signed. Vì vậy khi u=1111, $signed(u) là -1, không phải signed +15. Testbench kiểm trường hợp đó: -1 cộng -1 cho -2 ở output signed 8 bit.
Muốn biểu diễn unsigned 4 bit từ 0 đến 15 bằng một số signed không âm, bạn cần đủ bit và quy tắc chuyển đổi thích hợp; đừng chỉ đặt $signed quanh nibble rồi mong giá trị 15 còn nguyên. Đó là câu hỏi về miền giá trị, không chỉ cú pháp.
Bản giữ giá trị số của cả hai input trong example dùng value_total = extended_s + $signed({4'b0000, u}). extended_s đã sign-extend s lên signed 8 bit; {4'b0000, u} giữ u trong miền 0–15 rồi được diễn giải signed 8 bit không âm. Cả hai phía đều đủ rộng và signed. Với s=1111 (-1), u=1111 (15), phép tính này cho 14, không phải -2 hoặc 30. TB kiểm value_total với giá trị số nguyên signed của s cộng giá trị unsigned của u trên đủ 256 cặp, và thực sự in FIX RESULT widened=14.
Ở phép gán extended_s = s, sign extension biến signed 4 bit 1111 thành signed 8 bit 11111111, vẫn là -1. Zero extension của cùng nibble sẽ tạo 00001111, tức 15. Không đánh đồng “thêm số bit” với “thêm các bit 0”. [3]
Tích 225 hay chỉ còn 1?
Bài ép s sang unsigned trong phép nhân, để cả hai toán hạng có miền 0–15:
assign product = $unsigned(s) * u;
assign product_low = $unsigned(s) * u;
Hai vế trái khác độ rộng. Với cả hai nibble bằng 1111, tích unsigned là 225, hex e1, nhị phân 11100001. Output product 8 bit giữ được e1; product_low chỉ giữ bốn bit thấp 0001, bằng 1.
Một tín hiệu khác nhận lại giá trị product_low sẽ chỉ có thông tin về 1; không thể biết trước đó là 15*15 hay 1*1, vì cả hai đều có bốn bit thấp bằng 1. Muốn giữ tích đầy đủ, phải giữ đủ thông tin ngay tại phép toán và đường đi kết quả.
Đôi khi chỉ cần phần thấp, ví dụ phép toán modulo theo một yêu cầu cụ thể. Khi đó truncation là chủ ý cần ghi rõ. Nhưng nếu yêu cầu là tích đầy đủ, đổi expected thành 1 để test xanh sẽ che lỗi chức năng. Trong bài, cả hai output được đặt tên và kiểm tra theo hai yêu cầu khác nhau.
Dịch phải: điền 0 hay điền bit dấu?
Hai output của ví dụ là:
assign logical_shift = s >> 1;
assign arithmetic_shift = s >>> 1;
Với signed 4 bit s=1000 (-8), logical right shift điền 0 ở đầu trái, cho 0100 (4). Arithmetic right shift dùng bit dấu, cho 1100, là bit pattern của -4 nếu diễn giải signed 4 bit. Output TB in dạng hex nên thấy c.
Dấu >>> không phải cách tự biến mọi operand thành signed; cần đọc kiểu của operand bên trái. Ví dụ đang kiểm s đã khai báo signed. Khi ghép concatenation hoặc ép kiểu khác vào biểu thức, cần xem lại signedness; không suy ra từ hình thức >>> là đủ.
Nối bit không phải cộng số
pair = {s, u} đặt bốn bit của s ở nửa cao, bốn bit của u ở nửa thấp. Với s=u=1111, kết quả là 11111111 (hex ff); output pair được khai báo unsigned 8 bit nên mang giá trị 255. Đây không phải phép cộng 15+15=30 hoặc -1+-1=-2.
Trong TB, phép nối được kiểm bằng số nguyên i*16+j cho toàn bộ 256 cặp, tách khỏi phép cộng và phép nhân. Đừng chỉ so “cùng ra các bit 1” ở một vector rồi coi các toán tử có thể thay thế nhau.
Các quy tắc đầy đủ về kiểu của concatenation và context cần đối chiếu [4]. Bằng chứng ở đây là đúng các phép nối và output đã khai báo trong chương trình, không phải toàn bộ tổ hợp ép kiểu có thể viết.
Checklist trước khi tin một biểu thức
- Signedness của cả hai phía: một input signed không bảo đảm mọi biểu thức chứa nó đều signed.
- Miền giá trị sau cast: reinterpret bốn bit unsigned 15 thành signed bốn bit cho -1; không có bit mới tự xuất hiện.
- Độ rộng tại nơi tính và nơi nhận: giữ full product khác với lấy low nibble rồi mở rộng.
- Kiểu của operand trước khi dịch:
>>/>>>và signedness phải được đọc cùng nhau. - Ý định của
{}: nối trường bit theo vị trí, không cộng các giá trị. - Warning có lý do: các warning dự kiến trong bài là phần của thí nghiệm; warning mới hoặc mismatch vẫn phải làm kiểm tra thất bại. Không tắt toàn bộ lint để bỏ qua.
Tự kiểm tra
Tóm tắt để review RTL
Luôn hỏi ba câu: các bit có độ rộng bao nhiêu, được diễn giải signed hay unsigned, và biểu thức nhận context nào? Sau đó tính vài trường hợp biên bằng tay, chạy self-checking testbench và đọc warning.
Bài kiểm input 0/1 bằng simulator 2-state và một số context cụ thể. Chưa dùng các kết quả này để khẳng định hành vi X/Z, toán tử so sánh 4-state, mọi cast/literal hoặc độ trễ phần cứng. Các phần ngôn ngữ còn cần đối chiếu LRM vẫn giữ nhãn chưa xác minh.
Nguồn và mức độ đối chiếu
- IEEE 1800-2023 SystemVerilog LRM, LRM: expressions. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, Kết quả chạy và phạm vi kiểm tra. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, Thử nghiệm signed và extension. Trích dẫn; nguồn đã đọc toàn văn.
- IEEE 1800-2023 SystemVerilog LRM, LRM: concatenation và expression types. Chưa đối chiếu với văn bản nguồn (chưa mở).
- IEEE 1800-2023 SystemVerilog LRM, Expression bit lengths, signed expressions, concatenation and shift operators. Chưa đối chiếu với văn bản nguồn (chưa mở).
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: MIX RESULT. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: WIDTH RESULT signed=-2. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: WIDTH RESULT product=225 low=1. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: SHIFT RESULT. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: WIDTH RESULT pair=ff; tb_width.sv pair self-check. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, verification.json: FIX RESULT widened=14. Trích dẫn; nguồn đã đọc toàn văn.
- Thử nghiệm width/sign: content/code/ex.a6.operators/verification.json, content/code/ex.a6.operators/verification.json. Trích dẫn; nguồn đã đọc toàn văn.