CP Sport Week

CP Sport Week เป็นงานกีฬาประจำภาควิชาวิศวกรรมคอมพิวเตอร์ที่จัดขึ้นทุกปี ปีนี้จัดในวันที่ 29 พฤศจิกายน – 4 ธันวาคม 2553 (ต่อจากกีฬาเอเชียนเกมส์พอดี 🙂 )

ทีมการแข่งขัน

การแข่งขันแบ่งออกเป็น 4 ทีมได้แก่

  1. นิสิตปริญญาตรีปี 2
  2. นิสิตปริญญาตรีปี 3
  3. นิสิตปริญญาตรีปี 4
  4. นิสิตปริญญาโท ปริญญาเอก นิสิตปริญญาตรีปี 1 ศิษย์เก่า (มาร่วมก็ได้นะ) อาจารย์ บุคลากร หรือบุคคลอื่นๆ ที่ได้รับอนุญาตจากอาจารย์

ชนิดกีฬาที่แข่งขัน

  1. ฟุตบอล
  2. บาสเกตบอล
  3. ดอทเอ
  4. วินนิ่ง
  5. กีฬาฮาเฮ
  6. แบดมินตัน
  7. ปิงปอง
  8. แชร์บอล

ลิงก์ข้อมูล

ประชาสัมพันธ์

Advertisements
Posted in กิจกรรม | Tagged | Leave a comment

Verilog Programming Style: Finite State Machine

บล็อกนี้เป็นบล็อกที่ต่อมาจากบล็อก Verilog Programming Style: Declaration

ในบล็อกนี้เรามาดูส่วนสำคัญนั่นคือ Finite State Machine ซึ่งเป็นส่วนควบคุมหลัก Finite State Machine ใน module ATM จะเขียนแบบ two-block ดังนี้

 //state
 reg[2:0] ps=0,ns=0;
 always@(posedge clock)begin
 if(command==RESET) ps<=0;
 else ps<=ns;
 end

 always@*begin

enT=0;enFh=0;enH=0;nT=memT;nFh=memFh;nH=memH;nBalance=balance;nDraw=draw;ready=0;

ns=0;
 case(ps)
 0:begin ns=1;  ready=1; nDraw=0; nT=0; nFh=0; nH=0; end
 1:begin
 ready=1;
 case(command)
 DEPOS: ns=2;
 WDRAW:ns=3;
 default:ns=0;
 endcase
 end
 2:begin
 nBalance=balance+(t<<3)+(t<<1)+(fh<<2)+fh+h;
 ns=0;
 end
 3:begin
 if(balance<need_money) ns=0;
 else begin nBalance=balance-need_money; nDraw=need_money; ns=4; end
 end
 4:begin
 if(draw>10) begin nDraw=draw-10; nT=memT+1; ns=4; end
 else begin ns=5; end
 end
 5:begin
 if(draw>5) begin nDraw=draw-5; nFh=memFh+1; ns=5; end
 else begin nH=draw; ns=6; end
 end
 6:begin enT=1; enFh=1; enH=1; ns=0; end
 default: begin ns=0; end
 endcase
 end

คราวนี้เราก็มาดูเป็นส่วนๆ เหมือนเช่นเคย

Block ที่ 1 ของ Finite State Machine

 //state
 reg[2:0] ps=0,ns=0;
 always@(posedge clock)begin
 if(command==RESET) ps<=0;
 else ps<=ns;
 end

Block ที่ 1 นั้นก็เหมือนการประกาศ register ปกตินั่นคือ register ps และ ns เป็น next signal ใน ISE Design คำว่า ps มักถูก hilight เป็นสีน้ำเงิน ไม่ต้องตกใจ ps ไม่ได้เป็นคำสงวนที่ห้ามใช้ประกาศเป็นชื่อแต่อย่างใด เพียงแต่โดน hilight ให้มันดูเด่นขึ้นเฉยๆ น่าจะเกิดจากการที่ ps ไปตรงกับตัวย่อคำว่า “picosecond” พอดี

Default Value

always@*begin

