Introduce Rust FSBL
Some checks failed
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled

This PR introduces some major features while also changing the project structure to be more flexible
for multiple platforms (e.g. host tooling). It also includes a lot of
bugfixes, renamings for consistency purposes and dependency updates.

Added features:

1. Pure Rust FSBL for the Zedboard. This first variant is simplistic. It
   is currently only capable of QSPI boot. It searches for a bitstream
   and ELF file inside the boot binary, flashes them and jumps to them.
2. QSPI flasher for the Zedboard.
3. DDR, QSPI, DEVC, private CPU timer and PLL configuration modules
3. Tooling to auto-generate board specific DDR and DDRIOB config
   parameters from the vendor provided ps7init.tcl file

Changed project structure:

1. All target specific project are inside a dedicated workspace inside
   the `zynq` folder now.
2. All tool intended to be run on a host are inside a `tools` workspace
3. All other common projects are at the project root

Major bugfixes:

1. SPI module: CPOL was not configured properly
2. Logger flush implementation was empty, implemented properly now.
This commit is contained in:
2025-08-01 14:32:08 +02:00
committed by Robin Mueller
parent 0cf5bf6885
commit 5d0f2837d1
166 changed files with 9496 additions and 979 deletions

22
scripts/memory_ddr.x Normal file
View File

@@ -0,0 +1,22 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
recommended for something like DMA descriptors. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
}
REGION_ALIAS("DATA", CODE);
SECTIONS
{
/* Uncached memory */
.uncached (NOLOAD) : ALIGN(4) {
. = ALIGN(4);
_sbss_uncached = .;
*(.uncached .uncached.*);
. = ALIGN(4);
_ebss_uncached = .;
} > UNCACHED
}

24
scripts/memory_ocm.x Normal file
View File

@@ -0,0 +1,24 @@
MEMORY
{
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
to configuring the page at address 0x400_0000 accordingly */
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
}
REGION_ALIAS("DATA", CODE);
SECTIONS
{
/* Uncached memory */
.uncached (NOLOAD) : ALIGN(4) {
. = ALIGN(4);
_sbss_uncached = .;
*(.uncached .uncached.*);
. = ALIGN(4);
_ebss_uncached = .;
} > UNCACHED
}

View File

@@ -1,20 +0,0 @@
#!/bin/bash
# Exit if no arguments are provided
if [ "$#" -eq 0 ]; then
echo "Error: No arguments provided."
echo "Usage: run.sh <binary>"
exit 1
fi
# Get the absolute path to the `scripts/` directory
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
# Get the absolute path to the project root
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Run the initialization script
"$SCRIPT_DIR/zynq7000-init.py"
# Run the GDB debugger in GUI mode.
gdb-multiarch -q -x gdb.gdb "$@" -tui

View File

@@ -1,3 +0,0 @@
#!/bin/bash
cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p zynq7000-hal
cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p zynq7000

144
scripts/xsct-flasher.tcl Normal file
View File

@@ -0,0 +1,144 @@
if {[info exists env(ip_address_hw_server)]} {
set ip $env(ip_address_hw_server)
} else {
set ip "localhost"
}
# Defaults
set init_tcl ""
set app ""
set bitstream ""
# Usage helper
proc usage {} {
puts "Usage: xsct xsct-helper.tcl <init.tcl> \[-a|--app app.elf\] \[-b|--bit design.bit]"
puts "Options:"
puts " -a, --app Path to application ELF to download"
puts " -b, --bit Path to FPGA bitstream (.bit) to program"
puts " -h, --help Show this help"
}
# Compact, robust parser
set expecting ""
set endopts 0
foreach arg $argv {
# If previous option expects a value, take this arg
if {$expecting ne ""} {
set $expecting $arg
set expecting ""
continue
}
# Option handling (until we see --)
if {!$endopts && [string match "-*" $arg]} {
if {$arg eq "--"} { set endopts 1; continue }
if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 }
if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue }
if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue }
puts "error: unknown option: $arg"; usage; exit 1
}
# Positional: expect only <init.tcl>
if {$init_tcl eq ""} {
set init_tcl $arg
} else {
puts "error: unexpected positional argument: $arg"
usage
exit 1
}
}
# Validate required init script
if {$init_tcl eq ""} {
puts "error: missing required first argument pointing to initialization TCL script"
usage
exit 1
}
if {![file exists $init_tcl]} {
puts "error: the PS init tcl script '$init_tcl' does not exist"
exit 1
}
# Resolve app: CLI takes precedence over env(APP)
if {$app ne ""} {
if {![file exists $app]} {
puts "error: the app file '$app' does not exist"
exit 1
}
} elseif {[info exists env(APP)]} {
if {[file exists $env(APP)]} {
set app $env(APP)
} else {
puts "warning: APP environment variable is set but file does not exist: $env(APP)"
}
}
# Validate bitstream if provided
if {$bitstream ne "" && ![file exists $bitstream]} {
puts "error: the bitstream file '$bitstream' does not exist"
exit 1
}
puts "Hardware server IP address: $ip"
connect -url tcp:$ip:3121
set devices [targets]
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
set apu_device_num [string index $apu_line 0]
set arm_core_0_num [string index $arm_core_0_line 0]
set fpga_device_num [string index $fpga_line 0]
puts "Select ps target with number: $apu_device_num"
# Select the target
target $apu_device_num
# Resetting the target involved problems when an image is stored on the flash.
# It has turned out that it is not essential to reset the system before loading
# the software components into the device.
puts "Reset target"
# TODO: Make the reset optional/configurable via input argument.
# Reset the target
rst
# Check if bitstream is set and the file exists before programming FPGA
if {$bitstream eq ""} {
puts "Skipping bitstream programming (bitstream argument not set)"
} elseif {![file exists $bitstream]} {
puts "Error: The bitstream file '$bitstream' does not exist"
} else {
puts "Set FPGA target with number: $fpga_device_num"
target $fpga_device_num
# Without this delay, the FPGA programming may fail
after 1500
puts "Programming FPGA with bitstream: $bitstream"
fpga -f $bitstream
}
puts "Set ps target with device number: $arm_core_0_num"
targets $arm_core_0_num
puts "Initialize processing system"
# Init processing system
source $init_tcl
ps7_init
ps7_post_config
puts "Set arm core 0 target with number: $arm_core_0_num"
target $arm_core_0_num
if {$app ne ""} {
puts "Download app $app to target"
dow $app
puts "Starting app"
con
}
puts "Success"

