FPGA evaluation cards, like the Xilinx VC-707, often have 16x2 LCD panels. These display up to two lines of 16 characters each.
There are numerous examples online showing how to exercise these displays using low-level RTL. It is much easier with a HLS flow such as Kiwi, where the thread of control with method call inlining neatly generates the same designs.
The standard 16x2 DisplayTech LCD is documented here https://www.openhacks.com/uploadsproductos/eone-1602a1.pdf.
The following CSharp file was compiled with mono mcs.
//
// Kiwi Scientific Acceleration
// (C) 2009 DJ Greaves - University of Cambridge, Computer Laboratory
//
// Demonstration of driving the 16x2 LCD display panel found on FPGA development boards.
//
using System;
using KiwiSystem;
class KiwiLCD1602Driver
{
/*
// Put the following padring code in a Xilinx top-level file.
// Instance of the Kiwi-generated LCD driver
wire lcd_data4_openable;
wire [3:0] lcd_data4_in, lcd_data4_out;
bufif1(LCD_DB4_LS, lcd_data4_out[0], lcd_data4_openable);
bufif1(LCD_DB5_LS, lcd_data4_out[1], lcd_data4_openable);
bufif1(LCD_DB6_LS, lcd_data4_out[2], lcd_data4_openable);
bufif1(LCD_DB7_LS, lcd_data4_out[3], lcd_data4_openable);
IBUF lcd_in40(lcd_data4_in[0], LCD_DB4_LS);
IBUF lcd_in41(lcd_data4_in[1], LCD_DB5_LS);
IBUF lcd_in42(lcd_data4_in[2], LCD_DB6_LS);
IBUF lcd_in43(lcd_data4_in[3], LCD_DB7_LS);
kiwi_lcd1602_driver the_lcd_driver(
.LCD_RS_LS(LCD_RS_LS),
.LCD_E_LS(LCD_E_LS),
.LCD_RW_LS(LCD_RW_LS),
.lcd_data4_in(lcd_data4_in),
.lcd_data4_out(lcd_data4_out),
.lcd_data4_openable(lcd_data4_openable)
);
*/
// The standard 1602a DisplayTech LCD is documented here https://www.openhacks.com/uploadsproductos/eone-1602a1.pdf
// http://embeddedlifehelp.blogspot.co.uk/2012/03/16x2-lcd-programming-for-beginners-made.html
// https://learningmsp430.wordpress.com/2013/11/16/16x2-lcd-interfacing-in-4-bit-mode/
// Use 4-bit, tri-state connection to the LCD panel.
[Kiwi.OutputBitPort("LCD_RS_LS")] static bool LCD_RS;
[Kiwi.OutputBitPort("LCD_RW_LS")] static bool LCD_RW;
[Kiwi.OutputBitPort("LCD_E_LS")] static bool LCD_E;
[Kiwi.OutputBitPort("lcd_data4_openable")] static bool lcd_data4_openable;
[Kiwi.InputWordPort(3, 0, "lcd_data4_in")] static byte lcd_data4_in;
[Kiwi.OutputWordPort(3, 0, "lcd_data4_out")] static byte lcd_data4_out;
public static void PortIdle()
{
lcd_data4_openable = true;
LCD_RS = false;
LCD_RW = false;
LCD_E = false;
lcd_data4_out = 0;
}
// To send a byte on the 4-bit bus, send high nibble first.
// To read a byte on the 4-bit bus, get high nibble first.
static byte readByte(bool dataf)
{
LCD_RS = dataf; // Low for control, high for readback of data.
lcd_data4_openable = false; LCD_RW = true;
Kiwi.Pause(); LCD_E = true;
Kiwi.Pause(); byte r = (byte)(lcd_data4_in & 0xF); LCD_E = false;
Kiwi.Pause(); LCD_E = true;
Kiwi.Pause(); r = (byte)((r << 4) | (lcd_data4_in & 0xF)) ; LCD_E = false;
Kiwi.Pause();
lcd_data4_openable = true; // Re-enable outputs for next write.
return r;
}
// To send a byte on the 4-bit bus, send high nibble first.
static void sendByte(bool dataf, byte data)
{
LCD_RS = dataf; // Low for control, high for readback of data.
lcd_data4_openable = true; LCD_RW = false; lcd_data4_out = (byte)((data >> 4) & 0xF);
Kiwi.Pause(); LCD_E = true;
Kiwi.Pause(); LCD_E = false;
Kiwi.Pause(); lcd_data4_out = (byte)((data >> 0) & 0xF) ;
Kiwi.Pause(); LCD_E = true;
Kiwi.Pause(); LCD_E = false;
Kiwi.Pause();
}
static void WaitNotBusy()
{
// Have two pauses to give it a chance to go busy.
Kiwi.Pause();
Kiwi.Pause();
// Busy flag is bit seven of the status register - poll to be not busy.
while (true)
{
byte status_reg = readByte(false);
Kiwi.Pause();
if (((int)(status_reg) & 128)==0) break;
}
}
static void wait_delay_40()
{
for (int k=5; k != 0; k--) Kiwi.Pause(); // Wait 5*period for 4.1 ms or 100 us
}
static void LcdCmdD(byte cmd, int delay) // Send a command byte using a delay.
{ sendByte(false, cmd);
Console.WriteLine("LCD sent cmd byte using delay timing alue={0:X}", cmd);
}
static void LcdCmd(byte cmd) // Send a command byte using polled busy.
{
WaitNotBusy();
wait_delay_40();
sendByte(false, cmd);
Console.WriteLine("LCD sent cmd byte value={0:X}", cmd);
}
public static void sendDataByte(byte data)
{
WaitNotBusy();
wait_delay_40();
sendByte(true, data);
Console.WriteLine("LCD sent data byte (datareg={1}) value={0:X}", data, LCD_RS);
}
public static void Reset()
{
lcd_data4_openable = false;
Kiwi.Pause();
PortIdle();
wait_delay_40();
// Wait >4.1ms and >100 us in the gaps between the first 2 '3' operations, then used BF polling instead of delays.
LcdCmdD(0x33, 2000); // Reset code 30
wait_delay_40();
LcdCmdD(0x32, 2000); // Reset code 20
wait_delay_40();
LcdCmdD(0x28, 2000); // 4-bit interface, 2 lines.
wait_delay_40();
}
public static void Setup()
{
LcdCmd(0x06); // Entry mode, ID=1, S=0;
LcdCmd(0x0f); // Display: disp on, cursor on, blink on.
LcdCmd(0x01); // Home
LcdCmd(0x80); // Set data data RAM location 0 of top line write
}
public static void WriteString(string msg)
{
char [] cdata = msg.ToCharArray();
for (int x=0; x<msg.Length; x++) sendDataByte((byte)cdata[x]);
}
}
class tester
{
[Kiwi.OutputBitPort("done")] static bool done;
[Kiwi.InputBitPort("select")] static bool select;
public static void Main()
{
select = false;
RunHW();
}
[Kiwi.HardwareEntryPoint()]
public static void RunHW()
{
done = false;
Console.WriteLine("Hello from LCD 1602 driver");
Kiwi.Pause();
Kiwi.Pause();
Kiwi.Pause();
KiwiLCD1602Driver.PortIdle();
KiwiLCD1602Driver.Reset();
KiwiLCD1602Driver.Setup();
Kiwi.Pause();
string msg = (select) ? "Hello World": "David Greaves";
KiwiLCD1602Driver.WriteString(msg);
Kiwi.Pause();
while(true)
{
done = true;
Kiwi.Pause();
}
}
}
// eof
gmcs kiwi-lcd1602-driver.cs -r:/home/djg11/d320/hprls/kiwipro/kiwic/distro/support/Kiwi.dll
kiwi-lcd1602-driver.cs(20,49): warning CS0414: The private field `KiwiLCD1602Driver.LCD_RW' is assigned but its value is never used
kiwi-lcd1602-driver.cs(21,48): warning CS0414: The private field `KiwiLCD1602Driver.LCD_E' is assigned but its value is never used
kiwi-lcd1602-driver.cs(24,58): warning CS0414: The private field `KiwiLCD1602Driver.LCD_DATA' is assigned but its value is never used
kiwi-lcd1602-driver.cs(97,44): warning CS0414: The private field `tester.done' is assigned but its value is never used
Compilation succeeded - 4 warning(s)
/home/djg11/d320/hprls/kiwipro/kiwic/distro/bin/kiwic -give-backtrace -vnl-rootmodname=DUT -vnl=kiwi-lcd1602-driver.v kiwi-lcd1602-driver.exe -vnl-resets=synchronous -kiwic-cil-dump=combined -kiwic-kcode-dump=enable -res2-loadstore-port-count=0 -vnl-roundtrip=disable
devx (getenv "HPRLS_DEVX"="1") developer mode=true
+++ devx: other form in bifo GetLength pokidl: CT_arr (CTL_net (false,16,Signed,[Cil_at_native]),Some 11L)
+++ devx: other form in bifo GetLength pokidl: CT_arr (CTL_net (false,16,Signed,[Cil_at_native]),Some 11L)
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'2I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'8I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'1I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'6I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))>>4): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'2I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'8I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'1I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'6I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))>>4): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'3I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'2I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'8I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'1I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'6I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'0I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := U8'12I: assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))>>4): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ wrap check warning: LCD_DATA_LS[3:0]:OUTPUT::Unsigned{init=0, io_output=true, username=LCD_DATA_LS, HwWidth=4, storage=8} := C8u(15&(C8u("Hello World"[TKWr0.8_V_1]))): assignment may wrap differently: rhs/w=8, lhs/w=4, store/w=8I
+++ devx: wrap_unaltered: no arg nn in net $$AUTOFORMAT: This will be automatically replaced with a printf formatted string.
iverilog kiwi-lcd1602-driver.v vsys.v
./a.out
VCD info: dumpfile vcd.vcd opened for output.
Hello from LCD 1602 driver
LCD sent cmd byte (datareg=0) value=33
LCD sent cmd byte (datareg=0) value=33
LCD sent cmd byte (datareg=0) value=32
LCD sent cmd byte (datareg=0) value=2c
LCD sent cmd byte (datareg=0) value=8
LCD sent cmd byte (datareg=0) value=1
LCD sent cmd byte (datareg=0) value=6
LCD sent cmd byte (datareg=0) value=c
LCD sent data byte (datareg=1) value=48
LCD sent data byte (datareg=1) value=65
LCD sent data byte (datareg=1) value=6c
LCD sent data byte (datareg=1) value=6c
LCD sent data byte (datareg=1) value=6f
LCD sent data byte (datareg=1) value=20
LCD sent data byte (datareg=1) value=57
LCD sent data byte (datareg=1) value=6f
LCD sent data byte (datareg=1) value=72
LCD sent data byte (datareg=1) value=6c
LCD sent data byte (datareg=1) value=64
Process make finished
Download
Src files in this ZIP.
Archive: kiwi_lcd1602_driver.zip
Length Date Time Name
--------- ---------- ----- ----
7071 2016-06-30 10:44 cbguart1.v
5796 2016-08-08 16:19 lcd_panel_top.v
1863 2016-08-08 16:23 simsys.v
908 2016-06-17 16:45 vsys.v
6210 2016-08-08 16:19 kiwi_lcd1602_driver.cs
2453 2016-06-22 14:22 lcd-panel.tcl
888 2016-08-08 16:22 Makefile
3437 2016-08-01 15:50 lcd-panel.xdc
--------- -------
28626 8 files
Updated April 2016
UP.