enT=0;enFh=0;enH=0;nT=memT;nFh=memFh;nH=memH;nBalance=balance;nDraw=draw;ready=0;

ns=0;

Block ที่ 2 เป็นบล็อกที่ผมมักเรียกว่า star block บล็อกนี้มักเป็นบล็อกเดียวใน module ที่มี always@* ถ้าในวงจรนี้ไม่มี combinational logic ส่วนอื่นๆ เช่น encoder หรือ decoder อีก หลัง always@* มักมีการประกาศกำหนดค่าปริยาย (Default Value) ของ signal ต่างๆ signal ที่จะถูกควบคุมจะใน star block นี้แบ่งได้ออกเป็น 4 ประเภท คือ enable signal ของ inout, next signal ของ register, control signal ของ register พิเศษเช่น counter และ flag signal เช่น ready ของ module เอง ทั้งนี้ควรระบุค่า default ทั้งหมดของ signal ที่จะถูก Finite State Machine ควบคุม เพื่อกันไม่ให้วงจร synthesis signal ดังกล่าวเป็น Latch ได้ (ซึ่ง Latch นี่เองมักจะนำไปสู่ “บั๊ก”)

ทั้งนี้พึงสังเกตว่า เราไม่ควรจะไปควบคุม register ต้นแบบโดยตรง แต่ควรควบคุมผ่าน next signal ของ register และเพื่อให้ปรับเป็นค่าใหม่นั้นใน clock ถัดไป

State Label และ Reset State

 case(ps)
 0:begin ns=1;  ready=1; nDraw=0; nT=0; nFh=0; nH=0; end

การสร้าง state transition table นั้นเรามักใช้ switch-case เพื่อทำให้โค้ดดูดีขึ้น (Clean Code) เราอาจตั้งชื่อ state ผ่าน parameter ได้ แต่ไม่แนะนำเวลาทำ Lab เพราะเวลาแก้ไขจะต้องแก้ไขซ้ำซ้อน คือต้องไปเพิ่มตรงที่ประกาศ parameter และก็ต้องมาเพิ่มชื่อ state ดังกล่าวใน star block อีก ทางที่ดีหากไม่อยากสับสนให้ comment ไว้เหนือ state จะดีกว่า

state 0 มักถูกเก็บไว้เป็น reset state คือยังไม่ใช่ส่วนที่เริ่มต้นจริงๆ แต่เป็นส่วนที่ clear ค่าต่างๆ เพื่อพร้อมที่จะเริ่มทำงานต่อไป เป็น state ที่จะกระโดดมาเวลา reset ตอนเริ่มต้นออกแบบมักจะเขียนกันไว้ว่า

 case(ps)
 0:begin ns=1; end

อย่างนี้ก่อนเป็น passing state เปล่าๆ หนึ่งอัน แล้วค่อยมาวิเคราะห์ต่อว่าค่าใดเป็นค่าที่ต้อง clear ก่อนเพื่อให้ module พร้อมที่จะทำงานในรอบใหม่ต่อไป

Command Choice

 1:begin
    ready=1;
    case(command)
       DEPOS: ns=2;
       WDRAW:ns=3;
       default:ns=0;
    endcase
    end

state หนึ่งที่น่าสนใจคือ state ที่เลือก command; state นี้มักจะอยู่เป็น state ที่ 1 ต่อจาก reset state; state นี้ไว้พิจารณาดูว่าขณะนี้มี command อะไรเข้ามาบ้าง; command choice มักอยู่ในรูปแบบ switch-case และที่สำคัญเช่นกันคืออย่าลืม default case (เดี๋ยวจะถูกเข้าใจเป็น latch อีก) และที่ต่างจากภาษาโปรแกรมส่วนใหญ่คือ switch-case ใน Verilog จะไม่ต้อง break; และใช้ keywork case แทน keyword switch ทั้งนี้เตือนเพราะจะสับสนเวลาเขียนโปรแกรมในภาษาอื่น

Arithmetic Logic Transition

2:begin
   nBalance=balance+(t<<3)+(t<<1)+(fh<<2)+fh+h;
   ns=0;
   end