View File

@@ -1,86 +0,0 @@
if {[info exists env(ip_address_hw_server)]} {
set ip $env(ip_address_hw_server)
} else {
set ip "localhost"
}
set init_tcl ""
if {[llength $argv] >= 1} {
set init_tcl [lindex $argv 0]
} else {
puts "error: missing required first argument pointing to initialization TCL script"
exit 1
}
if {![file exists $init_tcl]} {
puts "the ps init tcl script '$init_tcl' does not exist"
exit 0
}
# parse command-line arguments
set bitstream ""
if {[llength $argv] >= 2} {
set bitstream [lindex $argv 1]
}
puts "Hardware server IP address: $ip"
connect -url tcp:$ip:3121
set devices [targets]
set apu_line [string trim [targets -nocase -filter {name =~ "APU"}]]
set arm_core_0_line [string trim [targets -nocase -filter {name =~ "ARM Cortex-A9 MPCore #0"}]]
set fpga_line [string trim [targets -nocase -filter {name =~ "xc7z020"}]]
set apu_device_num [string index $apu_line 0]
set arm_core_0_num [string index $arm_core_0_line 0]
set fpga_device_num [string index $fpga_line 0]
puts "Select ps target with number: $apu_device_num"
# Select the target
target $apu_device_num
# Resetting the target involved problems when an image is stored on the flash.
# It has turned out that it is not essential to reset the system before loading
# the software components into the device.
puts "Reset target"
# TODO: Make the reset optional/configurable via input argument.
# Reset the target
rst
# Check if bitstream is set and the file exists before programming FPGA
if {$bitstream eq ""} {
puts "Skipping bitstream programming (bitstream argument not set)"
} elseif {![file exists $bitstream]} {
puts "Error: The bitstream file '$bitstream' does not exist"
} else {
puts "Set FPGA target with number: $fpga_device_num"
target $fpga_device_num
# Without this delay, the FPGA programming may fail
after 1500
puts "Programming FPGA with bitstream: $bitstream"
fpga -f $bitstream
}
puts "Set ps target with device number: $arm_core_0_num"
targets $arm_core_0_num
puts "Initialize processing system"
# Init processing system
source $init_tcl
ps7_init
ps7_post_config
puts "Set arm core 0 target with number: $arm_core_0_num"
target $arm_core_0_num
if {[info exists env(APP)] && [file exists $env(APP)]} {
puts "Download app $env(APP) to target"
dow $env(APP)
}
puts "Successful"

View File

@@ -7,7 +7,7 @@ import sys
from pathlib import Path
# Define the default values
TCL_SCRIPT_NAME = "xsct-init.tcl"
TCL_SCRIPT_NAME = "xsct-flasher.tcl"
SCRIPTS_DIR = "scripts"
DEFAULT_IP_ADDRESS_HW_SERVER = "localhost"
@@ -55,7 +55,7 @@ def main():
default=os.getenv("TCL_INIT_SCRIPT"),
help="Path to the ps7 initialization TCL file to prepare the processing system.\n"
"You can also set the TCL_INIT_SCRIPT env variable to set this.\n"
"It is also set implicitely when specifying the SDT folder with --sdt"
"It is also set implicitely when specifying the SDT folder with --sdt",
)
parser.add_argument(
"-b",
@@ -63,7 +63,7 @@ def main():
dest="bit",
default=os.getenv("ZYNQ_BITSTREAM"),
help="Optional path to the bitstream which will also be programed to the device. It is"
" also searched automatically if the --sdt option is used.\n"
" also searched automatically if the --sdt option is used.\n",
)
args = parser.parse_args()
@@ -91,9 +91,6 @@ def main():
print(f"The app '{args.app}' does not exist")
sys.exit(1)
# Export environment variables
if args.app:
os.environ["APP"] = args.app
os.environ["IP_ADDRESS_HW_SERVER"] = args.ip
init_tcl = None
bitstream = None
@@ -124,7 +121,9 @@ def main():
bitstream = args.bit
init_tcl = args.init_tcl
xsct_script = Path(TCL_SCRIPT_NAME)
# Get the script's directory
script_dir = Path(__file__).resolve().parent
xsct_script = script_dir / TCL_SCRIPT_NAME
if not xsct_script.exists():
xsct_script = Path(os.path.join(SCRIPTS_DIR, TCL_SCRIPT_NAME))
@@ -136,7 +135,11 @@ def main():
# Prepare tcl_args as a list to avoid manual string concatenation issues
cmd_list = ["xsct", str(xsct_script), init_tcl]
if bitstream:
cmd_list.append("--bit")
cmd_list.append(bitstream)
if args.app:
cmd_list.append("--app")
cmd_list.append(args.app)
# Join safely for shell execution
xsct_cmd = shlex.join(cmd_list)