ECE 4514 Lecture 21 Using a keyboard with DE1-SoC (but not a PS/2 keyboard!) 5:00 Using a keyboard with DE1-SoC The DE1-SoC has a PS/2 keyboard interface directly connected to the FPGA. The PS/2 interface electrically does not represent much; it's a serial interface that comes with a clock. SLIDES -> slides-1-ps2-keyboard.pdf However, PS/2 keyboards are outdated. You can use a USB keyboard as a PS/2 keyboard with a proper conversion plug; the USB keyboard chips can fall back in PS/2 mode. In this lecture, we will follow an alternate route, however. We will use the keyboard of your laptop/computer as the keyboard for the DE1SoC board. We avoid the PS/2 interface, but instead build a connection through alternate connections to the board: 1. USB (Blaster) 2. Ethernet These interfaces are not so easy to access from the FPGA fabric as the PS/2 interface, though; - The USB (Blaster) can be accessed only through JTAG. We need a JTAG-UART peripheral, which in turn has an Avalon MM Slave interface; we need to create a Platform (QSYS) to access the JTAG-UART peripheral. - The Ethernet is only directly accessible from the hard-core ARM on the FPGA; which in turn can access the FPGA fabric only through an AMBA-AXI bus and an AMBA-AXI-to-Avalon bridge We will discuss two examples that illustrate how to use these interfaces. These examples are not straightforward, and touch upon aspects that are strictly speaking broader than the topic of DD/2. However, they may be able to save you design time for the final project. The first example illustrates USB (Blaster) and is probably the easiest and quickest. The second example (Ethernet) is more tricky and only suited if you are strong in software. To use these designs, you will need to download the example repos: git clone https://github.com/vt-ece4514-s19/reading-keyboard This gives you two directories: reading-keyboard-hps -> keyboard using Ethernet or USB-UART and HPS reading-keyboard-nios -> keyboard using USB-Blaster and NIOS-II 5:05 A Keyboard through USB (Blaster) Block Diagram: LED PC (usb) --- (usb blaster) -- JTAG | UART MEMORY NIOS2 PIO | | | | +-----------+-----------+-----------+ - The NIOS2 is a 32-bit processor that runs a C program. - The C program scans the JTAG UART for characters typed on PC - The C program displays each received character on the LEDs - Note that this is a QSYS system; if you want to integrate it in your application, you have to instantiate the QSYS top-level in your design, and e.g. substitute the LED output with the port in your design where you would like to use the character codes. 5:10 Quick overview of the NIOS-II core: SLIDES -> slides-2-nios.ppt * 32-bit design (w 32-bit address) * IO ports: custom instructions tightly coupled memories main memory bus * Configurable! Optional elements: cache, mmu, shadow regs - Expected Performance/Area Cost (slides) Nios-II Classic / Nios-II Next Generation Nios II-e Nios II-s Nios II-f ------------------------------- Pipe Stage 1 5 6 Br. Pred. - static dynamic Multiplier SW 3-cycle 1-cycle Shifter SW 3-cycle 1-cycle (barrel) ICache - v v DCache - - v MMU - - optional 5:15 Design flow for this system (1) Design Hardware -> download (2) Generate BSP (3) Write Software -> download (4) Interact through terminal => make drawing mentioning Hardware Design in QSYS (system architecture) Generate HDL Link into FPGA (+ pin binding) Logic Synthesis Mapping P&R Download SOF Take QSYS design in BSP editor Select features of software libraries Mapping of low-level IO channels Linker Description File (memory map) Generate BSP library Write Software Compile Software Open Terminal Download ELF 5:20 C program on NIOS processor SLIDES -> slides-3-altera-jtag.pdf #include #include #include "altera_avalon_jtag_uart_regs.h" #define JTAG_UART_DATA ((volatile int*) JTAG_UART_0_BASE) int main() { volatile unsigned *led_pio = (unsigned *) PIO_0_BASE; unsigned c; while (1) { c = *JTAG_UART_DATA; if (c & ALTERA_AVALON_JTAG_UART_DATA_RVALID_MSK) { *led_pio = (c & ALTERA_AVALON_JTAG_UART_DATA_DATA_MSK); if ((c & ALTERA_AVALON_JTAG_UART_DATA_DATA_MSK) == 'X') break; printf("char read: %c (hex %x)\n", c & ALTERA_AVALON_JTAG_UART_DATA_DATA_MSK, c & ALTERA_AVALON_JTAG_UART_DATA_DATA_MSK); } } printf("Program complete\n"); *led_pio = 0x0; // all off return 0; } ========================================================== Step by Step 1. Open keyboard.qpf in quartus (inspect keyboard.v) 2. Run tools - QSYS (Platform Designer) 3. Open simplesys.qsys (inspect system configuration) 4. In QSYS, clock 'Generate HDL...' followed by 'Generate ..' followed by 'Finish' 5. Click 'Compile Design' in Quartus -> generates bitstream keyboard.sof 6. Open a 'NIOS-II Command Shell' (from the Quartus tools) Navigate to the toplevel directory of reading-keyboard-nios 7. In NIOS-II CS, type 'jtagconfig' and observe: 1) DE-SoC [USB-1] 4BA00477 SOCVHPS 02D120DD 5CSE(BA5|MA5)/5CSTFD5D5/.. If you don't see this string, the board is not connected through USB Blaster 8. Configure the bitstream quartus_pgm -m jtag -o "p;keyboard.sof@2" 9. Run nios2-bsp-editor New NiosII BSP Open simplesys.sopcinfo Select Main Tab -> enable_small_c_library Select Drivers -> enable_small_driver Generate 10. Move to the software subdirectory in the Nios-II command shell cd software nios2-bsp-generate-files --settings=hal_bsp/settings.bsp \ --bsp-dir=hal_bsp cd hal_bsp make cd .. 11. Generate the software application makefile nios2-app-generate-makefile \ --bsp-dir=hal_bsp \ --src-files=main.c \ --elf-name=main.elf 12. make 13. Open a nios2-terminal (do this in a SECOND NIOS-II command shell) 14. Run the program nios2-download main.elf --go Type characters in the nios2 terminal; you will see the LEDs change. The program continues until you press 'X' 15. To quit: - close all nios2 command shells - close quartus ========================================================== 5:45 A Keyboard through Ethernet or USB-UART Block Diagram: --- (ethernet) -------+--- MEMORY | PC --- (usb uart) -------+ | HPS | switch LED AXI Bridge | | | PIO PIO | | | +-----------+-----------+ - The HPS is dual-core ARM A8 that runs a linux kernel - The Linux kernel has a great number of peripherals available - Ethernet - UART - FPGA fabric - We add a small bus system on the FPGA that hosts two PIO ports - The PC connects to the Linux kernel standard I/O - The Linux kernel runs a program that accepts characters to standard I/O - The program then sends the characters to the LEDs 5:50 Quick overview of the HPS dual arm, L1, L2 cache L3 switch FPGA bridges High-performance AXI FPGA -> HPS High-performance AXI HPS -> FPGA Peripheral AXI HPS -> FPGA L3 peripheral slave switch I2C controller UART DDR controller L3 peripheral master switch SD card interface Ethernet MAC off-chip FPGA: DDR controller -> 1GB DDR SD card interface -> SD card I2c controller -> Accelerometer UART -> uart-2-USB chip -> USB FPGA fabric -> HEX/LED/Buttons FPGA fabric -> JTAGUSB -> USB Blaster Ethernet MAC -> Ethernet PHY Chip What happens when we boot linux? The HPS contains a boot rom which looks for an SDcard The SDcard itself contains several partitions. For our configuration, relevant (1) A linux kernel (2) A root file system Once the kernel boots, it opens a connection (console) to the UART You can then connect a terminal to the UART and log in. How does Linux 'talk' to hardware? It uses a C program as an application and talks to a memory-mapped peripheral (as Similar to a memory-mapped peripheral, but with a few caveats: a) There is more than one application. Linux uses the name 'process' to indicate one logical thread of control, ie. the equivalent of one C program running. All applications result in at least one process, but not all processes are user applications. b) As a consequence of (a), every hardware resource has to be shared among the processes. Hardware resources include CPU, memory, peripherals - Linux solves this by using Virtual memory, and by distinguishing different priorities between processes. We will define 'user space' and 'kernel space' to identify the two most important priority levels. The linux OS, and the low level device drivers that talk to hardware, are part of 'kernel space' The C applications that we will write, and that will talk to FPGA hardware, are part of 'user space'. So there are two concepts, and both are important: - Virtual Memory - User Space and Kernel Space - Both of them can be explained in a Figure Application 1 Application 2 +-----------+ +-----------+ App 1 Address App 2 Address Space (32 bit) Space (32 bit) +-----------+ +-----------+ User Space ------------------------------------------------- Kernel Space CPU Mem Management Unit Device Drivers ------------------------------------------------- Hardware Hardware Bus (32 bit) +---------------------+ Physical Address Space +---------------------+ The consequence of this design is that applications cannot direct access the hardware they want because (a) They don't 'see' the hardware as an address (b) They don't have the privilege to access it - In codesign, we are going to address these two issues through a variation of techniques: - Device drivers These are abstractions of the hardware created by the OS. Typically, a device driver allows your application to access the hardware as a file - Memory mapping These are 'pipes' between a Virtual Address Space and the Physical Address Space, such that the hardware becomes visible to the Application, AND such that the Application has the privileges to access that hardware 6:00 C program on NIOS processor #include #include // close #include // O_RDWR, O_SYNC #include // PROT_READ, PROT_WRITE #include "socal/socal.h" // alt_write #include "socal/hps.h" #include "socal/alt_gpio.h" #include "hps_0.h" // definitions for LED_PIO and SWITCH_PIO #define HW_REGS_BASE ( ALT_STM_OFST ) #define HW_REGS_SPAN ( 0x04000000 ) #define HW_REGS_MASK ( HW_REGS_SPAN - 1 ) #include static struct termios oldt; int atexit(void (*func)(void)); void restore_terminal_settings(void) { tcsetattr(0, TCSANOW, &oldt); /* Apply saved settings */ } void disable_waiting_for_enter(void) { struct termios newt; /* Make terminal read 1 char at a time */ tcgetattr(0, &oldt); /* Save terminal settings */ newt = oldt; /* Init new settings */ newt.c_lflag &= ~(ICANON | ECHO); /* Change settings */ tcsetattr(0, TCSANOW, &newt); /* Apply settings */ atexit(restore_terminal_settings); /* Make sure settings will be restored when program ends */ } int main(int argc, char **argv) { void *virtual_base; volatile unsigned long *led_pio; int fd; if( ( fd = open( "/dev/mem", ( O_RDWR | O_SYNC ) ) ) == -1 ) { printf( "ERROR: could not open \"/dev/mem\"...\n" ); return( 1 ); } virtual_base = mmap( NULL, HW_REGS_SPAN, ( PROT_READ | PROT_WRITE ), MAP_SHARED, fd, HW_REGS_BASE ); if( virtual_base == MAP_FAILED ) { printf( "ERROR: mmap() failed...\n" ); close( fd ); return(1); } led_pio = virtual_base + ( ( unsigned long )( ALT_LWFPGASLVS_OFST + LED_PIO_0_BASE) & ( unsigned long)( HW_REGS_MASK ) ); char c; disable_waiting_for_enter(); while (1) { c = getchar(); *led_pio = c; if (c == 'X') break; printf("char read: %c (hex %x)\n", c, c); } *led_pio = 0x0; // all off if( munmap( virtual_base, HW_REGS_SPAN ) != 0 ) { printf( "ERROR: munmap() failed...\n" ); close( fd ); return( 1 ); } close( fd ); return 0; } ========================================================== Step by Step 1. Go to hardware subdirectory; open baseline.qpf in quartus 2. Open Tools - QSYS Load baseline_qys.sys Click Generate HDL .. followed by 'Generate ..' followed by 'Finish' 3. Compile design in Quartus 4. Open a SOC EDS Shell and navigate to reading-keyboard-hps/conversion ./runme.txt This command: a/ creates a custom header-include file (hps_0.h), useful for compiling ARM programs. This include file contains the configuration mapping for the FPGA (similar to the board support package in NIOS) b/ converts the SOF to an RBF file, which allows the bitstream to be loaded by the ARM core on the FPGA 5. Navigate to software subdir: make This generates a program for the ARM called piodemo 6. Boot the DE1SoC board with linux, and connect to it over Ethernet (If you don't know how to do this, the HPS is not for you - stick to the NIOS method for now) Copy the following files to the board software/piodemo conversion/hps_config_fpga conversion/baseline.rbf 7. In a shell on the linux of the FPGA board, configure the bitstream into the FPGA ./hps_config_fpga baseline.rbf And run the program chmod +x piomod ./piomod 8. Typing characters will change the LEDs. until you type 'X' 6:10 Conclusions - We discussed two examples to connect your laptop's keyboard to the FPGA. Both of them require quite a bit of software, but still solve the problem. - One uses NIOS, an FPGA softcore, to control a JTAG-UART periperhal and a PIO port The second uses HPS, the ARMs inside of your FPGA, running linux (on a network such as ethernet) to control a PIO port.