Stierlitz Example: Memory-Mapped I/O to a Character LCD
Here is a somewhat dumb example of the kind of thing one can do with Stierlitz. Let's say that you have some memory-mapped I/O ports in your system architecture, which you would like to test without having a working CPU design of any kind loaded into your FPGA.
The Xilinx ML501 board includes a Tianma-TM162, which is a Hitachi-compatible character LCD module. This is a gadget familiar to many electronics hobbyists. Let's create a memory-mapped port to access this device.
First, instantiate a Stierlitz in your top-level Verilog module:
wire sbus_ready; wire sbus_rw; wire sbus_start_op; wire [40:0] sbus_address; wire [7:0] sbus_data; stierlitz foo(.clk(hpi_clock), /* 16MHz clock */ .reset(usbreset), /* Reset (active-high) */ .enable(1'b1), /* Hardwired ON for now */ /* Control wiring */ .bus_ready(sbus_ready), .bus_address(sbus_address), .bus_data(sbus_data), .bus_rw(sbus_rw), .bus_start_op(sbus_start_op), /* CY7C67300 connections */ .cy_hpi_address(...), .cy_hpi_data(...), .cy_hpi_oen(...), .cy_hpi_wen(...), .cy_hpi_csn(...), .cy_hpi_irq(...), .cy_hpi_resetn(...) );
Use the read/write logic from the earlier "128K of SRAM" example:
wire ram_we; wire ram_oe; assign ram_we = (~sbus_rw) & sbus_start_op; assign ram_oe = sbus_rw & sbus_start_op;
Now let's create an 8-bit memory-mapped output (at the very bottom of the address space) which maps to the LCD's data port:
assign LCD_E = ((sbus_address[40:0] == 41'b0) && ram_we); assign LCD_RS = sbus_data[7]; assign LCD_RW = 0; assign LCD_DB[7:4] = sbus_data[3:0];
Now, the Lisp. Open the Stierlitz image:
(defvar *stierlitz* (open-stierlitz-image "/mnt/usb/LOPERIMG.BIN"))
The exact way in which the above works was discussed in the original post.
Stierlitz is inescapably wasteful in this case, but it will work perfectly. This is how we will write to the LCD module's data port:
(defun lcd-data-out (data) (let ((buf (make-array 512 :element-type '(unsigned-byte 8) :initial-element #x00))) (stierlitz-seek 0) ;; Block zero (setf (aref buf 0) data) ;; Set zeroth byte to equal datum (write-sequence buf *stierlitz*) ;; Write to Stierlitz ;; Force cache flush ;; We shouldn't have to do this ;; when O_DIRECT is set ... Linux hate. (stierlitz-seek 0)))
Really basic code to drive the LCD module:
(defun lcd-write-nibble (data rs) "Send a nibble to the LCD module." (let ((dout (logior (logand data #b00001111) (if rs #b10000000 0)))) (sleep 0.01) ;; Must delay as per the LCD spec (lcd-data-out dout))) (defun lcd-write (byte rs) "Send command or data byte to the LCD module." (let* ((upper-nibble (logand (ash byte -4) #xF)) (lower-nibble (logand byte #xf))) (lcd-write-nibble upper-nibble rs) (lcd-write-nibble lower-nibble rs))) (defun lcd-write-char (char) "Write a character to the LCD module." (lcd-write (char-code char) t)) (defun lcd-write-cmd (cmd) "Write a command to the LCD module." (lcd-write cmd nil)) (defun lcd-init () (lcd-write #b0010 nil) ;; Enable 4-bit mode (lcd-write-cmd #b00101000) ;; Function Set (lcd-write-cmd #b00001100) ;; Display on, cursor and blink off (lcd-write-cmd #b00000110) ;; Entry mode set (lcd-write-cmd #x01)) ;; Clear LCD
Now let's try actually using all of this:
(lcd-init) ;; Initialize LCD module. ;; Write some text using the memory-mapped port (loop for c across "www.loper-os.org" do (lcd-write-char c))
The result:
Nothing earth-shaking here, but this demo ought to make sense to everyone.
That's rather cool. I would compare it to a bus pirate but that'd be a subset of its potential.
Dear Chris Smith,
I briefly considered actually using a Bus Pirate. It quickly dawned on me that it would be a rather poor way of moving hundreds of megabytes back and forth between a PC Lisp image and the Loper prototype's memory bank.
Stierlitz is in some ways a blunter instrument than I'd like, but it has the upside of requiring no drivers (in the usual sense) on the PC side.
Yours,
-Stanislav
Yay!
Hi,
I really admire the work you do and I must say this is a very interesting approach (emulating a disk to communicate with the fpga). I'm trying to figure out the simplest way to transfer data using they cypress chip so it works in windows. I, too am using this fpga, so any advice will be well appreciated!