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

This entry was posted in เทคนิควิธีการ, Lab Verilog and tagged , . Bookmark the permalink.

5 Responses to Verilog Programming Style: Declaration

  1. protoad56 says:

    nBalance=balance+(t<<3)+(t<<1)+(fh<<2)+fh+h;

    LIKE~!!!

  2. Mimi says:

    สุดยอดเลยนาย

  3. tonwachara says:

    ขอบใจมาก Mimi มีแรงบันดาลใจในการเขียนจากความแค้นส่วนบุคคลสมัยตอนเรียนแล้วล่ะ
    ปล. จริงๆ ATM มันฝากเงินไม่ได้ แต่สมมติว่านี่เป็น ATM/CDM ละกัน

  4. Hello! This post couldn’t be written any better!
    Reading this post reminds me of my previous room mate!

    He always kept talking about this. I will forward this article to him.

    Fairly certain he will have a good read.
    Thank you for sharing!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s