Saturday, May 2, 2026

AbstractX Update: Moving to QMTECH Zynq-7020 with Python TUN/DMA Bring-Up

I’ve been doing a direction update on AbstractX, and the current focus is now shifting toward a QMTECH Zynq-7020 platform for the next phase of bring-up and integration work.

The Gowin-based targets are still useful and working reference points, but for the next round of development I wanted a platform that makes it easier to combine Linux, DMA, FPGA logic, and userspace networking experiments in one place. The QMTECH Zynq-7020 board is a good fit for that.

Why Zynq-7020?

The main reason is practical integration.

AbstractX already has the pieces for transport, switching, and host interaction, but moving toward a Zynq platform opens up a much smoother path for:

  • Linux-based userspace tooling
  • DMA-backed transport experiments
  • TUN/TAP style networking interfaces
  • tighter software/hardware iteration
  • eventual real-time tuning work

This is not about replacing the smaller FPGA targets. It is about creating a stronger system integration platform.

Current software direction

The software path is intentionally staged:

  • Python first
  • Rust next
  • kernel later only if needed

Right now, Python is the fastest way to get the system moving. It’s easy to inspect, easy to modify, and ideal for validating transport behavior while the board, Linux image, DMA path, and framing details are still evolving.

To support that, I added a unified Python userspace TUN bridge that can target either SPI or DMA-style backends.

After the behavior is proven in Python, the plan is to move the long-running bridge into Rust userspace. That should give a cleaner and more durable implementation for sustained TUN + DMA operation without jumping too early into kernel complexity.

The kernel path is still on the table, but only if actual measurements show that userspace cannot meet latency or jitter requirements.

CRC policy update

One specific design choice I wanted to make explicit:

  • CRC stays enabled for SPI / serial-style transport
  • CRC is disabled by default for DMA
  • the CRC field is still retained in the frame format for compatibility

That split is intentional.

SPI is an external serialized link and benefits from link-level error detection. DMA, on the other hand, is an in-box trusted path, so doing per-frame CRC there usually adds overhead without much value during normal operation.

Buildroot first, bleeding edge later

For Linux bring-up, I’m keeping the plan simple:

  • start from a Buildroot baseline
  • get the board, FPGA flow, DMA path, and userspace bridge working
  • only then decide whether a newer kernel or more RT-focused stack is worth the churn

That avoids turning early bring-up into a moving-target problem.

JTAG / bring-up notes

For debug and hardware access, I also documented the Pico/XVC path using the xvc-pico project. That gives a low-cost way to expose JTAG through XVC and hook into Vivado Hardware Manager without needing more specialized hardware right away.

There was also an important USB reminder worth capturing for Zynq bring-up: some external ULPI PHY setups may need the usb-nop-xceiv device-tree path, plus reset GPIO wiring and the matching kernel config.

Repo updates

The repository now includes:

  • Python TUN/DMA bridge scaffolding
  • Rust TUN/DMA bridge scaffolding
  • updated QMTECH Zynq-7020 notes
  • clarified CRC policy in the direction docs
  • Buildroot output directory conventions
  • updated engineering log entries explaining the why behind these choices

Bottom line

The short version is this:

AbstractX is now being pushed toward a QMTECH Zynq-7020 + Linux + DMA + TUN workflow, with Python for bring-up and Rust for the next-stage hardened userspace bridge.

That should give me a better platform for validating the real shape of the system before deciding how much belongs in userspace, how much belongs in the kernel, and what the real-time tradeoffs actually look like.

More soon as the board bring-up progresses.

Sunday, June 1, 2025

Gowin EDA on Ubuntu 24.04.2 LTS

To run on Ubuntu open terminal

 cd into the gowin installed directory where bin lib directories are after the untar

 export LD_LIBRARY_PATH=$PWD/lib 

export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libfreetype.so 

export QT_XCB_GL_INTEGRATION=none

./gw_ide

Saturday, May 31, 2025

Using Gowin IDE on ubuntu 22.04

 Need to install QT

   sudo apt install -y qtcreator qtbase5-dev qt5-qmake cmake

Open terminal and use

cd <to where the gowin tools are>

there should be a bin and library dir then


export LD_LIBRARY_PATH=$PWD/lib

cd bin

./gw_ide

Monday, April 7, 2025

Raspberry PI Zero freeing up serial port /dev/ttyAMA0 for

The Pi Zeros only have two serial ports, only one can be mapped to the GPIO pin header at a time.  So, how to free up the serial port for a user/app to use?

This is for a buildroot generated file system.

The edit cmdline.txt:

root=/dev/mmcblk0p2 rootwait console=tty1 console=ttyAMA0,115200

remove console=ttyAMA,115200
so it looks like this:
root=/dev/mmcblk0p2 rootwait console=tty1

