آنچه در این مقاله میخوانید :
Toggleدر این مقاله ، به جزئیات رابط کاربری یک ال سی دی کاراکتری ۱۶×۲ با FPGA خواهیم پرداخت. در ادامه به نکات و جزئیات در رابطه با طراحی FSM خواهیم پرداخت. سپس کدهای Verilog مورد نیازمان را برای بلوک های مختلف خواهیم نوشت. همچنین از از تابلوی Mojo V3 برای تأیید طراحی خود استفاده خواهیم کرد.
بلوک دیاگرام سیستم
نمودار بلوک رابط FPGA-LCD در شکل زیر نشان داده شده است:
همانطور که مشاهده می کنید، FSM دارای دو ورودی (addr_reg و cnt_reg) و پنج خروجی (s1، s2، RS، RW و E) می باشد. ورودی های FSM به ما امکان می دهند تا وضعیت بلوک های مسیر ۱ و مسیر ۲ را کنترل کنیم. خروجی s1، مسیر ۱ را کنترل می کند و به تبع آن داده ها روی پین های DB7-DB0 اعمال می شوند. خروجی s2، مسیر ۲ را کنترل می کند و می تواند برای تولید تاخیر زمانی لازم مورد استفاده قرار گیرد. خروجی های RS ،RW و E شکل موجهای مناسبی را برای پین های کنترل LCD ایجاد می کنند.
💎حتما ببینید : قیمت ال سی دی کاراکتری ۲*۱۶
طراحی FSM
طراحی اولیه برای FSM در شکل زیر نشان داده شده است. FSM دارای سه حالت است:
۱) idle
۲) lcd_init
۳) lcd_print
1) حالت idle
در این حالت، به خروجی ها مقدار دهی اولیه می شود. بلوک ASM (دستگاه حالت الگوریتمی) برای این حالت را در شکل زیر مشاهده می کنید :
در حالت idle، خروجی های RW ،RS و E در مقدار دهی های استارت می زنند. خروجی های s1 و s2 برابر با ۲ هستند؛ به طوری که DFF های مسیر ۱ و مسیر ۲ در لبه ساعت بعدی تنظیم مجدد شوند. توجه داشته باشید ورودی اضافی (شروع) برای FSM است. این به یک دکمه فشار روی نمونه متصل خواهد شد. تنظیم ورودی start روی منطق بالا، روند چاپ روی LCD را آغاز می کند.
2) حالت lcd_init
در این حالت ، ماژول LCD اولیه سازی می شود. برای این منظور، چهار فرمان (x38 ۰، ۰x0C ، ۰x06 و۰x01) که در آدرس های ۰ تا ۳ رام ذخیره می شوند، باید روی پین های داده LCD قرار گیرند. به همین دلیل است که نمودار حالت دومین شکل صفحه، عبارت addr_reg = 3 را به عنوان شرط انتقال از lcd_init به حالت بعدی درج می کند (lcd_print).
اصطلاح دیگر برای شرایط انتقال حالت cnt_reg = ۳,۵۵۰,۰۰۰ است. هر بایت از ROM باید برای مدت زمان کافی (۷۱ مایل در این طراحی) روی پین های داده LCD استفاده شود. شرط cnt_reg = ۳,۵۵۰,۰۰۰ اطمینان می دهد که LCD زمان کافی (۷۱ میلی ثانیه) برای خواندن پین های داده دارد. بنابراین، شرایط addr_reg = 3 && cnt_reg = ۳,۵۵۰,۰۰۰ بدان معنی است که چهارمین دستور ذخیره شده در ROM برای حدود (۷۱ میلی ثانیه) روی LCD اعمال می شود. وقتی این شرایط برآورده شود، LCD با موفقیت آغاز می شود و می توانیم داده های پیام را روی DB7-DB0 قرار دهیم.
بلوک ASM برای حالت lcd_init در شکل زیر نشان داده شده است:
قبل از بررسی بلوک ASM بالا، ببینیم که چگونه شماره آستانه برای cnt_reg تعیین می شود. نمودار زمان بندی برای نوشتن ماژول LCD در شکل زیر نشان داده شده است :
توجه داشته باشید که سیگنال E باید پس از tAS به منطق بالا برود. برای PWEH در منطق بالا باقی مانده و سپس انتقال به منطق پایین tH قبل از عملیات نوشتن بعدی انجام شود. در این مقاله tAS برابر با ۱۰ میلی ثانیه، PWEH برابر با ۵۸ میلی ثانیه و tH برابر با ۳ میلی ثانیه هستند. شکل موج برای سیگنال E مطابق شکل زیر می باشد :
این شکل همچنین تعداد معادل شمارش برای هر تأخیر زمانی را نشان می دهد. توجه داشته باشید که فرکانس ساعت صفحه Mojo V3 در ۵۰ مگاهرتز (مدت زمان = ۲۰ نانومتر) است. همانطور که در تصویر چهارم نشان داده شد، مقدار cnt_reg را بررسی می کنیم تا یک مقدار مناسب برای E تنظیم شود. برای مثال، هنگامی که سیگنال cnt_reg از ۵۰۰,۰۰۰ بزرگتر و کمتر از ۴,۴,۰۰۰,۰۰۰ باشد، سیگنال E باید منطقی باشد.
باز هم، خروجی های s1 و s2 بر اساس مقدار سیگنال cnt_reg تعیین می شوند. اگر cnt_reg کمتر از ۳,۵۵۰,۰۰۰ (معادل ۷۱ میلی ثانیه) باشد، ما به انتهای عملیات نوشتن نرسیده ایم. بنابراین، داده های اعمال شده بر روی LCD نباید تغییر کنند (s1 = 0) و شمارنده باید حساب شود (s2 = 1). اما وقتی cnt_reg بیشتر از ۳,۵۵۰,۰۰۰ (معادل ۷۱ میلی ثانیه) باشد، ما به پایان عملیات نوشتن فعلی رسیده ایم. در این حالت، داده های آدرس بعدی ROM باید بر روی ال سی دی (s1 = 1) اعمال شوند و شمارنده در صفر (S2 = 2) مجددا تنظیم شود.
همانطور که قبلاً بحث شد، در چهار دستور پیکربندی LCD داریم که در آدرس های ۰ تا ۳ رام ذخیره می شوند. هنگامی که دستور چهارم (addr_reg = 3) برای ۷۱ میلی ثانیه (cnt_reg = ۳,۵۵۰,۰۰۰) روی LCD اعمال می شود، باید به حالت lcd_print برویم که داده های پیام را به LCD ارسال می کند. این با آخرین شش ضلعی در بلوک ASM حاصل می شود.
توجه داشته باشید که ورودی RS از ماژول LCD مشخص می کند که آیا DB7-DB0 باید به عنوان یک کد دستورالعمل (RS = 0) یا به عنوان داده (RS = 1) رفتار کند. از این رو، در حالت lcd_init، پین RS باید از نظر منطقی کم باشد. پین RW مشخص می کند که آیا ما برو روی ماژول می نویسیم (RW = 0) یا از آن می خوانیم (RW = 1). در هر دو حالت lcd_init و lcd_print ، ما یک عملیات نوشتن را انجام می دهیم و RW باید منطقی باشد.
3) حالت lcd_print
حالت lcd_print کاملاً شبیه حالت lcd_init است با این تفاوت که RS باید منطقی باشد (زیرا اکنون DB7-DB0 باید به عنوان یک داده به جای یک کد دستورالعمل رفتار شود). علاوه بر این، مقادیر آستانه بررسی شده برای سیگنال addr_reg باید تغییر کند زیرا این پیام در آدرس های ۴ تا ۱۹ رام ذخیره می شود. با این تغییرات، بلوک ASM را در شکل زیر مشاهده می شود :
سرانجام، با اتصال همه این بلوکهای ASM، نمودار ASM را که در شکل زیر نشان داده شده است، بدست می آوریم.
💎حتما ببینید : انواع نمایشگرها و تفاوت هایشان را بشناسید
کد Verilog
اگر نمودار بلوک شکل اول و نمودار ASM فوق را داشته باشیم، ایجاد توصیف Verilog از سیستم، کار ساده ای است. بیایید نگاهی به کد بلوک های ساختمانی مختلف بیندازیم:
مسیر 1
برای بلوک مسیر ۱، به رام ۸✕۲۰ نیاز داریم:
//Path 1: ROM
;[wire [7:0] rom_data [19:0
;assign rom_data[0] = 8’h38
;assign rom_data[1] = 8’h06
;assign rom_data[2] = 8’h0C
;assign rom_data[3] = 8’h01
;” “=[assign rom_data[4
;” “=[assign rom_data[5
;”assign rom_data[6] = “H
;”assign rom_data[7] = “E
;”assign rom_data[8] = “L
;”assign rom_data[9] = “L
;”assign rom_data[10] = “O
;” “=[assign rom_data[11
;”assign rom_data[12] = “W
;”assign rom_data[13] = “O
;”assign rom_data[14] = “R
;”assign rom_data[15] = “L
;”assign rom_data[16] = “D
;”!”=[assign rom_data[17
;” “=[assign rom_data[18
;” “=[assign rom_data[19
;[assign data = rom_data[addr_reg
این مشخص می کند که rom_data یک آرایه ی دو بعدی از سیم است. توجه داشته باشید که در Verilog، بُعد دوم آرایه پس از نام آرایه آمده است. مقادیر مورد نظر به آرایه اختصاص داده می شود و سرانجام، خروجی ROM به داده اختصاص می یابد. داده خروجی طراحی است و به DB7-DB0 ماژول LCD وصل خواهد شد.
رجیسترها و مولتی پلکسر مسیر ۱ را می توان با کد زیر شرح داد:
;reg [4:0] addr_reg, addr_next
//Path 1: Registers
always@(posedge clk, posedge rst)
begin
if(rst)
;addr_reg <= 5’b0
else
;addr_reg <= addr_next
end
//Path 1: Mux
always @*
case(s1)
:۲’b00
;addr_next = addr_reg
:۲’b01
;addr_next = addr_reg + 1’b1
:۲’b10
;addr_next = 5’b0
:default
;addr_next = addr_reg
endcase
مسیر ۲
بلوک مسیر ۲ به شرح زیر است:
;reg [21:0] cnt_reg, cnt_next
//Path 2: Registers
always@(posedge clk, posedge rst)
begin
if(rst)
;cnt_reg <= 21’h0
else
;cnt_reg <= cnt_next
end
//Path 2: Mux
always @*
case(s2)
:۲’b00
;cnt_next = cnt_reg
:۲’b01
;cnt_next = cnt_reg + 1’b1
:۲’b10
;cnt_next = 21’h0
:default
;cnt_next = cnt_reg
endcase
The FSM
برای توصیف FSM در Verilog HDL، می توانیم از عبارت localparam برای تعریف ثابت های نمادین که نمایانگر حالت های FSM هستند استفاده کنیم. FSM ما سه حالت دارد. این حالت را می توان با localparam زیر شرح داد:
,localparam [1:0] idle = ۲’b00
,lcd_init = ۲’b01
;lcd_print = 2’b10
برای ذخیره حالت سیستم، یک ثابت دو بیتی تعریف می کنیم:
;reg [1:0] state_reg, state_next
//State Registers
always@(posedge clk, posedge rst)
begin
if(rst)
;state_reg <= idle
else
;state_reg <= state_next
end
سپس، باید مدار ترکیبی را تعیین کنیم که وضعیت بعدی FSM را تعیین کند. این قسمت از کد را می توان بر اساس شش ضلعی های نمودار ASM در شکل هشتم نوشت.
*) توجه داشته باشید که کد زیر شرایط انتقال حالت را که قبلاً از آن گرفته شده ساده کرده است.
//Next State Logic
always @*
begin
case(state_reg)
:idle
if(start)
;state_next = lcd_init
else
;state_next = idle
:lcd_init
if(addr_reg == 5’h03 && cnt_reg == 3550000)
;state_next = lcd_print
else
;state_next = lcd_init
:lcd_print
if(addr_reg == 5’h19 && cnt_reg == 3550000)
;state_next = idle
else
;state_next = lcd_print
:default
;state_next = idle
endcase
end
سرانجام، ما باید کد Verilog را برای خروجی های FSM بنویسیم. این قسمت از کد را می توان بر اساس بیضی شکل هشتم نوشت که نشان دهنده تکالیف شرطی FSM است.
//Output Logic
always @*
begin
case(state_reg)
:idle
begin
;s1 = 2’b10
;s2 = 2’b10
;RW = 0
;RS = 0
;E = 0
end
:lcd_init
begin
;s1 = 2’b00
;s2 = 2’b01
;RS = 0
;RW = 0
;E = 0
if (cnt_reg >= 500000)
;E = 1
if (cnt_reg >= 3400000)
;E = 0
if (cnt_reg == 3550000)
begin
;s1 = 2’b01
;s2 = 2’b10
end
end
:lcd_print
begin
;s1 = 2’b00
;s2 = 2’b01
;RS = 1
;RW = 0
;E = 0
if (cnt_reg >= 500000)
;E = 1
if (cnt_reg >= 3400000)
;E = 0
if (cnt_reg == 3550000)
begin
;s1 = 2’b01
;s2 = 2’b10
end
end
:default
begin
;s1 = 2’b10
;s2 = 2’b10
;RW = 0
;RS = 0
;E = 0
end
endcase
end
پروژه Mojo
اکنون می توانید نسخه ای از پروژه Mojo Base را بگیرید و بخش های کد بالا را به آن اضافه کنید. به یاد داشته باشید که باید ورودی ها و خروجی های ماژول سطح بالا Verilog را تغییر دهید.
کد نهایی به شکل زیر خواهد بود:
module mojo_top(
// ۵۰MHz clock input
,input clk
// Input from reset button (active low)
,input rst_n
// cclk input from AVR, high when AVR is ready
,input cclk
// Outputs to the 8 onboard LEDs
,output[7:0]led
// AVR SPI connections
,output spi_miso
,input spi_ss
,input spi_mosi
,input spi_sck
// AVR ADC channel select
,output [3:0] spi_channel
// Serial connections
input avr_tx, // AVR Tx => FPGA Rx
output avr_rx, // AVR Rx => FPGA Tx
input avr_rx_busy, // AVR Rx buffer full
//My Inputs and Outputs
,input start
,output [7:0] data
output reg RS, RW, E
);
wire rst = ~rst_n; // make reset active high
// these signals should be high-z when not used
;assign spi_miso = 1’bz
;assign avr_rx = 1’bz
;assign spi_channel = 4’bzzzz
;assign led[7:0] = 5’h00
,localparam [1:0] idle = ۲’b00
,lcd_init = ۲’b01
;lcd_print = 2’b10
;reg [1:0] state_reg, state_next
;reg [4:0] addr_reg, addr_next
;reg [21:0] cnt_reg, cnt_next
;reg [1:0] s1, s2
//State Registers
always@(posedge clk, posedge rst)
begin
if(rst)
;state_reg <= idle
else
;state_reg <= state_next
end
//Next State Logic
always @*
begin
case(state_reg)
:idle
if(start)
;state_next = lcd_init
else
;state_next = idle
:lcd_init
if(addr_reg == 5’h03 && cnt_reg == 3550000)
;state_next = lcd_print
else
;state_next = lcd_init
:lcd_print
if(addr_reg == 5’h19 && cnt_reg == 3550000)
;state_next = idle
else
;state_next = lcd_print
:default
;state_next = idle
endcase
end
//Output Logic
always @*
begin
case(state_reg)
idle:
begin
s1 = 2’b10;
s2 = 2’b10;
RW = 0;
RS = 0;
E = 0;
end
lcd_init:
begin
s1 = 2’b00;
s2 = 2’b01;
RS = 0;
RW = 0;
E = 0;
if (cnt_reg >= 500000)
E = 1;
if (cnt_reg >= 3400000)
E = 0;
if (cnt_reg == 3550000)
begin
s1 = 2’b01;
s2 = 2’b10;
end
end
lcd_print:
begin
s1 = 2’b00;
s2 = 2’b01;
RS = 1;
RW = 0;
E = 0;
if (cnt_reg >= 500000)
E = 1;
if (cnt_reg >= 3400000)
E = 0;
if (cnt_reg == 3550000)
begin
s1 = 2’b01;
s2 = 2’b10;
end
end
default:
begin
s1 = 2’b10;
s2 = 2’b10;
RW = 0;
RS = 0;
E = 0;
end
endcase
end
//Path 1: ROM
wire [7:0] rom_data [19:0];
assign rom_data[0] = 8’h38;
assign rom_data[1] = 8’h06;
assign rom_data[2] = 8’h0C;
assign rom_data[3] = 8’h01;
assign rom_data[4] = ” “;
assign rom_data[5] = ” “;
assign rom_data[6] = “H”;
assign rom_data[7] = “E”;
assign rom_data[8] = “L”;
assign rom_data[9] = “L”;
assign rom_data[10] = “O”;
assign rom_data[11] = ” “;
assign rom_data[12] = “W”;
assign rom_data[13] = “O”;
assign rom_data[14] = “R”;
assign rom_data[15] = “L”;
assign rom_data[16] = “D”;
assign rom_data[17] = “!”;
assign rom_data[18] = ” “;
assign rom_data[19] = ” “;
assign data = rom_data[addr_reg];
//Path 1: Registers
always@(posedge clk, posedge rst)
begin
if(rst)
addr_reg <= 5’b0;
else
addr_reg <= addr_next;
end
//Path 1: Mux
always @*
case(s1)
۲’b00:
addr_next = addr_reg;
۲’b01:
addr_next = addr_reg + 1’b1;
۲’b10:
addr_next = 5’b0;
default:
addr_next = addr_reg;
endcase
//Path 2: Registers
always@(posedge clk, posedge rst)
begin
if(rst)
cnt_reg <= 21’h0;
else
cnt_reg <= cnt_next;
end
//Path 2: Mux
always @*
case(s2)
۲’b00:
cnt_next = cnt_reg;
۲’b01:
cnt_next = cnt_reg + 1’b1;
۲’b10:
cnt_next = 21’h0;
default:
cnt_next = cnt_reg;
endcase
endmodule
شماتیک و پرونده UCF
شکل زیر اتصالات بین Mojo و ماژول LCD را نشان می دهد.
حال می توان محدودیت های جدیدی را، به محدودیت تعریف شده توسط کاربر (UCF) پروژه Mojo Base اضافه کرد تا پین های FPGA را که به ورودی ها یا خروجی های ماژول سطح بالا متصل هستند، مشخص کنیم. چندین خط اول در پرونده UCF زیر در پروژه Mojo Base گنجانده شده است. با این وجود، ۱۲ خط آخر پین های FPGA متصل به سیگنال های DB7-DB0 start ، RS ، RW و E را در این کد مشخص می کنند.
#Created by Constraints Editor (xc6slx9-tqg144-3) – 2012/11/05
;NET “clk” TNM_NET = clk
;TIMESPEC TS_clk = PERIOD “clk” 50 MHz HIGH 50%
# PlanAhead Generated physical constraints
;NET “clk” LOC = P56 | IOSTANDARD = LVTTL
;NET “rst_n” LOC = P38 | IOSTANDARD = LVTTL
;NET “cclk” LOC = P70 | IOSTANDARD = LVTTL
;NET “led<0>” LOC = P134 | IOSTANDARD = LVTTL
;NET “led<1>” LOC = P133 | IOSTANDARD = LVTTL
;NET “led<2>” LOC = P132 | IOSTANDARD = LVTTL
;NET “led<3>” LOC = P131 | IOSTANDARD = LVTTL
;NET “led<4>” LOC = P127 | IOSTANDARD = LVTTL
;NET “led<5>” LOC = P126 | IOSTANDARD = LVTTL
;NET “led<6>” LOC = P124 | IOSTANDARD = LVTTL
;NET “led<7>” LOC = P123 | IOSTANDARD = LVTTL
;NET “spi_mosi” LOC = P44 | IOSTANDARD = LVTTL
;NET “spi_miso” LOC = P45 | IOSTANDARD = LVTTL
;NET “spi_ss” LOC = P48 | IOSTANDARD = LVTTL
;NET “spi_sck” LOC = P43 | IOSTANDARD = LVTTL
;NET “spi_channel<0>” LOC = P46 | IOSTANDARD = LVTTL
;NET “spi_channel<1>” LOC = P61 | IOSTANDARD = LVTTL
;NET “spi_channel<2>” LOC = P62 | IOSTANDARD = LVTTL
;NET “spi_channel<3>” LOC = P65 | IOSTANDARD = LVTTL
;NET “avr_tx” LOC = P55 | IOSTANDARD = LVTTL
NET “avr_rx” LOC = P59 | IOSTANDARD = LVTTL
;NET “avr_rx_busy” LOC = P39 | IOSTANDARD = LVTTL
#My Constraints
;NET “start” LOC = P1 | IOSTANDARD = LVTTL
;NET “RS” LOC = P5 | IOSTANDARD = LVTTL
;NET “RW” LOC = P7 | IOSTANDARD = LVTTL
;NET “E” LOC = P9 | IOSTANDARD = LVTTL
;NET “data<0>” LOC = P11 | IOSTANDARD = LVTTL
;NET “data<1>” LOC = P14 | IOSTANDARD = LVTTL
;NET “data<2>” LOC = P16 | IOSTANDARD = LVTTL
;NET “data<3>” LOC = P21 | IOSTANDARD = LVTTL
;NET “data<4>” LOC = P23 | IOSTANDARD = LVTTL
;NET “data<5>” LOC = P26 | IOSTANDARD = LVTTL
;NET “data<6>” LOC = P29 | IOSTANDARD = LVTTL
;NET “data<7>” LOC = P32 | IOSTANDARD = LVTTL
نتیجه گیری
با داشتن FPGA معمولاً باید مشکل را در پایین ترین سطح طراحی اجرا بررسی کنیم. آنچه در این کد ها مشاهده می شود، دروازه های منطقی و برخی از ساختمانهای اساسی مانند افزودنیها و مقایسه ها هستند. اگرچه این ها می تواند طراحی FPGA را تا حدودی دشوار کنند اما FPGA چندین مزیت را ارائه می دهد.
منبع : allaboutcircuits
دیدگاهتان را بنویسید