หลายครั้งที่ในวงจรต้องมีการคำนวณที่ซับซ้อนเช่น คูณหรือหาร แต่ทว่านี่ไม่ใช่ภาษาโปรแกรมเชิงซอฟต์แวร์ ดังนั้นเราจึงต้องคิดว่าการคำนวณเหล่านั้นจะ synthesis ออกมาเป็นวงจรอย่างไร สำหรับการคูณหรือหารด้วยค่าคงที่บางจำนวน เช่น 2n อาจลดรูปเป็นวงจรได้ง่ายด้วยการ shift คูณ 2 ก็ shift left 1 ที คูณ 4 ก็ shift left 2 ที เป็นต้น

การหารก็ในทางกลับกัน หาร 2 ก็ shift right 1 ที หาร 4 ก็ shift right 2 ที หากคูณค่าคงที่ที่ไม่ใช่ 2n ก็อาจใช้กฎการแจกแจงเข้าช่วยเช่น t*10 ก็กระจายเป็น (t*8)+(t*2) ก็กลายเป็น (t<<3)+(t<<1) จะใช้เลขอะไรช่วยแจกแจงนั้นก็สามารถดูได้ด้วยการเปลี่ยนเลขนั้นเป็นไบนารีดูเช่น t*15 15 มีเลขไบนารีเป็น 1111 หรือกล่าวอีกนัยหนึ่งว่า 15=8+4+2+1 ได้นั่น เอง ก็จะได้ว่า t*15= (t*8)+(t*4)+(t*2)+(t*1) = (t<<3)+(t<<2)+(t<<1)+t นั่นเอง

สำหรับการหารเลขที่ไม่ใช่ 2n อาจมีปัญหาสักหน่อย เพราะไม่มีกฎการแจกแจงการหาร เราเปลี่ยนการหารเป็นการคูณไมได้เพราะจะสร้างเป็นทศนิยมซ้ำ เช่น t/3=t*1/3 1/3 ในไบนารีก็จะเป็น 0.01010101… อาจทำให้เสียความแม่นยำลงไปได้ ทางที่ดีเราอาจใช้วน loop แล้วค่อยๆ ลบเอา ระหว่างนั้นก็เพิ่มตัวหาร หรือที่เรากันว่า childish algorithm จะดีกว่า

สำหรับการคูณหารค่าใน register 2 ตัวก็อาจทำโดยใช้ loop ด้วยการบวกไปเรื่อยๆ สำหรับการคูณ หรือลบไปเรื่อยๆ สำหรับการหาร หรืออาจจะใช้ algorithm การ shift หรือเลือกใช้การ represent ตัวเลขด้วยระบบ floating point เพราะหากมีการคำนวณจำนวนมาก floating point จะใช้ logarithm เข้าช่วยเพื่อเปลี่ยนการคูณ-หารเป็นการบวก-ลบได้ สำหรับ balance ในที่นี้เราสามารถมองได้เป็น nBalance=balance+(t*10)+(fh*5)+h จะสังเกตเห็นได้ว่า แบงค์พัน แบงค์ห้าร้อย และแบงค์ร้อยจะถูกคูณแค่ 10 5 และ 1 ตามลำดับเท่านั้น นั่นเพราะเราเก็บ balance ในหน่วยร้อยบาทนั่นเอง การออกแบบวิธีการ represent ข้อมูลอย่างเหมาะสมสามารถช่วยลดความซับซ้อนในการคำนวณได้

บางการคำนวณมีวงจรสำเร็จอยู่แล้วเช่น บวกและลบ บางการคำนวณต้องคำนึงถึงอัลกอริทึมและโครงสร้างข้อมูลที่ใช้เก็บเอง ดังนั้น เราจึงต้องคำนึงถึงทรัพยากรที่เรามีอยู่นอกเหนือจากความต้องการทางด้านการใช้งานเพียงเดียว

Type of State and Looping State

