To write a C application running in Linux user-space that communicates with your custom hardware IP.
We will bypass standard drivers and use Direct Memory Access (via /dev/mem) to “poke” the specific physical address where your hardware lives. This effectively proves you can control the FPGA fabric from software.
Before starting, ensure Step 3 was successful.
We need a C program that maps physical memory into the program’s virtual memory space.
// The Physical Address from Vivado Address Editor #define PHY_ADDR 0x43C00000 // Size of the address range (we only need a few bytes, but 4KB page is standard) #define MEM_SIZE 4096
int main() { int dh; void *mapped_base; volatile unsigned int *reg_ptr; // Volatile is crucial for hardware registers!
// 1. Open /dev/mem (Requires Root)
dh = open("/dev/mem", O_RDWR | O_SYNC);
if (dh == -1) {
perror("Error opening /dev/mem. Are you root?");
return -1;
}
// 2. Map Physical Memory to Virtual Memory
mapped_base = mmap(0, MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dh, PHY_ADDR);
if (mapped_base == (void *)-1) {
perror("Error mapping memory");
close(dh);
return -1;
}
// 3. Create a pointer to our registers
reg_ptr = (volatile unsigned int *)mapped_base;
printf("--- FPGA Loopback Test ---\n");
printf("Hardware Address: 0x%08x\n", PHY_ADDR);
// 4. Write to Register 0 (Offset 0)
unsigned int input_val = 42;
printf("Writing %d to Register 0...\n", input_val);
reg_ptr[0] = input_val;
// 5. Read from Register 1 (Offset 4 bytes = index 1)
// Recall: Our Verilog logic says Reg1 = Reg0 + 1
unsigned int output_val = reg_ptr[1];
printf("Read %d from Register 1.\n", output_val);
// 6. Validation
if (output_val == input_val + 1) {
printf("\n[SUCCESS] Hardware added 1 correctly!\n");
} else {
printf("\n[FAIL] Expected %d, got %d. Is the bitstream loaded?\n", input_val + 1, output_val);
}
// Cleanup
munmap(mapped_base, MEM_SIZE);
close(dh);
return 0; } ```
To compile code for the Zynq’s Linux OS, we need the Platform SDK.
Why do we need this?
The SDK is a standalone, distributable package containing the cross-compiler (arm-linux-gnueabi-gcc) and the system roots (sysroot).
loopback_os), run:
petalinux-build --sdk
petalinux-package --sysroot
source images/linux/sdk/environment-setup-cortexa9t2hf-neon-xilinx-linux-gnueabi
echo $CC. It should now print a long string starting with arm-xilinx-linux-gnueabi-gcc ... \--sysroot=....$CC -o loopback_test loopback_test.c
file loopback_test.
Transfer the binary to the board.
scp loopback_test petalinux@192.168.2.99:/home/petalinux/
loopback_test to the BOOT drive (the one Windows opens).# Copy from /boot (PetaLinux automatically mounts the FAT32 partition here)
cp /boot/loopback_test ~/loopback_test
# Make it executable
chmod +x ~/loopback_test
# You generally need root privileges to access /dev/mem
sudo ./loopback_test
Error: “Segmentation Fault”
Error: “Read 0 from Register 1”
Congratulations! You have completed the full embedded stack.
This loopback_test program is the foundation of every driver you will ever write. Whether you are controlling a $100,000 radar system or a simple LED, the mechanism (Base Address + Offset) is exactly the same.
You’ve built the system, but the development cycle is slow. Let’s speed it up.