การใช้คำสั่ง case ใน Verilog: พื้นฐาน ตัวอย่าง และข้อควรระวังในการออกแบบวงจรดิจิทัล

1. บทนำ

Verilog เป็นหนึ่งในภาษาบรรยายฮาร์ดแวร์ (HDL) ที่ถูกใช้อย่างแพร่หลายในการออกแบบวงจรดิจิทัล ภายในนั้น คำสั่ง case เป็นโครงสร้างที่สำคัญสำหรับ การเขียนเงื่อนไขแบบแยกแขนงอย่างมีประสิทธิภาพ โดยมักถูกใช้บ่อยใน การออกแบบการเปลี่ยนสถานะ (State Machine) และตัวเลือกหลายทาง (Multiplexer)

บทความนี้จะอธิบายรายละเอียดตั้งแต่พื้นฐานจนถึงการประยุกต์ใช้ case ของ Verilog พร้อมทั้งข้อควรระวังในการใช้งาน โดยจะมี ตัวอย่างโค้ดที่เข้าใจง่ายสำหรับผู้เริ่มต้น เพื่อให้คุณสามารถเรียนรู้ได้อย่างมั่นใจจนจบเนื้อหา。

2. พื้นฐานของคำสั่ง case ใน Verilog

case คืออะไร?

คำสั่ง case ใน Verilog คือโครงสร้างที่ใช้ ดำเนินการต่าง ๆ ตามเงื่อนไข (selector) ที่กำหนด ซึ่งทำงานคล้ายกับคำสั่ง switch-case ในภาษา C แต่มีความแตกต่างบางประการ。

โครงสร้างพื้นฐานเป็นดังนี้:

case (expression)
    เงื่อนไข1: คำสั่ง1;
    เงื่อนไข2: คำสั่ง2;
    เงื่อนไข3: คำสั่ง3;
    default: คำสั่ง4;  // กรณีที่ไม่ตรงกับเงื่อนไขใด ๆ
endcase

การใช้งานพื้นฐานของ case

ตัวอย่างนี้แสดง case แบบง่าย ที่กำหนดค่าสัญญาณ out ตามค่าของอินพุต 2 บิต sel

module case_example(input [1:0] sel, output reg [3:0] out);
    always @(*) begin
        case (sel)
            2'b00: out = 4'b0001;
            2'b01: out = 4'b0010;
            2'b10: out = 4'b0100;
            2'b11: out = 4'b1000;
            default: out = 4'b0000;  // ค่าเริ่มต้นเพื่อความปลอดภัย
        endcase
    end
endmodule

ในโค้ดนี้ out จะเปลี่ยนค่าตาม sel และเมื่อใส่ default จะช่วย ป้องกันปัญหาเมื่อได้รับค่าที่ไม่คาดคิด

ความแตกต่างระหว่าง case, casex, casez

Verilog มีรูปแบบขยายของคำสั่ง case ได้แก่ casex และ casez ซึ่งทำงานคล้ายตัวแทน (wildcard) ที่สามารถละเว้นบิตบางตำแหน่งได้。

รูปแบบคุณสมบัติ
caseต้องตรงกันทั้งหมด (ค่าเริ่มต้น)
casexละเว้นค่า X (ไม่ทราบค่า) และ Z (high-impedance)
casezละเว้นเฉพาะค่า Z

ตัวอย่างการใช้ casez:

casez (sel)
    2'b1?: out = 4'b1111; // ถ้าบิตบนสุดเป็น 1 จะตรงกัน
    2'b01: out = 4'b0001;
    default: out = 4'b0000;
endcase

ที่นี่ 1? หมายถึง “ตรงกันเมื่อบิตบนสุดเป็น 1 โดยไม่สนใจบิตล่าง”。

3. ตัวอย่างการใช้งาน case

การแยกเงื่อนไขพื้นฐาน

โค้ดนี้เป็นดีโคเดอร์ของ CPU ขนาดเล็ก ที่ทำงานต่างกันตามค่าอินพุต opcode ขนาด 8 บิต。

module decoder(input [7:0] opcode, output reg [3:0] control_signal);
    always @(*) begin
        case (opcode)
            8'h00: control_signal = 4'b0001; // NOP
            8'h01: control_signal = 4'b0010; // ADD
            8'h02: control_signal = 4'b0100; // SUB
            default: control_signal = 4'b0000; // คำสั่งไม่ถูกต้อง
        endcase
    end
endmodule

การใช้ใน State Machine

คำสั่ง case มักถูกนำมาใช้ใน Finite State Machine (FSM)

typedef enum reg [1:0] {IDLE, RUN, STOP} state_t;
state_t current_state, next_state;

always @(posedge clk) begin
    if (reset)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

always @(*) begin
    case (current_state)
        IDLE: next_state = RUN;
        RUN:  next_state = STOP;
        STOP: next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

โค้ดนี้เป็น FSM ที่มี 3 สถานะ การใช้ case จะช่วยให้โครงสร้างโค้ดอ่านง่ายและชัดเจน

4. ข้อควรระวังในการใช้ case

1. ควรมี default เสมอ

การครอบคลุมทุกเงื่อนไขเป็นสิ่งสำคัญ หากไม่มี default อาจทำให้เกิด latch โดยไม่ตั้งใจ เมื่อทำการสังเคราะห์ลง FPGA/ASIC。

2. ระวังการใช้ casex และ casez

คำสั่งเหล่านี้อาจทำให้ สัญญาณที่ไม่ต้องการถูกจับคู่ ดังนั้นควร ทดสอบการทำงานด้วยการจำลอง ทุกครั้ง。

3. อย่าใช้ case มากเกินไป

ถ้าเป็นเงื่อนไขขนาดเล็ก if-else อาจ เข้าใจง่ายกว่า ดังนั้นควรใช้ case เฉพาะเมื่อมีหลายทางเลือก。

5. สรุป

บทความนี้ได้อธิบายเกี่ยวกับคำสั่ง case ของ Verilog ได้แก่:

✅ โครงสร้างพื้นฐานและการทำงาน
✅ ความแตกต่างระหว่าง case, casex, casez
✅ ตัวอย่างการใช้งานจริง (การแยกเงื่อนไขและ FSM)
✅ ข้อควรระวังในการใช้งาน

การใช้คำสั่ง case อย่างถูกต้อง จะช่วย เพิ่มความชัดเจนของโค้ดและลดข้อผิดพลาดในการออกแบบ แนะนำให้นำไปประยุกต์ใช้กับการออกแบบวงจรของคุณ!

สิ่งที่ควรเรียนรู้ต่อไปใน Verilog

หลังจากเข้าใจ case แล้ว ขั้นต่อไปควรศึกษา “always” และ “วงจรผสม (Combinational) กับวงจรลำดับ (Sequential)” เพื่อเพิ่มความเข้าใจที่ลึกขึ้น。