3:begin
 if(balance<need_money) ns=0;
 else begin nBalance=balance-need_money; nDraw=need_money; ns=4; end
 end
 4:begin
 if(draw>10) begin nDraw=draw-10; nT=memT+1; ns=4; end
 else begin ns=5; end
 end
 5:begin
 if(draw>5) begin nDraw=draw-5; nFh=memFh+1; ns=5; end
 else begin nH=draw; ns=6; end
 end
 6:begin enT=1; enFh=1; enH=1; ns=0; end
 default: begin ns=0; end
 endcase
 end
//โค้ดส่วนนี้แสดงอัลกอริทึมการทอนเงินใน Lab 2 ให้ดูด้วย

ถ้าหากลองพิจารณาดูจะพบว่าลักษณะการเขียน state แบ่งได้เป็น 3 แบบใหญ่ๆ คือ

  1. Passing State คือ state ที่จะไป state ถัดไปแน่นอน แต่มาแวะผ่านเพื่อ set ค่า, clear ค่า, รอการทำงาน และ อื่นๆ เช่น Reset State ดังกล่าวขั้นต้น
  2. Choice State คือ state ที่พิจารณาว่าสถาการณ์ในปัจจุบันนี้ควรจัดการด้วยการไปที่ state ไหน ส่วนมากจะใช้พิจารณา command
  3. Looping State คือ state ที่จะดักทำการเปลี่ยนค่าให้สอดคล้องกับเงื่อนไขก่อน แล้วจึงปล่อยให้ระบบไปยัง state อื่นต่อไป ลักษณะสำคัญของ state แบบนี้คือจะมีการทำงานหลัง if เพื่อดักให้ค่าที่ไม่เหมาะสมเข้ามา เปลี่ยนค่าต่างๆ และกำหนดให้ nextstate เป็น state เดิม จนกว่าจะผ่านเงื่อนไขจึงจะผ่านไปได้ ดังนั้นจึงทำให้ Looping State มักถูกออกแบบแบบ Mealy เพราะมีการเปลี่ยนแปลงหลังจากที่ผ่านการเช็คเงื่อนไขแล้ว หรือเขียนใน ASM Chart เป็นรูปวงรี

การเขียนวิธีการควบคุม control signal ในแต่ละ state มีหลายแบบ บ้างเขียนไล่ control signal ที่จะควบคุมใน state นั้นๆ การไล่ก็มี 2 แบบ บ้างก็เขียนไล่ในบรรทัด ซึ่งมีข้อดีคือจะทำให้โค้ดดูสั้นเป็นพิเศษ แต่โค้ดจะดูยากหรือจะเขียนไล่สัญญาณละบรรทัดก็ได้ แต่ if else หรือ case ส่วนมากจะเขียน case ละบรรทัด บางคนอาจแยก control signal บางอันออกมา เขียนนอก Finite State Machine เป็น Mealy Assignment เช่น

assign flag= (ps==2)||(ps==8);

ข้อดีของวิธีนี้คือสามารถลด control signal ที่ Finite State Machine ควบคุมแต่ข้อเสียคือถ้าจะเปลี่ยน state จะต้องแก้หลายที่ แต่ก็สามารถทำได้ แต่โค้ดที่ไม่ควรทำเลยคือการไป hack state คือไปเก็บ state ไปเปลี่ยน state นอก Finite State Machine กล่าวโดยง่ายคือพยายามทำอะไรที่พยายามไปควบคุม Finite State Machine ซึ่งไม่ควร เพราะ Finite State Machine ควรจะเป็นตัวที่ควบคุมทั้งหมด ทั้งนี้การพยายามควบคุม Finite State Machine จะทำให้ synthesis ได้วงจรที่ซับซ้อนและมักจะตามมาด้วยปัญหาเสมอ

โดยภาพรวมแล้วโค้ด Verilog จะมีความยาวหลายบรรทัด แต่มี pattern ซ้ำๆ กันจนดูเหมือน copy paste ไปๆมาๆ ทั้งนี้ก็เพื่อเหตุผลเดียวอันเป็นหัวใจสำคัญของการเขียน Verilog นั่นคือ ให้วงจรสามารถ synthesis ได้ง่ายและตรงประเด็นที่สุด

Posted in เทคนิควิธีการ, Lab Verilog | Tagged , , | Leave a comment

Verilog Programming Style: Declaration