next edit /etc/inittab to start a tty shell
add this line at the bottom of the file:
ttyGS0::respawn:/sbin/getty -L ttyGS0 115200 vt100

edit config.txt 
add dtoverlay=dwc2 

This will start the OTG USB driver. 

I did something simple, edit S40network add:
     modprobe dwc2
      modprobe g_serial

This will start the serial interface.  A new serial interface will be created /dev/ttyGS0  

I'm using a Linux PC, so, when the USB is plugged in Zero will create a serial connection to Linux PC over USB OTG.  Then use minicom --dev /dev/ttyACM0 








Tuesday, August 29, 2023

Sipeed M1S Dock Hello world how to

 Sipeed is selling a M1s Dock at several places pricing is around $13USD.  The main processor, has uses the BL808 which has 3 processors:

  1. RISC-V 64 bit RV64IMAFCV (D0)
    • Uses serial pins
       
  2. RISC-V 32 bit RV32IMAFCP (M0)
    • /dev/ttyUSB1
  3. RISC-V 32 bit  RV32EMC (LP)
    • (Have not tested)

Friday, August 26, 2022

SPI slave as a Wishbone master for offloading Pi Zero Flight controller (BaseFlight)

(This is still work in progress but wanted to publish it and keep working on it)

 The Raspberry PI zero has a good price performance ratio, the 2W even better.  I have been working on porting BaseFlight (Flight controller software for STM32 micro-controller) to Linux running on the PI.  The code is working with a MPU9250.  

The hardware will be a PI Zero or a PI Zero 2W (Still need one),  SPI0.0 to the IMU, the SPI0.1 connected to the FPGA.   The FPGA will create the signals needed for OneShot, decode 6 channels of PWM  (I have an old Transmitter that supports 6 channels of PWM), and LED controller.  

Will be using the CS7Mod, its small, has 4 LEDs and 1 RGB.  LEDS will be used for status indication for BaseFlight.

So, what will be the communication protocol over SPI?  Simple Wishbone bus instructions, Read/Write using address and data.  

The WishBone master is interface to the SPI Slave, so the PI Zero will be the master.  Here is commands for a read and write

  • Command string
    • 1 byte command, either Read/Write
    • 4 byte address Highest address first  (Big Endian)
    • 2 byte length: first byte is the highest followed by the lowest
    • 4 bytes of data, first byte is the lowest byte.  (Little endian)
  • Read: example using command line: 
    • printf '\xA1\x1\x2\x3\x0\x0\x4\xf\x00\x00\x0\x0' | spi-pipe -m 0 -s 1000000 -d /dev/spidev0.1 | hexdump -C
  • Wite: 
    • printf '\xA2\x1\x2\x3\x0\x0\x4\xf\xff\xff\x11\0' | spi-pipe -m 0 -s 1000000 -d /dev/spidev0.1 | hexdump -C
The reason for using a Wishbone bus:
  1. Simple
  2. Use existing code for Wishbone Master 
  3. Easy to test
  4. Extendable (More on that later)

Right now, there are 3 wishbone peripherals
  1. LED controller
    1. address 0: set 32 bit register
    2. address 4: xor 32 bit register (blink)
    3. address 8: clear 32 bit register
  2. PWM decoder 
    1. 6 32 bit registers.  
  3. One Shot generator
    1. 4 32 bit registers
Here is screen shot of vivado, this is only for LED controller:








Tuesday, October 20, 2020

Using asyncio with libgpiod on an OrangePI Zero

 Python is a great tool to create simple/fast test code, combine that with libgpiod, now you can toggle and monitor gpio pins.  

In recent Linux kernels libgpiod is a library to write C/C++ and Python to toggle and monitor general purpose pins.   There are plenty examples for python.  I use buildroot to create a Linux distro, one option under hardware is to install libgpiod and if you have python3 selected now you can write python scripts. 

Here is two basic classes gpiowrapper.py:

import gpiod
import threading
import asyncio
import logging

class OutputPin:

def __init__(self, chip ="gpiochip0", pin = 0):

logging.debug("args chip={0} pin={1}".format(chip, pin))
self.chip = gpiod.Chip(chip)

if self.chip is None:
logging.error("chip not found")
else:
logging.debug("type {0} {1}".format(type(self.chip), type(pin)))

self.line = self.chip.get_line(pin)
self.line.request(consumer=chip, type=gpiod.LINE_REQ_DIR_OUT)

def set_level(self, level=0):
if level:
self.line.set_value(1)
else:
self.line.set_value(0)

class EventPin:

def __init__(self, chip = "gpiochip0", pin=0 ):
try:
self._pin = pin
self.chip = gpiod.Chip(chip)
if self.chip is None:
logging.error("chip not found")
else:
logging.debug("type {0} {1}".format(type(self.chip), type(pin)))

