- 1 1. Verilog assign คืออะไร?【อธิบายสำหรับผู้เริ่มต้น】
- 2 2. พื้นฐานการเขียน assign ใน Verilog
- 2.1 โครงสร้างพื้นฐานของ assign
- 2.2 ตัวอย่างการใช้งานที่พบบ่อย①: การดำเนินการลอจิกพื้นฐาน
- 2.3 ตัวอย่างการใช้งานที่พบบ่อย②: การจัดการบิต (Bitwise)
- 2.4 ความหมายของ “Continuous Assignment” ใน assign
- 2.5 การใช้ assign แบบกำหนดเวลาหน่วง (Delay)
- 2.6 ตัวอย่าง assign ที่ใช้เงื่อนไข (Conditional)
- 2.7 สรุป: เรียนรู้วิธีเขียน assign
- 3 3. ความสัมพันธ์ระหว่าง assign และ wire|ตั้งแต่การประกาศจนถึงการใช้งาน
- 4 4. ความแตกต่างระหว่าง assign และ always【จุดที่ผู้เริ่มต้นมักสับสน】
- 5 5. ตัวอย่างวงจรคอมบิเนชันด้วย assign【พร้อมภาพประกอบ】
- 6 6. ข้อควรระวังและข้อผิดพลาดที่พบบ่อยเมื่อใช้ assign
- 6.1 หลุมพรางที่ผู้เริ่มต้นมักเจอเมื่อใช้ assign
- 6.2 1. ใช้ assign กับ reg แล้วเกิด error
- 6.3 2. เขียน assign ซ้ำกับสัญญาณเดียวกัน
- 6.4 3. พยายามตั้งค่าเริ่มต้นด้วย assign
- 6.5 4. ลืมประกาศสัญญาณก่อนใช้ assign
- 6.6 5. ใช้การดำเนินการที่ไม่เหมาะสม
- 6.7 6. ใช้ ternary operator ซ้อนกันมากเกินไป
- 6.8 เทคนิคการดีบัก assign
- 6.9 สรุป: assign ใช้ง่าย แต่ต้องระวัง
- 7 7. คำถามที่พบบ่อย (FAQ)
- 7.1 Q1: สำหรับผู้เริ่มต้น ควรใช้ assign หรือ always ก่อน?
- 7.2 Q2: ถ้าอยากใช้ assign กับ reg ทำได้ไหม?
- 7.3 Q3: ใช้ assign หลายครั้งกับสัญญาณเดียวกันได้ไหม?
- 7.4 Q4: การใช้ delay (#) ใน assign มีประโยชน์ไหม?
- 7.5 Q5: ถ้าอยากเขียนเงื่อนไขด้วย assign ต้องทำอย่างไร?
- 7.6 Q6: ทำไม assign ไม่เปลี่ยนค่าเวลา test?
- 7.7 Q7: โค้ดที่เขียนด้วย assign สามารถสังเคราะห์ได้หรือไม่?
- 8 8. พจนานุกรมคำศัพท์พื้นฐานสำหรับผู้เริ่มต้น Verilog
- 8.1 wire (ไวร์)
- 8.2 reg (เรจิสเตอร์)
- 8.3 assign (อะแซน์)
- 8.4 always (ออลเวย์)
- 8.5 วงจรคอมบิเนชัน (Combinational Circuit)
- 8.6 วงจรซีเควนเชียล (Sequential Circuit)
- 8.7 ternary operator (ตัวดำเนินการเงื่อนไข)
- 8.8 module (โมดูล)
- 8.9 initial (อินิเทียล)
- 8.10 non-blocking assignment (<=)
- 8.11 สรุป: เข้าใจคำศัพท์คือก้าวแรกในการเรียน Verilog
- 9 9. สรุป|การใช้งาน assign อย่างเชี่ยวชาญ
1. Verilog assign คืออะไร?【อธิบายสำหรับผู้เริ่มต้น】
Verilog HDL คืออะไร?
Verilog HDL (Hardware Description Language) คือภาษาบรรยายฮาร์ดแวร์ที่ใช้ในการอธิบายวงจรดิจิทัล แตกต่างจากภาษาการเขียนโปรแกรมสำหรับซอฟต์แวร์ เพราะ Verilog ใช้ในการอธิบายโครงสร้างและการทำงานของฮาร์ดแวร์ (วงจรลอจิก) และสามารถแปลงเป็นวงจรจริง เช่น FPGA หรือ ASIC ได้ผ่านการจำลองและการสังเคราะห์
หนึ่งในคำสั่งที่ถูกใช้บ่อยใน Verilog คือคำสั่ง assign
โดยเฉพาะเมื่อเขียน วงจรคอมบิเนชัน (Combinational Circuit) ซึ่งเป็นโครงสร้างพื้นฐานสำคัญ
หน้าที่ของ assign คืออะไร?
assign
คือคำสั่งที่ใช้สำหรับการกำหนดค่าแบบต่อเนื่อง (continuous assignment) ให้กับสัญญาณชนิด wire การกำหนดค่าแบบต่อเนื่องหมายความว่า เมื่ออินพุตเปลี่ยนแปลง เอาต์พุตก็จะเปลี่ยนทันที
ตัวอย่างเช่น หากต้องการคำนวณ AND ระหว่างสัญญาณสองตัวแล้วส่งออก สามารถเขียนได้ดังนี้:
assign out = in1 & in2;
เพียงบรรทัดเดียวก็สามารถทำให้ “out แสดงผลเป็น in1 AND in2 ตลอดเวลา” ได้ assign
จึงทำหน้าที่เหมือนการกำหนดการเชื่อมต่อของวงจรฮาร์ดแวร์โดยตรง
assign ในวงจรคอมบิเนชัน
วงจรดิจิทัลแบ่งได้เป็น วงจรคอมบิเนชัน และ วงจรซีเควนเชียล
- วงจรคอมบิเนชัน: เอาต์พุตเปลี่ยนทันทีตามอินพุต (เช่น ตัวบวก, เกตลอจิก)
- วงจรซีเควนเชียล: ใช้นาฬิกาหรือองค์ประกอบหน่วยความจำเพื่อเก็บสถานะ (เช่น ฟลิปฟลอป, เคาน์เตอร์)
assign
ถูกใช้ในวงจรประเภทแรกคือ คอมบิเนชัน เพราะต้องอัปเดตเอาต์พุตทันทีตามอินพุต
ทำไม assign ถึงสำคัญสำหรับผู้เริ่มต้น?
ในขั้นเริ่มต้นของการเรียนรู้ Verilog สิ่งสำคัญที่สุดคือการเข้าใจวงจรคอมบิเนชัน และ assign
เป็นเครื่องมือหลักที่ใช้ในการอธิบาย ตั้งแต่วงจรลอจิกง่าย ๆ เงื่อนไข การบวก ไปจนถึงตัวเปรียบเทียบ ล้วนสามารถเขียนได้ด้วย assign
อย่างกระชับ
นอกจากนี้ assign
ยังช่วยให้ผู้เรียนเข้าใจการไหลของสัญญาณในระดับฮาร์ดแวร์ ซึ่งเป็นพื้นฐานสำคัญสำหรับการเขียนวงจรซีเควนเชียลที่ซับซ้อนในอนาคต
สรุป: เข้าใจพื้นฐานของ assign
assign
ใน Verilog คือคำสั่งพื้นฐานที่สุดในการเขียนวงจรคอมบิเนชัน เพราะช่วยอธิบายการเชื่อมต่อและการดำเนินการลอจิกได้อย่างง่ายดาย ดังนั้นผู้เริ่มต้นควรทำความเข้าใจและใช้งานได้อย่างคล่องแคล่วตั้งแต่แรกเริ่ม
2. พื้นฐานการเขียน assign ใน Verilog
โครงสร้างพื้นฐานของ assign
โครงสร้างของ assign
ใน Verilog มีความเรียบง่าย โดยใช้สำหรับกำหนดค่าให้กับสัญญาณชนิด wire ด้วยนิพจน์ทางลอจิกหรือการคำนวณ
assign สัญญาณออก = นิพจน์;
นิพจน์สามารถเป็นการอ้างอิงสัญญาณอื่น ๆ การดำเนินการลอจิก หรือการดำเนินการบิต ทั้งนี้ assign
ใช้ได้เฉพาะกับwire ไม่สามารถใช้กับ reg
ตัวอย่างการใช้งานที่พบบ่อย①: การดำเนินการลอจิกพื้นฐาน
รูปแบบที่ใช้บ่อยที่สุดคือการใช้เกตลอจิก ตัวอย่างเช่น AND, OR, XOR ซึ่งสามารถเขียนด้วย assign ได้ดังนี้:
assign and_out = a & b; // AND gate
assign or_out = a | b; // OR gate
assign xor_out = a ^ b; // XOR gate
ด้วยการใช้โอเปอเรเตอร์ สามารถรวมสัญญาณหลายตัวแล้วกำหนดค่าเอาต์พุตอย่างต่อเนื่องได้
ตัวอย่างการใช้งานที่พบบ่อย②: การจัดการบิต (Bitwise)
assign รองรับการดำเนินการในระดับบิต เช่น การดึงบางบิตออกมา หรือรวมหลายบิตเข้าด้วยกัน
assign upper_4bits = data[7:4]; // ดึง 4 บิตบนจาก 8 บิต
assign lower_4bits = data[3:0]; // ดึง 4 บิตล่าง
assign combined = {data1[3:0], data2[3:0]}; // รวม 4 บิตจาก 2 ตัว ให้เป็น 8 บิต
ดังนั้น assign จึงเหมาะสำหรับการดึงและรวมข้อมูลบางส่วนใหม่อย่างมีประสิทธิภาพ
ความหมายของ “Continuous Assignment” ใน assign
ใน Verilog การใช้ assign
เรียกว่า continuous assignment (การกำหนดค่าแบบต่อเนื่อง) หมายความว่าเมื่ออินพุตเปลี่ยน เอาต์พุตจะอัปเดตทันที
แตกต่างจาก “การกำหนดค่า” ในซอฟต์แวร์ เพราะในฮาร์ดแวร์การเชื่อมต่อของสัญญาณจะคงอยู่เสมอ ทำให้ assign สามารถจำลองพฤติกรรมการส่งสัญญาณจริงของฮาร์ดแวร์ได้
การใช้ assign แบบกำหนดเวลาหน่วง (Delay)
ใน Verilog สามารถเพิ่มdelay (เวลาหน่วง) ใน assign ได้ โดยใช้สำหรับการจำลอง (simulation) เป็นหลัก ไม่ค่อยมีผลต่อการสังเคราะห์จริง
assign #5 out = a & b; // ส่งผลลัพธ์ AND ของ a และ b ไปที่ out โดยหน่วง 5 time units
“#5” หมายถึงเวลาหน่วงตามหน่วยเวลา (time unit) ซึ่งมีประโยชน์ในการตรวจสอบการทำงานเชิงจำลอง แต่จะถูกละเว้นในการสังเคราะห์เป็นวงจรจริง
ตัวอย่าง assign ที่ใช้เงื่อนไข (Conditional)
assign สามารถใช้ร่วมกับตัวดำเนินการแบบมีเงื่อนไข (ternary operator) เพื่อให้ทำงานคล้าย if ได้
assign out = sel ? data1 : data2;
ความหมายคือ “ถ้า sel = 1 ให้ใช้ data1 ไม่เช่นนั้นใช้ data2” เหมาะสำหรับการเขียน MUX หรือการเลือกสัญญาณ
สรุป: เรียนรู้วิธีเขียน assign
assign ใน Verilog เป็นคำสั่งที่เรียบง่ายแต่ทรงพลัง รองรับการดำเนินการหลากหลาย เช่น ลอจิก, การจัดการบิต, เงื่อนไข และการหน่วงเวลา
สำหรับผู้เริ่มต้น ควรทำความเข้าใจโครงสร้างพื้นฐานของ assign
เพื่อเขียนวงจรคอมบิเนชันได้อย่างอิสระ ถือเป็นก้าวแรกที่สำคัญในการเรียนรู้ Verilog
3. ความสัมพันธ์ระหว่าง assign และ wire|ตั้งแต่การประกาศจนถึงการใช้งาน
ความสัมพันธ์พื้นฐานระหว่าง assign และ wire
หนึ่งในกฎสำคัญของ Verilog คือ assign
ใช้ได้เฉพาะกับสัญญาณชนิด wire
เท่านั้น หากไม่เข้าใจกฎนี้ เวลาเขียนโค้ดมักจะเกิดข้อผิดพลาดทางไวยากรณ์
การกำหนดค่าด้วย assign
เรียกว่า continuous assignment (การกำหนดค่าแบบต่อเนื่อง) และสามารถใช้ได้เฉพาะกับสัญญาณที่ถูกประกาศเป็น wire
เท่านั้น
wire คืออะไร? ── ภาพจำลองของ “สายไฟ”
ชนิดข้อมูล wire
ใน Verilog เปรียบเสมือน การจำลองสายไฟจริงในวงจรลอจิก ทำหน้าที่เป็นตัวกลางในการส่งค่าที่ได้รับจากแหล่งอื่น (เช่น assign หรือเอาต์พุตของโมดูล)
ดังนั้น wire
จะไม่เก็บค่าเอง แต่จะส่งต่อค่าที่ได้รับจากแหล่งกำเนิด
wire a, b, out;
assign out = a & b; // เอาต์พุต out แสดงผล a AND b ตลอดเวลา
ในตัวอย่างนี้ out
ต้องเป็นชนิด wire
หากกำหนดเป็น reg
จะเกิดข้อผิดพลาดทันที
ความแตกต่างกับ reg: ทำไม assign ใช้ไม่ได้?
reg
ถูกใช้ในวงจรซีเควนเชียล เพื่อเก็บค่าในช่วงเวลาหนึ่ง และจะถูกกำหนดค่าภายใน always
block เท่านั้น ดังนั้น assign
ไม่สามารถใช้กับ reg
ได้
ตัวอย่างโค้ดที่ผิด:
reg out;
assign out = a & b; // ❌ ผิด! assign ใช้กับ reg ไม่ได้
สรุปคือ assign
ใช้กับ wire
ส่วน always
ใช้กับ reg
การประกาศ wire แบบหลายบิต (bus)
wire
รองรับทั้งการประกาศแบบ 1 บิตและหลายบิต (bus)
wire [3:0] a, b;
wire [3:0] out;
assign out = a & b; // การ AND ทีละบิต (bitwise AND)
หากเป็นหลายบิต ต้องกำหนดขนาดบิตอย่างชัดเจนตอนประกาศ
wire ในการเชื่อมต่อระหว่างโมดูล
นอกจากใช้กับ assign แล้ว wire
ยังใช้เป็นตัวกลางเชื่อมต่อสัญญาณระหว่างโมดูล
wire result;
module1 u1 (.a(a), .b(b), .out(result));
module2 u2 (.in(result), .y(y));
ดังนั้น wire
จึงเป็นองค์ประกอบพื้นฐานที่ใช้ทั่วไปใน Verilog
สรุป: เข้าใจ wire เพื่อใช้ assign อย่างถูกต้อง
เพื่อใช้ assign
อย่างถูกต้อง จำเป็นต้องเข้าใจ wire
เนื่องจากเป็น “สายไฟ” ที่รับค่ามาจากสัญญาณอื่น ๆ และ assign ทำหน้าที่กำหนดการเชื่อมต่อ
ในทางตรงกันข้าม reg
จะถูกกำหนดค่าภายใน always
เท่านั้น การเข้าใจการใช้งานทั้งสองแบบจะช่วยให้เขียนโค้ด Verilog ได้ถูกต้องและมีประสิทธิภาพ
4. ความแตกต่างระหว่าง assign และ always【จุดที่ผู้เริ่มต้นมักสับสน】
ทำไม assign และ always ถึงทำให้ผู้เริ่มต้นสับสน?
สำหรับผู้ที่เริ่มเรียน Verilog ใหม่ ๆ มักสับสนระหว่าง assign
และ always
เพราะทั้งคู่ใช้ในการกำหนดค่าให้กับสัญญาณ แต่มีจุดประสงค์และชนิดข้อมูลที่รองรับแตกต่างกัน
ในส่วนนี้ เราจะอธิบายความแตกต่างหลัก และแนวทางในการเลือกใช้อย่างถูกต้อง
คุณสมบัติและการใช้งานของ assign
ก่อนอื่น มาทบทวนลักษณะของ assign
กันก่อน
- การใช้งาน: ใช้สำหรับวงจรคอมบิเนชัน
- ชนิดสัญญาณ: ใช้กับ
wire
เท่านั้น - วิธีการกำหนดค่า: continuous assignment (กำหนดค่าอย่างต่อเนื่อง)
- คีย์เวิร์ด:
assign
ตัวอย่าง: AND gate แบบ 2 อินพุต (assign)
wire a, b;
wire out;
assign out = a & b;
อินพุตเปลี่ยนเมื่อใด เอาต์พุตจะเปลี่ยนทันที ── นี่คือลักษณะของวงจรคอมบิเนชัน
คุณสมบัติและการใช้งานของ always
always
มีความยืดหยุ่นกว่า และใช้สำหรับวงจรซีเควนเชียล หรือการควบคุมแบบมีเงื่อนไข โดยมักจะสัมพันธ์กับสัญญาณนาฬิกา (clock)
- การใช้งาน: วงจรซีเควนเชียลหรือการควบคุมที่ซับซ้อน
- ชนิดสัญญาณ: ใช้กับ
reg
- วิธีการกำหนดค่า: กำหนดตามเงื่อนไข (triggered assignment)
- คีย์เวิร์ด:
always
ตัวอย่าง: การประมวลผลแบบซิงโครไนซ์กับ clock (always)
reg out;
always @(posedge clk) begin
out <= a & b;
end
ในกรณีนี้ ค่า a & b
จะถูกเก็บไว้ใน out
ทุกครั้งที่ขอบขาขึ้นของสัญญาณ clock มาถึง ── ใช้เมื่อมีการพิจารณาเรื่องเวลา
การเปรียบเทียบระหว่าง wire และ reg
คุณสมบัติ | wire | reg |
---|---|---|
พื้นที่ใช้งาน | assign | always block |
เก็บค่าได้หรือไม่ | ไม่ได้ (เพียงส่งต่อ) | ได้ (เก็บชั่วคราว) |
กำหนดค่าเริ่มต้น | ไม่ได้ | ทำได้ (เฉพาะ simulation) |
รูปแบบการกำหนดค่า | continuous assignment | blocking หรือ non-blocking assignment |
ดังนั้น assign
และ always
มีความสัมพันธ์กับชนิดสัญญาณโดยตรง และควรเรียนรู้เป็น “คู่” เพื่อไม่สับสน
เลือกใช้ assign หรือ always อย่างไร?
วัตถุประสงค์ | ควรใช้ | ชนิดสัญญาณ |
---|---|---|
การดำเนินการลอจิก (คอมบิเนชัน) | assign | wire |
การเก็บค่าซิงโครไนซ์ด้วย clock (ซีเควนเชียล) | always | reg |
การเปลี่ยนค่าแบบมีเงื่อนไข | always | reg |
เพียงการเชื่อมต่อสัญญาณ | assign | wire |
ตัวอย่าง: ต้องการใช้ if ⇒ ควรใช้ always
reg y;
always @(a or b) begin
if (a == 1) y = b;
else y = 0;
end
โค้ดลักษณะนี้ไม่สามารถทำด้วย assign ได้ หากมีเงื่อนไขหรือการไหลของโปรแกรม ควรใช้ always
assign และ always ใช้พร้อมกันได้หรือไม่?
ไม่สามารถใช้ assign และ always กำหนดค่าสัญญาณเดียวกันได้ หากทำเช่นนั้นจะเกิด “การขับเคลื่อนหลายแหล่ง” (multiple drivers) ทำให้เกิดข้อผิดพลาด
โค้ดผิดตัวอย่าง:
assign y = a & b;
always @(posedge clk)
y <= a | b; // ❌ y ถูกกำหนดทั้งใน assign และ always
สรุป: แยกความแตกต่างของ assign และ always ให้ชัดเจน
สรุปการเลือกใช้:
- อินพุตและเอาต์พุตเชื่อมตรงกันเสมอ → ใช้ assign (wire)
- ต้องมีการเก็บค่า หรือขึ้นกับเวลา → ใช้ always (reg)
หากเข้าใจกฎนี้ จะช่วยให้ผู้เริ่มต้นไม่สับสนระหว่าง assign และ always
5. ตัวอย่างวงจรคอมบิเนชันด้วย assign【พร้อมภาพประกอบ】
วงจรคอมบิเนชันคืออะไร?
เริ่มจากพื้นฐานก่อน วงจรคอมบิเนชัน (Combinational Circuit) หมายถึง เอาต์พุตเปลี่ยนแปลงทันทีตามค่าของอินพุตปัจจุบัน โดยไม่มีการเก็บสถานะในอดีต ดังนั้น ผลลัพธ์ขึ้นอยู่กับอินพุต ณ เวลานั้นเท่านั้น
ใน Verilog คำสั่งที่เหมาะที่สุดสำหรับการเขียนวงจรประเภทนี้คือ assign
การสร้างเกตลอจิกพื้นฐาน (AND, OR, XOR)
ตัวอย่างโมดูลที่ใช้ assign
เขียนเกตลอจิกพื้นฐาน:
module logic_gates(
input wire a,
input wire b,
output wire and_out,
output wire or_out,
output wire xor_out
);
assign and_out = a & b;
assign or_out = a | b;
assign xor_out = a ^ b;
endmodule
โมดูลนี้จะรับอินพุต a
และ b
แล้วส่งออกค่า AND, OR, XOR โดยใช้เพียง assign
── ไม่จำเป็นต้องมี clock หรือเงื่อนไขใด ๆ
การสร้าง Half Adder (ตัวบวกครึ่ง)
Half Adder เป็นวงจรพื้นฐานที่ใช้ในการสอนบ่อยที่สุด ทำหน้าที่บวกเลขฐานสอง 2 บิต แล้วส่งออกผลรวมและบิตพกพา
สมการลอจิก
- ผลรวม (sum) = A ⊕ B (XOR)
- ตัวพกพา (carry) = A・B (AND)
ตัวอย่างการเขียน Verilog
module half_adder(
input wire a,
input wire b,
output wire sum,
output wire carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
Half Adder สามารถเขียนได้ง่าย ๆ ด้วย เพียง 2 บรรทัด assign เหมาะกับผู้เริ่มต้นฝึกฝน
การสร้าง Full Adder (ตัวบวกเต็ม)
Full Adder ทำการบวกเลขฐานสอง 3 บิต (A, B, Cin) แล้วส่งออกผลรวม (Sum) และตัวพกพา (Carry)
สมการลอจิก
- ผลรวม (sum) = A ⊕ B ⊕ Cin
- ตัวพกพา (carry) = (A・B) + (Cin・(A ⊕ B))
ตัวอย่างการเขียน Verilog
module full_adder(
input wire a,
input wire b,
input wire cin,
output wire sum,
output wire cout
);
wire ab_xor;
assign ab_xor = a ^ b;
assign sum = ab_xor ^ cin;
assign cout = (a & b) | (cin & ab_xor);
endmodule
ตัวอย่างนี้ใช้สัญญาณกลาง ab_xor
เพื่อเพิ่มความอ่านง่าย ── แสดงให้เห็นว่า assign สามารถจัดการหลายขั้นตอนของการคำนวณได้อย่างยืดหยุ่น
การสร้างวงจรเลือกสัญญาณ (MUX)
อีกตัวอย่างที่พบบ่อยคือ Multiplexer (MUX) ซึ่งทำหน้าที่เลือกเอาต์พุตตามเงื่อนไข
module mux2to1(
input wire a,
input wire b,
input wire sel,
output wire y
);
assign y = sel ? b : a;
endmodule
ถ้า sel = 1
จะเลือก b
ถ้า sel = 0
จะเลือก a
── เขียนได้ง่าย ๆ ด้วย ternary operator
ข้อควรระวังในการใช้งาน
- สัญญาณต้องประกาศเป็น wire: assign ใช้กับ reg ไม่ได้
- เอาต์พุตหลายตัวควรเขียน assign แยกกัน: เพื่อความอ่านง่าย
- ใช้สัญญาณกลาง (wire) เมื่อสมการซับซ้อน: เพื่อเพิ่มความชัดเจน
สรุป: วงจรพื้นฐานเขียนได้ด้วย assign
ดังที่เห็น วงจรคอมบิเนชันพื้นฐานทั้งหมดสามารถเขียนได้ด้วย assign ไม่ว่าจะเป็นเกตลอจิก, ตัวบวก หรือ MUX
สำหรับผู้เริ่มต้น แนะนำให้ฝึกเขียนวงจรเล็ก ๆ ด้วย assign เพื่อให้เข้าใจการไหลของสัญญาณและโครงสร้างวงจรได้อย่างเป็นธรรมชาติ
6. ข้อควรระวังและข้อผิดพลาดที่พบบ่อยเมื่อใช้ assign
หลุมพรางที่ผู้เริ่มต้นมักเจอเมื่อใช้ assign
assign
เป็นหนึ่งในคำสั่งที่เรียบง่ายที่สุดใน Verilog แต่ความง่ายนี้เองที่ทำให้หลายคนใช้โดยไม่เข้าใจอย่างถูกต้อง และนำไปสู่ข้อผิดพลาดหรือพฤติกรรมที่ไม่คาดคิด
ในส่วนนี้ เราจะอธิบายข้อผิดพลาดที่พบบ่อย และแนวทางแก้ไข
1. ใช้ assign กับ reg แล้วเกิด error
✅ ตัวอย่างโค้ดผิดบ่อย:
reg out;
assign out = a & b; // ❌ Error! reg ไม่สามารถใช้ assign
💡 สาเหตุและวิธีแก้:
assign
ใช้ได้เฉพาะกับ wire เท่านั้น ส่วน reg
ต้องใช้ใน always
block
วิธีแก้: เปลี่ยน out ให้เป็น wire
หรือย้ายไปเขียนใน always
2. เขียน assign ซ้ำกับสัญญาณเดียวกัน
✅ ตัวอย่างที่ผิด:
assign y = a & b;
assign y = a | b; // ❌ ขัดแย้งกัน
💡 สาเหตุและวิธีแก้:
สัญญาณหนึ่งตัวสามารถมีแหล่งขับ (driver) ได้เพียง 1 แหล่งเท่านั้น หากมีหลาย assign จะเกิด error หรือ undefined behavior
วิธีแก้: ใช้ always
block หรือแทรกสัญญาณกลางแทน
3. พยายามตั้งค่าเริ่มต้นด้วย assign
✅ ตัวอย่างที่ผิดพลาด:
assign a = 1'b0; // ❌ ไม่ใช่ค่าเริ่มต้น แต่หมายถึง a = 0 ตลอดเวลา
💡 สาเหตุและวิธีแก้:
assign
หมายถึงการขับค่าอย่างต่อเนื่อง ไม่ใช่การกำหนดค่าเพียงครั้งเดียว
วิธีแก้: ใช้ initial
block สำหรับ simulation หรือใช้วงจร reset ในการออกแบบจริง
4. ลืมประกาศสัญญาณก่อนใช้ assign
✅ ตัวอย่างผิด:
assign result = a & b; // ❌ result ไม่ได้ประกาศ
💡 วิธีแก้:
ต้องประกาศสัญญาณทุกตัวเป็น wire
หรือ reg
อย่างชัดเจนก่อนใช้งาน
5. ใช้การดำเนินการที่ไม่เหมาะสม
บางการดำเนินการเช่น การหาร (/
) หรือการหารเอาเศษ (%
) อาจทำให้เครื่องมือสังเคราะห์ FPGA ไม่รองรับ
assign out = a / 3; // ⚠️ บางกรณีอาจสังเคราะห์ไม่ได้
วิธีแก้: ตรวจสอบว่าโอเปอเรเตอร์รองรับการสังเคราะห์จริงหรือไม่ ถ้าไม่ ควรเขียนใหม่ด้วยลอจิก
6. ใช้ ternary operator ซ้อนกันมากเกินไป
assign out = sel1 ? a : (sel2 ? b : (sel3 ? c : d)); // ❌ อ่านยากมาก
วิธีแก้:
- ใช้สัญญาณกลาง (wire) เพื่อแยกเป็นหลายบรรทัด
- หรือใช้
always
block แทน
เทคนิคการดีบัก assign
- ตรวจสอบชนิดสัญญาณ (wire/reg) ให้ถูกต้อง
- อ่าน warning ของเครื่องมือ simulation อย่างละเอียด
- ตรวจสอบข้อจำกัดของ FPGA/ASIC tool แต่ละค่าย
สรุป: assign ใช้ง่าย แต่ต้องระวัง
assign
เป็นคำสั่งที่เรียบง่ายและทรงพลัง แต่มีข้อจำกัด เช่น ใช้ได้เฉพาะกับ wire และห้ามกำหนดหลายครั้งกับสัญญาณเดียวกัน
การปฏิบัติตามกฎเหล่านี้ จะช่วยให้หลีกเลี่ยงบั๊กและทำให้โค้ดอ่านง่ายและดูแลรักษาได้ดีขึ้น
7. คำถามที่พบบ่อย (FAQ)
เกี่ยวกับคำสั่ง assign
ใน Verilog ผู้เริ่มต้นจนถึงระดับกลางมักจะมีข้อสงสัย เราได้รวบรวมคำถามที่ถูกค้นหาบ่อยและอธิบายเป็นรูปแบบ Q&A
Q1: สำหรับผู้เริ่มต้น ควรใช้ assign หรือ always ก่อน?
A: ควรเริ่มจาก assign ก่อน
assign
ใช้เขียนวงจรคอมบิเนชันได้ง่าย จึงเหมาะสำหรับผู้เริ่มเรียน Verilog ในขณะที่ always
เหมาะกับวงจรซีเควนเชียลและซับซ้อนกว่า
- ลอจิกง่าย ๆ →
assign
- ต้องมีเวลา/สถานะ →
always
Q2: ถ้าอยากใช้ assign กับ reg ทำได้ไหม?
A: ไม่ได้ ต้องใช้ always
assign
ใช้ได้เฉพาะกับ wire
ส่วน reg
ต้องกำหนดค่าใน always
// ✅ ถูกต้อง
reg out;
always @(a or b)
out = a & b;
// ❌ ผิด
reg out;
assign out = a & b;
Q3: ใช้ assign หลายครั้งกับสัญญาณเดียวกันได้ไหม?
A: ไม่ได้เด็ดขาด
กฎของ Verilog คือ สัญญาณหนึ่งต้องมีแหล่งขับเพียง 1 แหล่ง หากมีหลาย assign จะเกิด error หรือ undefined behavior
Q4: การใช้ delay (#) ใน assign มีประโยชน์ไหม?
A: มีประโยชน์เฉพาะในการ simulation เท่านั้น
assign #5 out = a & b;
delay (#5) จะทำงานใน simulation แต่จะถูกละเว้นเมื่อนำไปสังเคราะห์เป็น FPGA/ASIC
- Simulation → ใช้ได้
- Hardware synthesis → ถูกละเว้น
Q5: ถ้าอยากเขียนเงื่อนไขด้วย assign ต้องทำอย่างไร?
A: ใช้ ternary operator
assign out = sel ? a : b;
ถ้า sel=1
→ ส่งออก a
ถ้า sel=0
→ ส่งออก b
เหมาะกับเงื่อนไขง่าย ๆ ถ้าเงื่อนไขซับซ้อนควรใช้ always
Q6: ทำไม assign ไม่เปลี่ยนค่าเวลา test?
A: ตรวจสอบว่าอินพุตมีการเปลี่ยนจริงหรือไม่
assign
จะเปลี่ยนค่าเฉพาะเมื่ออินพุตเปลี่ยน หากอินพุตคงที่ เอาต์พุตก็จะคงที่เช่นกัน
- อินพุตมีการเปลี่ยนจริงหรือไม่?
- มีการกำหนดค่าเริ่มต้นให้สัญญาณหรือไม่?
- ใน simulation waveform มีการเปลี่ยนหรือเปล่า?
Q7: โค้ดที่เขียนด้วย assign สามารถสังเคราะห์ได้หรือไม่?
A: ส่วนใหญ่สังเคราะห์ได้ ยกเว้นบางโอเปอเรเตอร์
- AND / OR / XOR → ✅ สังเคราะห์ได้
- หาร (/) หรือทศนิยม → ⚠️ บางกรณีสังเคราะห์ไม่ได้
ดังนั้นก่อนใช้ควรตรวจสอบว่าเครื่องมือ FPGA/ASIC รองรับหรือไม่
8. พจนานุกรมคำศัพท์พื้นฐานสำหรับผู้เริ่มต้น Verilog
ต่อไปนี้คือคำศัพท์พื้นฐานที่ควรเข้าใจก่อนเริ่มเขียน Verilog โดยเฉพาะคำที่เกี่ยวข้องกับ assign
และวงจรคอมบิเนชัน
wire (ไวร์)
ความหมาย:
สัญญาณที่จำลองการเป็น “สายไฟ” ในวงจรลอจิก ใช้สำหรับรับค่าจากสัญญาณหรือเอาต์พุตอื่น
คุณสมบัติ:
- สามารถกำหนดค่าได้ด้วย
assign
- ไม่สามารถเก็บค่าด้วยตัวเอง
- ใช้หลัก ๆ ในวงจรคอมบิเนชัน
ตัวอย่าง:
wire a, b, out;
assign out = a & b;
reg (เรจิสเตอร์)
ความหมาย:
สัญญาณที่เก็บค่าได้ชั่วคราว ใช้ใน always
block เป็นหลัก
คุณสมบัติ:
- ไม่สามารถใช้กับ
assign
- เก็บสถานะในวงจรซีเควนเชียล
- มักใช้ร่วมกับ clock หรือเงื่อนไข
ตัวอย่าง:
reg out;
always @(posedge clk) out <= a;
assign (อะแซน์)
ความหมาย:
การกำหนดค่าแบบต่อเนื่อง (continuous assignment) ให้กับสัญญาณชนิด wire
คุณสมบัติ:
- ใช้ในวงจรคอมบิเนชัน
- เอาต์พุตเปลี่ยนทันทีตามอินพุต
- รองรับการคำนวณ ลอจิก เงื่อนไข และค่าคงที่
ตัวอย่าง:
assign y = a & b;
always (ออลเวย์)
ความหมาย:
บล็อกที่ทำงานเมื่อเกิดเหตุการณ์ (event) เช่น ขอบสัญญาณ clock
คุณสมบัติ:
- ใช้กับ
reg
- รองรับ if, case, และการควบคุมเชิงเงื่อนไข
- ใช้สำหรับวงจรซีเควนเชียล
ตัวอย่าง:
always @(posedge clk) begin
out <= a + b;
end
วงจรคอมบิเนชัน (Combinational Circuit)
ความหมาย:
วงจรที่เอาต์พุตขึ้นอยู่กับอินพุตปัจจุบันเท่านั้น
คุณสมบัติ:
- ไม่มีองค์ประกอบเก็บค่า
- เช่น ลอจิกเกต, ตัวบวก, MUX
- ใน Verilog ใช้
assign
หรือalways @(*)
วงจรซีเควนเชียล (Sequential Circuit)
ความหมาย:
วงจรที่เอาต์พุตขึ้นอยู่กับทั้งอินพุตปัจจุบันและสถานะในอดีต
คุณสมบัติ:
- มีองค์ประกอบเก็บค่า เช่น flip-flop, register
- ทำงานแบบซิงโครไนซ์กับ clock
- เขียนด้วย
always @(posedge clk)
ternary operator (ตัวดำเนินการเงื่อนไข)
ความหมาย:
โครงสร้างเงื่อนไขรูปแบบ เงื่อนไข ? ค่าเมื่อจริง : ค่าเมื่อเท็จ
คุณสมบัติ:
- มักใช้ใน assign
- เขียนได้กระชับกว่า if
ตัวอย่าง:
assign y = sel ? a : b;
module (โมดูล)
ความหมาย:
หน่วยการออกแบบพื้นฐานใน Verilog
คุณสมบัติ:
- มีพอร์ต input/output
- สามารถรวมโมดูลหลายตัวเป็นโครงสร้างแบบลำดับชั้น
ตัวอย่าง:
module adder(input a, input b, output sum);
assign sum = a + b;
endmodule
initial (อินิเทียล)
ความหมาย:
บล็อกที่ทำงานเพียงครั้งเดียวเมื่อเริ่ม simulation
คุณสมบัติ:
- ไม่ถูกสังเคราะห์เป็นวงจรจริง
- ใช้ใน testbench เป็นหลัก
ตัวอย่าง:
initial begin
a = 0;
b = 1;
end
non-blocking assignment (<=)
ความหมาย:
รูปแบบการกำหนดค่าใน always
block ที่ทำงานแบบขนาน
คุณสมบัติ:
- ใช้บ่อยในการออกแบบที่ซิงโครไนซ์กับ clock
- ช่วยหลีกเลี่ยงปัญหาลำดับการประมวลผล
ตัวอย่าง:
always @(posedge clk) begin
out1 <= in1;
out2 <= in2;
end
สรุป: เข้าใจคำศัพท์คือก้าวแรกในการเรียน Verilog
การเข้าใจคำเหล่านี้จะช่วยให้วิเคราะห์ error ได้ง่ายขึ้น และทำให้การเขียน Verilog ราบรื่นขึ้น
9. สรุป|การใช้งาน assign อย่างเชี่ยวชาญ
ในบทความนี้ เราได้อธิบายเกี่ยวกับ assign
ใน Verilog ตั้งแต่พื้นฐานไปจนถึงการประยุกต์ใช้ สำหรับผู้เริ่มต้น assign
ถือเป็นคำสั่งแรก ๆ ที่ควรเรียนรู้ เพราะเป็นเครื่องมือหลักในการเขียน วงจรคอมบิเนชัน
ทบทวนจุดสำคัญของ assign
✅ บทบาทของ assign
- ใช้
assign
เพื่อกำหนดค่าแบบต่อเนื่องให้กับสัญญาณชนิด wire - เอาต์พุตเปลี่ยนทันทีเมื่ออินพุตเปลี่ยน
- เหมาะสำหรับการเขียนวงจรคอมบิเนชัน
✅ กฎการใช้งาน
assign
ใช้กับ wire เท่านั้น ไม่สามารถใช้กับ reg- ห้ามเขียน assign ซ้ำกับสัญญาณเดียวกัน
- ไม่สามารถใช้ assign เพื่อกำหนดค่าเริ่มต้น
✅ เคล็ดลับการใช้งาน
- เข้าใจความแตกต่างระหว่าง
assign
และalways
- ใช้ ternary operator เมื่อมีเงื่อนไขง่าย ๆ
- เมื่อสมการซับซ้อน ควรแบ่งออกเป็นหลายบรรทัดด้วย wire กลาง เพื่อให้อ่านง่าย
ก้าวต่อไปที่ควรเรียนรู้
หลังจากเข้าใจ assign
แล้ว ขั้นตอนถัดไปที่ควรศึกษา:
- การเขียน วงจรซีเควนเชียล ด้วย
always
- การใช้
if
และcase
สำหรับเงื่อนไขที่ซับซ้อน - การเขียน testbench และการ simulation
- การออกแบบแบบโมดูลาร์ (hierarchical design)
Verilog เป็นภาษาที่ต้องอาศัยทั้ง “การฝึกฝน” และ “ประสบการณ์จริง” การลองเขียนวงจรเล็ก ๆ ด้วย assign
ซ้ำ ๆ จะช่วยให้คุณเข้าใจการทำงานของสัญญาณและโครงสร้างวงจรได้อย่างลึกซึ้ง
บทส่งท้าย
เมื่อคุณสามารถใช้ assign
ได้อย่างถูกต้อง นั่นหมายความว่าคุณได้ก้าวข้ามอุปสรรคสำคัญของการเรียน Verilog ไปแล้ว
หวังว่าบทความนี้จะเป็นเสมือน “คู่มือ assign” ที่คุณสามารถกลับมาเปิดอ่านได้ทุกครั้งที่ต้องการทบทวน