Programming Style เป็นกฎหรือข้อชี้แนะที่ใช้ในการเขียนโค้ด ซึ่งกฎหรือข้อชี้แนะเหล่านี้จะช่วยให้โปรแกรมอ่านแล้วเข้าใจง่าย ช่วยให้โปรแกรมเมอร์ทำงานร่วมกันได้ง่ายเพราะใช้ Style เหมือนกัน และที่สำคัญคือช่วยป้องกันไม่ให้เกิด “บั๊ก” ได้ [ดัดแปลงมาจาก Wikipedia: Programming Style]

Verilog นั้นแม้เป็นภาษาบรรยายเชิงฮาร์ดแวร์ (Hardware Description Language) แต่ทว่า Programming Style ใน Verilog กลับเป็นสิ่งที่ควรคำนึงถึงอย่างยิ่ง เพราะรูปแบบของโค้ดเป็นองค์ประกอบสำคัญในการพิจารณาการสังเคราะห์ฮาร์ดแวร์ (Hardware Synthesis) ถึงขนาดใน Xilinx ISE Design ต้องมี Language Template เพื่อให้สังเคราะห์ฮาร์ดแวร์ได้ตรงตามความต้องการ

Style ที่ดีคือ Style ที่ช่วยป้องกันไม่ให้เกิดบั๊กได้ เนื่องจากบั๊กส่วนมากมักไม่ได้เกิดจากการออกแบบครั้งแรก แต่มักเกิดจากการเปลี่ยนใจและเข้าแก้ไขโค้ดในภายหลัง ดังนั้น Style ที่ดีจึงควรเอื้อต่อการปรับเปลี่ยนในภายหลังได้ ทั้งนี้ไม่ได้หมายความว่า Style ที่ไม่ดีจะทำงานไม่ได้หรือทำได้ไม่ดี เพียงแต่อาจจะปรับเปลี่ยนภายหลังได้ยาก คราวนี้เราลองตั้งโจทย์ที่ครอบคลุม Lab 1-3 เพื่อยกตัวอย่าง Style หลักๆ ที่ใช้ และแสดงให้เห็นว่า Style เหล่านั้นสามารถปรับเปลี่ยนได้ง่ายอย่างไร?

โจทย์จำลองที่เราจะพูดถึงในบล็อกนี้คือ ATM ATM นี้มี 4 คำสั่ง (Command) คือ RESET DEPOS WDRAW และ NOPER ใน ATM จะมีหน่วยความจำส่วนหนึ่งไว้เก็บเงินในบัญชี (balance) ไว้เพียงบัญชีเดียว (เอาง่ายๆ) แต่ละคำสั่งจะทำงานดังนี้

  1. RESET จะทำการเคลียร์ค่าทั้งหมดใน ATM ยกเว้น balance จะเก็บค่าเดิมไว้
  2. DEPOS จะทำการฝากเงิน (คือบวกเงินเพิ่มใน balance) ซึ่งการฝากเงินนั้นจะใส่ผ่านช่องทาง (Channel) 3 ช่องคือ t (thousand ช่องใส่แบงค์พัน) fh( five hundred ช่องใส่แบงค์ห้าร้อย) และ h (hundred ช่องใส่แบงค์ร้อย) ช่องทางดังกล่าวเป็นลักษณะ inout คือฝากก็ฝากที่นี่ถอนก็ถอนที่นี่
  3. WDRAW คือการถอน ต้องใส่เงินที่ต้องการผ่าน need_money ถ้าเงินที่จะถอนมากกว่าเงินที่มีอยู่ ก็จะถอนไม่ได้ ถ้าถอนได้ก็จะถอนออกมาผ่านช่องทาง t fh และ h
  4. NOPER ย่อมาจาก No Operation แปลว่าไม่ต้องทำอะไร เป็นคำสั่งเปล่านั่นเอง

