Some time ago I bought a Digilent Nexys 2 FPGA development board and a Digilent PmodCLS LCD module. I spent some time implementing a more or less trivial CPU in the FPGA, and various bits and pieces to aid with debugging such as a driver for the 4-digit 7-segment LED display on the board (ideal for displaying the contents of 16-bit registers) and debug LEDs and switches to select what to display. I’ve been using this primarily as an exercise in learning Verilog.
I used SPI to send bytes to the LCD module, and hooked up this hardware to the address decoder such that I could simply issue writes from the CPU to send strings to the LCD module. It’s kinda a test project, the hardware equivalent of a hello world program.
The CPU is a very simple 16-bit RISC CPU with 4 orthogonal registers. It’s nothing special, not pipelined, takes quite a few clock cycles per instruction, and has a pretty rubbish instruction set. Conspicuously lacking are indirect addressing modes, which is a shame as it makes writing software quite hard.
The test program I used for this demo was rather complicated by the need for self-modifying code to write meaningful loops for string handling. That made debugging quite interesting. I wrote an assembler in perl, as assembling this kind of code by hand is rather too time-consuming.
The source code for the software running on the CPU is reproduced below. It’s not the model of great code, but it was quite a lot of fun to write and debug.
There’s also a video, running through one cycle.
Here’s the code. Execution starts at the beginning, at address zero.
ld r2 compare
st r2 comparestash
ld r2 testend
st r2 compare
ld r2 textloop
st r2 textloopstash
writetext:
ld r3 one
textloop:
ld r0 text
compare:
b testend ; mov r0 r0
beq loopend
st r0 lcdreg
ld r1 textloop
add r1 r1 r3
st r1 textloop
b textloop
loopend:
ld r1 textloop
add r1 r1 r3
st r1 textloop
ld r3 comparestash
st r3 compare
b textloop
testend:
mov r0 r0
beq gobacktostart
ld r2 testend
st r2 compare
b delay
gobacktostart:
ld r2 textloopstash
st r2 textloop
delay:
ld r0 count1
ld r1 count2
ld r2 zero
ld r3 one
delayloop:
sub r0 r0 r3
sbc r1 r1 r2
beq delaycontinue
b delayloop
delaycontinue:
mov r0 r0
beq goroundagain
b delayloop
; ready to go round again
goroundagain:
ld r2 testend
st r2 compare
b writetext
stop:
b stop
ds #0
ds #0
b stop
zero: ds #0
one: ds #1
comparestash: ds #0
textloopstash: ds #0
count1: ds #0xffff
count2: ds #0x002f
text:
ds #27 ; ESC
ds #ord('[')
ds #ord('j') ; j (clear screen and home cursor)
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; wrap line at 16 characters
ds #ord('h') ; set display mode
ds #27 ; ESC
ds #ord('[')
ds #ord('3') ; 3 (display on, backlight on)
ds #ord('e') ; e (control backlight)
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; cursor off
ds #ord('c') ; set cursor mode
ds #32 ; SPACE
ds #ord('H')
ds #ord('e')
ds #ord('l')
ds #ord('l')
ds #ord('o')
ds #ord(',')
ds #32 ; SPACE
ds #ord('W')
ds #ord('o')
ds #ord('r')
ds #ord('l')
ds #ord('d')
ds #ord('!')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('1') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #32 ; SPACE
ds #ord('.')
ds #ord('.')
ds #ord('o')
ds #ord('o')
ds #ord('O')
ds #ord('O')
ds #ord('(')
ds #ord(')')
ds #ord('O')
ds #ord('O')
ds #ord('o')
ds #ord('o')
ds #ord('.')
ds #ord('.')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('T')
ds #ord('h')
ds #ord('i')
ds #ord('s')
ds #32 ; SPACE
ds #ord('i')
ds #ord('s')
ds #32 ; SPACE
ds #ord('a')
ds #ord('n')
ds #32 ; SPACE
ds #ord('F')
ds #ord('P')
ds #ord('G')
ds #ord('A')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('1') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('a')
ds #ord('n')
ds #ord('d')
ds #32 ; SPACE
ds #ord('a')
ds #32 ; SPACE
ds #ord('C')
ds #ord('P')
ds #ord('U')
ds #32 ; SPACE
ds #ord('i')
ds #ord('n')
ds #ord('s')
ds #ord('i')
ds #ord('d')
ds #ord('e')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('[')
ds #ord('^')
ds #ord('^')
ds #ord(']')
ds #ord('N')
ds #ord('e')
ds #ord('x')
ds #ord('y')
ds #ord('s')
ds #32 ; SPACE
ds #ord('2')
ds #ord('[')
ds #ord('^')
ds #ord('^')
ds #ord(']')
ds #32 ; SPACE
ds #ord('[')
ds #ord('v')
ds #ord('v')
ds #ord(']')
ds #ord('P')
ds #ord('m')
ds #ord('o')
ds #ord('d')
ds #ord('C')
ds #ord('L')
ds #ord('S')
ds #ord('[')
ds #ord('v')
ds #ord('v')
ds #ord(']')
ds #32 ; SPACE
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('v')
ds #ord('e')
ds #ord('r')
ds #ord('i')
ds #ord('l')
ds #ord('o')
ds #ord('g')
ds #ord('-')
ds #ord('|')
ds #ord('-')
ds #ord('x')
ds #ord('i')
ds #ord('l')
ds #ord('i')
ds #ord('n')
ds #ord('x')
ds #ord('-')
ds #ord('-')
ds #ord('-')
ds #ord('a')
ds #ord('s')
ds #ord('m')
ds #ord('-')
ds #ord('-')
ds #ord('|')
ds #ord('-')
ds #ord('-')
ds #ord('p')
ds #ord('e')
ds #ord('r')
ds #ord('l')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('C')
ds #ord('P')
ds #ord('U')
ds #ord('<')
ds #ord('-')
ds #ord('-')
ds #ord('-')
ds #ord('S')
ds #ord('P')
ds #ord('I')
ds #ord('-')
ds #ord('-')
ds #ord('>')
ds #ord('L')
ds #ord('C')
ds #ord('D')
ds #ord('C')
ds #ord('P')
ds #ord('U')
ds #ord('<')
ds #ord('-')
ds #ord('>')
ds #ord('P')
ds #ord('S')
ds #ord('R')
ds #ord('A')
ds #ord('M')
ds #ord('.')
ds #ord('c')
ds #ord('o')
ds #ord('d')
ds #ord('e')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('0') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('>')
ds #ord('>')
ds #ord('L')
ds #ord('E')
ds #ord('D')
ds #ord('s')
ds #32 ; SPACE
ds #ord('f')
ds #ord('o')
ds #ord('r')
ds #ord('e')
ds #ord('v')
ds #ord('e')
ds #ord('r')
ds #ord('<')
ds #ord('<')
ds #ord('>')
ds #ord('>')
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #ord('<')
ds #ord('<')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('1') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('>')
ds #ord('>')
ds #ord('o')
ds #32 ; SPACE
ds #ord('o')
ds #32 ; SPACE
ds #ord('o')
ds #32 ; SPACE
ds #32 ; SPACE
ds #ord('o')
ds #32 ; SPACE
ds #ord('o')
ds #32 ; SPACE
ds #ord('o')
ds #ord('<')
ds #ord('<')
ds #0
ds #27 ; ESC
ds #ord('[')
ds #ord('1') ; row 1
ds #59 ; semicolon
ds #ord('0') ; column 0
ds #ord('H') ; set display mode
ds #ord('>')
ds #ord('>')
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #32 ; SPACE
ds #ord('.')
ds #ord('<')
ds #ord('<')
ds #0
ds #0
lcdreg: equ #0x7ff
2 Responses to Driving an LCD module from an FPGA