self.line = self.chip.get_line(pin)

self.line.request(consumer=chip, type=gpiod.LINE_REQ_EV_RISING_EDGE)
self.on_wait = asyncio.Queue()

asyncio.get_running_loop().add_reader( self.line.event_get_fd(), self.event_cb)

except Exception as ex:
logging.error("Error: {0}".format(str(ex)))

def event_cb(self):
event = self.line.event_read()
if event.type == gpiod.LineEvent.RISING_EDGE:
evstr = ' RISING EDGE'
elif event.type == gpiod.LineEvent.FALLING_EDGE:
evstr = 'FALLING EDGE'
else:
raise TypeError('Invalid event type')

logging.debug(" event {0}".format(evstr))
self.on_wait.put_nowait(event.type)
async def wait(self):
await self.on_wait.get()




 

The EventPin class can be used with asyncio to wait for Edge Triggered GPIO.  The key line is adding file descriptor of the event pin to asyncio and perform a callback when the FD is changes and push the event into a queue so await can be used. 

Here is a simple blink.py:

import gpiod
import sys
import time
import argparse
import gpiowrapper
import asyncio
import logging

async def blink(io_chip, o_pin):

logging.debug("blink")
pin = gpiowrapper.OutputPin(chip=io_chip, pin=int(o_pin))

while True:
pin.set_level(0)
await asyncio.sleep(0.1)
pin.set_level(1)
await asyncio.sleep(0.1)


async def toggleTask( o_pin, timeout):
try:
while True:
o_pin.set_level(0)
await asyncio.sleep(timeout)
o_pin.set_level(1)
await asyncio.sleep(timeout)
except Exception as ex:
logging.error("error {0}".format(str(ex)))

async def monitor_event(io_chip, output_pin, event_pin):

o_pin = gpiowrapper.OutputPin(io_chip, output_pin)
evt_pin = gpiowrapper.EventPin(io_chip, event_pin)

toggle_task = asyncio.create_task(toggleTask(o_pin, .5))

while True:
val = await evt_pin.wait()
if val == gpiod.LineEvent.RISING_EDGE:
logging.debug("Rising edge event")
elif val == gpiod.LineEvent.FALLING_EDGE:
logging.debug("Falling edge event")

if __name__ == "__main__":
logging.basicConfig(format='%(asctime)s %(levelname)s %(filename)s:%(lineno)s - %(funcName)20s() %(message)s', level=logging.DEBUG)
# execute only if run as a script
# Create the parser
my_parser = argparse.ArgumentParser(description='List the content of a folder')

# Add the arguments
my_parser.add_argument('-chip', help='chip ')
my_parser.add_argument('-opin', type=int, help='output pin')
my_parser.add_argument('--epin', type=int, help='event pin')
my_parser.add_argument('--blink', help='event pin')
my_parser.add_argument('--event', help='event pin')

# Execute the parse_args() method
args = my_parser.parse_args()
logging.debug("main")

if args.blink:
logging.debug("run blink")
asyncio.run(blink( args.chip, args.opin))
if args.event:
logging.debug("monitor")
asyncio.run(monitor_event(args.chip, args.opin, args.epin))

 

Load both files onto the Orange PI Zero. 

python blink.py -chip gpiochip0 -opin 1 --epin 0 --event True

This will use  PIN11 (GPIO1) for output and toggle at 1 second.  Pin13 will wait for Rising Edge trigger.  If you connect both pins together then you will get:

# python blink.py -chip gpiochip0 -opin 1 --epin 0 --event True
1970-01-01 00:47:21,419 DEBUG blink.py:60 -             <module>() main
1970-01-01 00:47:21,420 DEBUG blink.py:66 -             <module>() monitor
1970-01-01 00:47:21,421 DEBUG selector_events.py:59 -             __init__() Using selector: EpollSelector
1970-01-01 00:47:21,424 DEBUG gpiowrapper.py:10 -             __init__() args chip=gpiochip0 pin=1
1970-01-01 00:47:21,558 DEBUG gpiowrapper.py:16 -             __init__() type <class 'gpiod.Chip'> <class 'int'>
1970-01-01 00:47:21,648 DEBUG gpiowrapper.py:36 -             __init__() type <class 'gpiod.Chip'> <class 'int'>
1970-01-01 00:47:22,153 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:23,156 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:24,159 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:25,161 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:26,164 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:27,167 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:28,170 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:29,172 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:30,175 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:31,178 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:32,180 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:33,183 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:34,186 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:35,188 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:36,191 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:37,194 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:38,197 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:39,199 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:40,202 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:41,205 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1970-01-01 00:47:42,207 DEBUG gpiowrapper.py:57 -             event_cb()  event  RISING EDGE
1