ATM นี้ยังมีความสามารถและข้อจำกัดอื่นๆ ที่ยังไม่ได้กล่าวในรายละเอียดเช่น จำนวนเงินฝากรับได้สูงสุด และยังไม่ใช่การออกแบบฮาร์ดแวร์ที่ดี เช่นในการออกแบบฮาร์ดแวร์ดังกล่าวไม่ใส่ Single Pulser เป็นต้น ทั้งนี้เพื่อลดรายละเอียดที่ไม่จำเป็นและแสดง Style ให้ชัดเจนยิ่งขึ้น โค้ด ATM ที่ออกแบบมาเป็นดังนี้

module atm(
 input clock,
 input[9:0] need_money,
 input[1:0] command,
 inout[6:0] t,
 inout[6:0] fh,
 inout[6:0] h,
 output reg [2:0] ps=0,
 output reg[9:0] balance,
 output reg ready
 );

 //command
 parameter[1:0] RESET=0,DEPOS=1,WDRAW=2,NOPER=3;

 //inout config
 reg enT=0,enFh=0,enH=0;
 reg[6:0] memT=0,memFh=0,memH=0,nT=0,nFh=0,nH=0;
 assign t=  enT?memT:7'bz;
 assign fh= enFh?memFh:7'bz;
 assign h=  enH?memH:7'bz;

 always@(posedge clock)begin
 if(command==RESET) memT<=0;
 else memT<=nT;
 end

 always@(posedge clock)begin
 if(command==RESET) memFh<=0;
 else memFh<=nFh;
 end

 always@(posedge clock)begin
 if(command==RESET) memH<=0;
 else memH<=nH;
 end

 //balance
 reg[9:0] nBalance=0;
 always@(posedge clock)begin
 if(command==RESET) balance<=0;
 else balance<=nBalance;
 end

 //draw
 reg[9:0] draw=0,nDraw=0;
 always@(posedge clock)begin
 if(command==RESET) draw<=0;
 else draw<=nDraw;
 end

 //state
 reg[2:0] ns=0;
 always@(posedge clock)begin
 if(command==RESET) ps<=0;
 else ps<=ns;
 end

 always@*begin

enT=0;enFh=0;enH=0;nT=memT;nFh=memFh;nH=memH;nBalance=balance;nDraw=draw;ready=0;

ns=0;
 case(ps)
 0:begin ns=1;  ready=1; nDraw=0; nT=0; nFh=0; nH=0; end
 1:begin
 ready=1;
 case(command)
 DEPOS: ns=2;
 WDRAW:ns=3;
 default:ns=0;
 endcase
 end
 2:begin
 nBalance=balance+(t<<3)+(t<<1)+(fh<<2)+fh+h;
 ns=0;
 end
 3:begin
 if(balance<need_money) ns=0;
 else begin nBalance=balance-need_money; nDraw=need_money; ns=4; end
 end
 4:begin
 if(draw>10) begin nDraw=draw-10; nT=memT+1; ns=4; end
 else begin ns=5; end
 end
 5:begin
 if(draw>5) begin nDraw=draw-5; nFh=memFh+1; ns=5; end
 else begin nH=draw; ns=6; end
 end
 6:begin enT=1; enFh=1; enH=1; ns=0; end
 default: begin ns=0; end
 endcase
 end

endmodule

คราวนี้เราลองมาวิเคราะห์ Style ในแต่ละบรรทัดกัน

วิธีการประกาศ output

module atm(
 //...
 output reg [2:0] ps=0,
 output reg[9:0] balance,
 output reg ready
 );

output ส่วนมากจะเป็น reg อยู่แล้ว เราไม่จำเป็นต้อง assign ค่า output และประกาศ output เป็น wire และในเมื่อ output เป็น reg อยู่แล้ว เราก็ควรประกาศควบคู่กันซะเลย เพื่อให้เวลาแก้ไขชื่อหรือจำนวนบิตก็จะแก้ที่เดียว หาก Simulate ควรเอาสิ่งที่น่าสนใจออก output ให้หมด เพื่อเวลา Generate Test Fixture จะได้ Generate ได้ง่าย output ที่ควรออกเช่น state เป็นต้น

ลักษณะ Style ที่ไม่ค่อยดี

การทำให้ output เป็น wire เป็นการซ้ำซ้อน

module atm(
//...
 output r
 );

reg ready
assign r=ready;

ประกาศ output ทีแล้วก็ reg อีกทีเป็นการซ้ำซ้อนเช่นกัน (บางครั้ง ISE Design บางรุ่นก็ไม่ให้ประกาศเช่นนี้)

module atm(
//...
 output ready;
 );

reg ready

วิธีการประกาศ command

//command
parameter[1:0] RESET=0,DEPOS=1,WDRAW=2,NOPER=3;

command ควรจะประกาศเป็น parameter เพื่อให้ผู้อ่านเข้าใจได้ง่ายว่า command แต่ละอันใช้ทำหน้าที่อะไรและควรประกาศส่วนบนของ module เพื่อให้ผู้อ่านเข้าใจว่า module นี้มีลักษณะการทำงานแบบ command มี command อะไรบ้าง การประกาศแต่ละส่วนควรเว้นวรรคและ comment ไว้ข้างบนเพื่อให้รู้ว่าส่วนนี้เอาไว้ประกาศอะไร

วิธีการ config inout

 //inout config
 reg enT=0,enFh=0,enH=0;
 reg[6:0] memT=0,memFh=0,memH=0,nT=0,nFh=0,nH=0;
 assign t=  enT?memT:7'bz;
 assign fh= enFh?memFh:7'bz;
 assign h=  enH?memH:7'bz;

inout มักจะมีการ config เป็น tristate การ config inout หลายรูปแบบทั้งนี้ขึ้นอยู่กับว่าผู้เขียนมีมุมมอง inout ในรูปแบบไหน? ในที่นี้แสดงการ config แบบ Using assignment and ternary operation โดยมอง inout เหมือนช่องสัญญาณเน็ตเวิร์กในอินเตอร์เน็ต กล่าวเปรียบเทียบได้เหมือนเราสร้างเขื่อน ในขณะที่สัญญาณเข้ามาทาง inout เราก็ปิดเขื่อนคือ disable ซะ (ทำให้ enable เป็นศูนย์) ในขณะที่ถ้าเราอยากปล่อยสัญญาณออก เราก็เปิดเขื่อนหรือ enable ออกไป (ทำให้ enable เป็นหนึ่ง) เรามักเรียกส่วนที่กักเก็บค่าก่อนปล่อยออกมาว่า buffer และเรียกประตูที่ปล่อยสัญญาณว่า enable

ข้อดีของวิธีการนี้คือ Finite State Machine เราสามารถควบคุม inout ได้อย่างเบ็ดเสร็จ แต่บางครั้งการควบคุมเบ็ดเสร็จก็มีปัญหาในการออกแบบวงจร บางครั้งหลายคนจึงมักออกแบบวงจรให้การควบคุม inout ที่เป็นไปโดยอัตโนมัติเช่น

 //inout config
 assign databus=  (cs&&!rw) ? buff:8'bz;

ทั้งนี้มีข้อดีคือ finite state machine ไม่จำเป็นต้องควบคุมสัญญาณทุกอย่าง และการันตีว่า buffer จะถูกปล่อยหลังจากที่ทุกอย่างพร้อมแล้ว

มีอีกวิธีหนึ่งที่จะควบคุม inout คือการเขียนในลักษณะ Using always and if-else วิธีนี้แทนที่จะมองว่าเป็นปล่อยหรือไม่ปล่อย กลับมองว่าเราปล่อยสัญญาณทุกขณะ ขณะที่เราต้องการรับสัญญาณ เราก็จะมองว่าเป็นปล่อยสัญญาณ z (hi-impedance) เพื่อรับฟังสัญญาณด้วยเช่นกัน วิธีนี้อาจทำให้เกิดสัญญาณตีกันจึงมักแก้ด้วยการปล่อยสัญญาณ z บ่อยๆ วิธีนี้มักใช้กับอุปกรณ์ที่บังคับความเป็น inout เช่น RAM (ศึกษาเพิ่มเติมใน Language Template>Synthesis Construct>Coding Example>Tristates Buffer)

 reg[7:0] buff;
 assign databus=buff;
 always @* begin
 buff=8'bz;
 if (cs) begin
 if (rw) buff = ram[address];
 else begin ram[address] = databus; buff=8'bz; end
 end
 else buff=8'bz;
 end

inout มักถูกประกาศเป็นอันดับแรกของ module (ยกเว้นถ้ามี command parameter ตัว inout จะถูกประกาศเป็นอันดับสอง) เพราะ Verilog ต้องประกาศตัวแปรก่อนใช้และ buffer ใน inout เป็นตัวที่ใช้บ่อยๆ อีกทั้ง inout ยังเป็นส่วนต่อประสาน (interface) กับภายนอกจึงมักประกาศ config inout ไว้บนสุด

วิธีการประกาศ register

 //draw
 reg[9:0] draw=0,nDraw=0;
 always@(posedge clock)begin
 if(command==RESET) draw<=0;
 else draw<=nDraw;
 end

register เป็นหน่วยความจำซึ่งเปรียบเสมือน “ตัวแปร” ในโปรแกรมเชิงซอฟต์แวร์การประกาศ register มักจะประกาศในหน้าตาแบบนี้ (ศึกษาเพิ่มเติมใน Language Template>Synthesis Construct>Coding Example>Flip flop)

//สมมติว่าประกาศตัวแปร pc ขนาด 3 บิต
reg[2:0] pc=0,nextpc=0;
always @(posedge clock)
 if (reset) pc <= 0;
 else pc <= nextpc;

สังเกตว่าควรใส่ค่าเริ่มต้น (initial) ของ register ทุกครั้งและประกาศ next signal ซึ่งเป็นสัญญาณที่จะถูก assign ใน clock ต่อไป next signal มักประกาศติดกับ register ต้นแบบ (เพราะต้องมีจำนวนบิตเท่ากันอยู่แล้ว)  การตั้งชื่อ next signal มักจะใส่ตัว n ต่อหน้าเช่น next signal ของ register ชื่อ time มัีกตั้งชื่อว่า nTime เป็นต้น next signal นี่เองที่เป็นตัวที่บล็อกที่สองใน Finite State Machine คอยควบคุม

ในบางครั้ง register บางอันไม่ใช่หน่วยความจำ แต่ทำหน้าที่อื่นเช่น counter หากเป็น counter จะต้องมี control signal คอยควบคุมมักจะต้องชื่อ incX และ decX สำหรับ count up และ down ของ counter ชื่อ x ตามลำดับ

//พึงระวัง ควรเขียน else สุดท้ายให้ครบเพราะมิฉะนั้นวงจรจะ synthesis เป็น latch ได้
reg[2:0] pc=0;
reg incP,decP;
always @(posedge clock)
 if (reset) pc <= 0;
 else if(incP) pc<=pc+1;
 else if(decP) pc<=pc-1;
 else pc<=pc;

แต่ทั้งนี้ไม่ควรสร้าง control signal มากเกินควร เพราะเวลาแก้ไขจะต้องแก้ไขถึงสองสามที่ ควรทำ control signal เฉพาะการ count up one และ count down one เท่านั้น (เพราะการ generate counter up/down one จะประหยัด resource มากกว่าการสร้างหน่วยความจำและใส่ next signal ทีหลัง)

ลักษณะ Style ที่ไม่ดี

 reg[9:0] balance=0;
 reg incHundred,incFiveHundred,incThousand;
 always@(posedge clock)begin
 if(reset) balance<=0;
 else if(incHundred) balance<= balance+100;
 else if(incFiveHundred) balance<= balance+500;
 else if(incThousand) balance<=balance+1000;
 else balance<=balance

ควรแก้ไขเป็น

 reg[9:0] balance=0,nBalance=0;
 always@(posedge clock)begin
 if(reset) balance<=0;
 else balance<=nBalance;
//แล้วในบล็อกที่สองของ Finite State Machine ค่อยมาใส่ nBalance=Balance+100 ฯลฯ ทีหลัง

เดี๋ยวบล็อกถัดไปค่อยมาต่อเรื่อง Finite State Machine

Posted in เทคนิควิธีการ, Lab Verilog | Tagged , | 5 Comments