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

View File

@@ -1,24 +1,3 @@
[target.armv7a-none-eabihf]
runner = "./scripts/runner.sh"
rustflags = [
"-Ctarget-cpu=cortex-a9",
"-Ctarget-feature=+vfp3",
"-Ctarget-feature=+neon",
"-Clink-arg=-Tlink.x",
# If this is not enabled, debugging / stepping can become problematic.
"-Cforce-frame-pointers=yes",
# Can be useful for debugging.
# "-Clink-args=-Map=app.map"
]
# Tier 3 target, so no pre-compiled artifacts included.
[unstable]
build-std = ["core", "alloc"]
[build]
target = "armv7a-none-eabihf"
[env]
# The following two env variables need to be set for the supplied runner.sh script to work.

View File

@@ -7,41 +7,69 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
# Copy config file for rustflags and to build core/alloc.
- run: cp .cargo/def-config.toml .cargo/config.toml
- run: cargo check --target armv7a-none-eabihf -p zynq7000
- run: cargo check --target armv7a-none-eabihf -p zynq7000-rt
- run: just check zynq
- uses: dtolnay/rust-toolchain@stable
- run: just check tools
- run: just check zynq7000-boot-image
build:
name: Check build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: just build zynq
- uses: dtolnay/rust-toolchain@stable
- run: just build tools
- run: just build zynq7000-boot-image
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: extractions/setup-just@v3
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
- run: just fmt zynq
- run: just fmt tools
- run: just fmt zynq7000-boot-image
docs:
name: Check Documentation Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- uses: dtolnay/rust-toolchain@nightly
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-rt --all-features
- run: RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000 --all-features
with:
components: rust-src
- run: just docs-zynq
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy, rust-src
# Copy config file for rustflags and to build core/alloc.
- run: cp .cargo/def-config.toml .cargo/config.toml
- run: cargo clippy --target armv7a-none-eabihf -- -D warnings
- run: just clippy zynq
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: just clippy tools
- run: just clippy zynq7000-boot-image

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
/target
target
/app.map
/xsct-output.log
/.vscode

View File

@@ -1,23 +0,0 @@
[workspace]
resolver = "3"
members = [
"zynq7000-rt",
"zynq7000",
"zynq7000-hal",
"zynq7000-embassy",
"examples/simple",
"examples/embassy",
"examples/zedboard",
"zynq-mmu",
]
exclude = ["experiments"]
# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false # <-
incremental = false
lto = true
opt-level = 3 # <-
overflow-checks = false # <-

View File

@@ -6,36 +6,60 @@ family of SoCs.
# List of crates
This workspace contains the following crates:
This project contains the following crates:
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-rt)
## [Zynq Workspace](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq)
This workspace contains libraries and application which can only be run on the target system.
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-rt)
run-time crate containing basic low-level startup code necessary to boot a Rust app on the
Zynq7000.
- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000) PAC
crate containing basic low-level register definition.
- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-hal)
- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000) PAC
crate containing basic low-level register definitions.
- The [`zynq7000-mmu`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal)
crate containing common MMU abstractions used by both the HAL and the run-time crate.
- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal)
HAL crate containing higher-level abstractions on top of the PAC register crate.
- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-embassy)
crate containing support for running the embassy-rs RTOS.
- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-embassy)
crate containing support for running the embassy-rs asynchronous run-time.
This project was developed using a Zedboard, so there are several crates available targeted towards
this board:
- The [`zedboard-bsp`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-bsp)
crate containing board specific components for the Zedboard.
- The [`zedboard-fsbl`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-fsbl)
contains a simple first-stage bootloader application for the Zedboard.
- The [`zedboard-qspi-flasher`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-qspi-flasher)
contains an application which is able to flash a boot binary from DDR to the QSPI.
It also contains the following helper crates:
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples)
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/examples)
folder contains various example applications crates using the HAL and the PAC.
This folder also contains dedicated example applications using the
[`embassy`](https://github.com/embassy-rs/embassy) native Rust RTOS.
The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design)
folder contains a sample FPGA design and block design which was used in
some of the provided software examples. The project was created with Vivado version 2024.1.
The folder contains a README with all the steps required to load this project from a TCL script.
## Other libraries and tools
- The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design)
folder contains a sample FPGA design and block design which was used in some of the provided software examples. The project was created with Vivado version 2024.1.
The folder contains a README with all the steps required to load this project from a TCL script.
- The [`zynq7000-boot-image`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-boot-image)
library contains generic helpers to interface with the AMD
[boot binary](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide).
- The [`tools/zynq7000-ps7init-extract`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract)
tool allows extracting configuration from the AMD generated `ps7init.tcl` file which contains
static configuration parameters for DDR initialization.
# Using the `.cargo/config.toml` file
This is mostly relevant for development directly inside this repostiory.
Use the following command to have a starting `config.toml` file
```sh
cp .cargo/def-config.toml .cargo/config.toml
cp .cargo/config.toml.template .cargo/config.toml
```
You then can adapt the `config.toml` to your needs. For example, you can configure runners

View File

@@ -1,157 +0,0 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r";
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
unsafe {
gic.enable_interrupts();
}
let mut gpio_pins = GpioPins::new(dp.gpio);
// Set up global timer counter and embassy time driver.
let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(200));
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
let mut emio_leds: [Output; 8] = [
Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), PinState::Low),
Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), PinState::Low),
];
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await; // Wait for the next cycle of the ticker
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

45
justfile Normal file
View File

@@ -0,0 +1,45 @@
all: check-all build-all clean-all fmt-all clippy-all docs-zynq
check-all: (check "zynq") (check "tools") (check "zynq7000-boot-image")
clean-all: (clean "zynq") (clean "tools") (clean "zynq7000-boot-image")
build-all: build-zynq (build "tools") (build "zynq7000-boot-image")
fmt-all: (fmt "zynq") (fmt "tools") (fmt "zynq7000-boot-image")
clippy-all: (clippy "zynq") (clippy "tools") (clippy "zynq7000-boot-image")
check target:
cd {{target}} && cargo check
build target:
cd {{target}} && cargo build
build-zynq: (build "zynq")
cd "zynq/zedboard-fsbl" && cargo build --release
clean target:
cd {{target}} && cargo clean
fmt target:
cd {{target}} && cargo +stable fmt --all -- --check
clippy target:
cd {{target}} && cargo clippy -- -D warnings
[working-directory: 'zynq']
docs-zynq:
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-hal --features alloc
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-mmu
RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" cargo +nightly doc -p zynq7000-rt
[working-directory: 'zynq-boot-image/staging']
bootgen:
bootgen -arch zynq -image boot.bif -o boot.bin -w on
echo "Generated boot.bin at zynq-boot-image/staging"
[no-cd]
run binary:
# Run the initialization script. It needs to be run inside the justfile directory.
python3 {{justfile_directory()}}/scripts/zynq7000-init.py
# Run the GDB debugger in GUI mode.
gdb-multiarch -q -x {{justfile_directory()}}/zynq/gdb.gdb {{binary}} -tui

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)

796
tools/Cargo.lock generated Normal file
View File

@@ -0,0 +1,796 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.60.2",
]
[[package]]
name = "arbitrary-int"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
[[package]]
name = "arbitrary-int"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281"
[[package]]
name = "arm-targets"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3371884971a96d71d8bd4e781188a7d327d7e5e455d07ef4c352922c66695e9e"
[[package]]
name = "bitbybit"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762"
dependencies = [
"arbitrary-int 1.3.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "boot-image-test"
version = "0.1.0"
dependencies = [
"clap",
"zynq7000-boot-image",
]
[[package]]
name = "clap"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
]
[[package]]
name = "cortex-ar"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ea2a354642e242870bc43b57a517359b0be6e96d302b2811cd0644c979c54e"
dependencies = [
"arbitrary-int 2.0.0",
"arm-targets",
"bitbybit",
"num_enum",
"thiserror",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "deranged"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
dependencies = [
"powerfmt",
]
[[package]]
name = "derive-mmio"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005a6dabf68a87a460d3cb9b8e2fd5de3f474fc34e8d9451f5a1b6db518da143"
dependencies = [
"derive-mmio-macro",
"rustversion",
]
[[package]]
name = "derive-mmio-macro"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "584dc8e12e4aeb88000c2be8ef7db15657c817fba3e77999a24807d1efcdeefa"
dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.176"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "mmu-table-gen"
version = "0.1.0"
dependencies = [
"zynq7000-mmu",
"zynq7000-rt",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num_enum"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
dependencies = [
"critical-section",
"portable-atomic",
]
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "simple_logger"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb"
dependencies = [
"colored",
"log",
"time",
"windows-sys 0.48.0",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "zynq7000"
version = "0.1.0"
dependencies = [
"arbitrary-int 2.0.0",
"bitbybit",
"derive-mmio",
"once_cell",
"rustversion",
"static_assertions",
"thiserror",
]
[[package]]
name = "zynq7000-boot-image"
version = "0.1.0"
dependencies = [
"arbitrary-int 2.0.0",
"bitbybit",
"thiserror",
]
[[package]]
name = "zynq7000-mmu"
version = "0.1.0"
dependencies = [
"cortex-ar",
"thiserror",
]
[[package]]
name = "zynq7000-ps7init-extract"
version = "0.1.0"
dependencies = [
"clap",
"log",
"proc-macro2",
"quote",
"regex",
"simple_logger",
"syn",
"zynq7000",
]
[[package]]
name = "zynq7000-rt"
version = "0.1.0"
dependencies = [
"arbitrary-int 2.0.0",
"cortex-ar",
"zynq7000-mmu",
]

7
tools/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[workspace]
resolver = "3"
members = [
"boot-image-test",
"mmu-table-gen",
"zynq7000-ps7init-extract",
]

1
tools/boot-image-test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

305
tools/boot-image-test/Cargo.lock generated Normal file
View File

@@ -0,0 +1,305 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "arbitrary-int"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
[[package]]
name = "arbitrary-int"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281"
[[package]]
name = "bitbybit"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762"
dependencies = [
"arbitrary-int 1.3.0",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap"
version = "4.5.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tester"
version = "0.1.0"
dependencies = [
"clap",
"zynq-boot-image",
]
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "zynq-boot-image"
version = "0.1.0"
dependencies = [
"arbitrary-int 2.0.0",
"bitbybit",
"thiserror",
]

View File

@@ -0,0 +1,8 @@
[package]
name = "boot-image-test"
version = "0.1.0"
edition = "2024"
[dependencies]
zynq7000-boot-image= { path = "../../zynq7000-boot-image" }
clap = { version = "4", features = ["derive"] }

View File

@@ -0,0 +1,82 @@
//! Small tester app to verify some of the API offers by the zynq-boot-image crate.
use std::{io::Read, path::Path};
use clap::Parser as _;
use zynq7000_boot_image::{BootHeader, FIXED_BOOT_HEADER_SIZE};
#[derive(clap::Parser, Debug)]
#[command(version, about)]
pub struct Cli {
/// Path to boot.bin file to test.
#[arg(short, long)]
path: String,
}
fn main() {
let cli = Cli::parse();
let boot_bin = Path::new(&cli.path);
if !boot_bin.exists() {
eprintln!("File not found: {}", boot_bin.display());
std::process::exit(1);
}
let mut boot_bin_file = std::fs::File::open(boot_bin).expect("failed to open boot.bin file");
let mut header_buf = Box::new([0u8; 8192]);
boot_bin_file
.read_exact(&mut header_buf[0..FIXED_BOOT_HEADER_SIZE])
.expect("failed to read boot header");
let mut boot_header = BootHeader::new(&header_buf[0..FIXED_BOOT_HEADER_SIZE])
.expect("failed to parse boot header");
let source_offset = boot_header.source_offset();
boot_bin_file
.read_exact(&mut header_buf[FIXED_BOOT_HEADER_SIZE..source_offset - FIXED_BOOT_HEADER_SIZE])
.expect("failed to read full boot binary metadata");
// Re-assign with newly read data.
boot_header = BootHeader::new_unchecked(&header_buf[0..source_offset]);
let image_header_table = boot_header
.image_header_table()
.expect("failed extracting image header table");
let image_headers = image_header_table.count_of_headers();
println!(
"Image headers: {}, first image header offset {}, first partition header offset {}",
image_headers,
image_header_table
.first_image_header_offset()
.expect("failed reading first image header offset"),
image_header_table
.first_partition_header_offset()
.expect("failed reading first partition header offset")
);
let image_header_iter = boot_header
.image_header_iterator()
.expect("failed extracting boot header iterator");
for (idx, image_header) in image_header_iter.enumerate() {
println!("--------------------------------------");
println!(
"Image header {} with partition count {}",
idx,
image_header.partition_count()
);
let mut test: [u8; 64] = [0; 64];
let image_name = image_header
.image_name(&mut test)
.expect("image name error");
println!("image name: {}", image_name);
let partition_iter = image_header
.partition_header_iterator(header_buf.as_slice())
.unwrap();
if image_header.partition_count() > 0 {
println!("--------------------------------------");
}
for partition in partition_iter {
println!(
"partition with size {} and load address {:#08x}, section count {}",
partition.total_partition_length().unwrap(),
partition.destination_load_address(),
partition.section_count()
);
println!("section attributes: {:?}", partition.section_attributes());
}
println!("--------------------------------------\n\r");
}
}

View File

@@ -0,0 +1,8 @@
[package]
name = "mmu-table-gen"
version = "0.1.0"
edition = "2024"
[dependencies]
zynq7000-rt = { path = "../../zynq/zynq7000-rt", default-features = false }
zynq7000-mmu = { path = "../../zynq/zynq7000-mmu", features = ["tools"] }

View File

@@ -16,7 +16,7 @@ macro_rules! write_l1_section {
}
fn main() {
let file_path = "src/mmu_table.rs";
let file_path = "mmu_table.rs";
let file = File::create(file_path).expect("Failed to create file");
let mut offset = 0;
@@ -56,7 +56,7 @@ fn main() {
writeln!(buf_writer, "use crate::mmu::section_attrs;").unwrap();
writeln!(buf_writer, "use cortex_ar::mmu::L1Section;").unwrap();
writeln!(buf_writer, "use zynq_mmu::L1Table;").unwrap();
writeln!(buf_writer, "use zynq7000_mmu::L1Table;").unwrap();
writeln!(buf_writer, "").unwrap();
writeln!(buf_writer, "/// MMU Level 1 Page table.").unwrap();

View File

@@ -0,0 +1,2 @@
/ddrc_config_autogen.rs
/ddriob_config_autogen.rs

251
tools/zynq7000-ps7init-extract/Cargo.lock generated Normal file
View File

@@ -0,0 +1,251 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "clap"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "zynq7000-ps7init-extract"
version = "0.1.0"
dependencies = [
"clap",
]

View File

@@ -0,0 +1,14 @@
[package]
name = "zynq7000-ps7init-extract"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4", features = ["derive"] }
zynq7000 = { path = "../../zynq/zynq7000" }
log = "0.4"
simple_logger = "5"
regex = "1"
quote = "1"
syn = "2"
proc-macro2 = "1"

View File

@@ -0,0 +1,26 @@
Zynq7000 PS7 Init Extractor
=========
AMD provides tooling to auto-generate some of the hardware initialization for the external DDR
as native Rust code.
The AMD tooling generates these files as `ps7init.tcl`, `ps7init.c`, `ps7init.h` files but not as
Rust files. The specific parameters required for different DDR chips are proprietary, so that
portion is required for Rust programs as well. Do avoid the need of compiling the PS7 initialization
scripts with a C compiler, this tool extracts all required configuration parameters for DDR and
DDRIOB initialization and configuration and exports them as native Rust constants.
The generates files can be placed in individual projects or board support packages to initialize
the DDR in conjunction with the [Zynq7000 HAL library](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/fsbl-rs/zynq/zynq7000-hal).
Right now, the script expects the `ps7init.tcl` file to be passed as a command line argument
for `-p` or `--path`. It then generates the configuration as a `ddrc_config_autogen.rs` and
`ddrc_config_autogen.rs` file.
For example, assuming that there is a `ps7init.tcl` script in the current directory, you can use
```sh
cargo run -- --path ./ps7init.tcl
```
to generate the configuration files.

View File

@@ -0,0 +1,341 @@
use std::{collections::HashMap, ops::RangeInclusive, path::Path};
use clap::Parser as _;
use simple_logger::SimpleLogger;
const DDRC_ADDR_RANGE: RangeInclusive<u32> = 0xf800_6000..=0xf800_62b4;
const DDRIOB_ADDR_RANGE: RangeInclusive<u32> = 0xf800_0b40..=0xf800_0b68;
const DDRC_FILE_NAME: &str = "ddrc_config_autogen.rs";
const DDRIOB_FILE_NAME: &str = "ddriob_config_autogen.rs";
#[derive(clap::Parser, Debug)]
#[command(version, about)]
pub struct Cli {
/// Path to ps7init.tcl file.
#[arg(short, long)]
path: String,
}
fn extract_hex_values(line: &str) -> Option<(u32, u32, u32)> {
let re = regex::Regex::new(r"0[xX]([0-9A-Fa-f]+)").unwrap();
let captures: Vec<u32> = re
.captures_iter(line)
.filter_map(|cap| u32::from_str_radix(&cap[1], 16).ok())
.collect();
if captures.len() == 3 {
Some((captures[0], captures[1], captures[2]))
} else {
None
}
}
#[derive(Default)]
pub struct RegisterToValueMap(pub HashMap<u32, u32>);
impl RegisterToValueMap {
fn val_as_token(&self, reg_name: &str, addr: u32) -> proc_macro2::TokenStream {
let val = self.0.get(&addr).unwrap_or_else(|| {
panic!(
"failed to retrieve register value for register {}",
reg_name
)
});
format!("{:#010x}", val)
.parse::<proc_macro2::TokenStream>()
.unwrap()
}
}
enum ParsingMode {
DdrRev3,
MioRev3,
}
fn main() -> std::io::Result<()> {
SimpleLogger::new().init().unwrap();
let cli = Cli::parse();
let ps7init_tcl = Path::new(&cli.path);
if !ps7init_tcl.exists() {
log::error!("File not found: {}", ps7init_tcl.display());
std::process::exit(1);
}
let mut parsing_mode = None;
let mut reg_to_values = RegisterToValueMap::default();
for line in std::fs::read_to_string(ps7init_tcl)?.lines() {
match parsing_mode {
None => {
if line.contains("ps7_ddr_init_data_3_0") {
parsing_mode = Some(ParsingMode::DdrRev3);
} else if line.contains("ps7_mio_init_data_3_0") {
parsing_mode = Some(ParsingMode::MioRev3);
}
continue;
}
Some(ParsingMode::MioRev3) => {
if line.contains("}") {
parsing_mode = None;
continue;
}
}
Some(ParsingMode::DdrRev3) => {
if line.contains("}") {
parsing_mode = None;
continue;
}
}
}
if let Some((addr, _mask, value)) = extract_hex_values(line)
&& (DDRC_ADDR_RANGE.contains(&addr) || DDRIOB_ADDR_RANGE.contains(&addr))
&& addr % 4 == 0
{
// Only use first value.
if reg_to_values.0.contains_key(&addr) {
if addr != 0xF800_6000 {
log::warn!("detected duplicate register value for address {}", addr);
}
continue;
}
reg_to_values.0.insert(addr, value);
}
}
log::info!("generating DDRC config files: {}", DDRC_FILE_NAME);
generate_ddrc_config(&reg_to_values, DDRC_FILE_NAME)?;
log::info!("generating DDRIOB config files: {}", DDRIOB_FILE_NAME);
generate_ddriob_config(&reg_to_values, DDRIOB_FILE_NAME)?;
Ok(())
}
fn generate_ddrc_config(
reg_to_values: &RegisterToValueMap,
file_name: &str,
) -> std::io::Result<()> {
// Format as hex strings
let ddrc = reg_to_values.val_as_token("DDRC Control", 0xF800_6000);
let two_rank = reg_to_values.val_as_token("Two Rank", 0xF800_6004);
let hpr = reg_to_values.val_as_token("HPR", 0xF800_6008);
let lpr = reg_to_values.val_as_token("LPR", 0xF800_600C);
let wr = reg_to_values.val_as_token("WR", 0xF800_6010);
let dram_param_0 = reg_to_values.val_as_token("DRAM Reg0", 0xF800_6014);
let dram_param_1 = reg_to_values.val_as_token("DRAM Reg1", 0xF800_6018);
let dram_param_2 = reg_to_values.val_as_token("DRAM Reg2", 0xF800_601C);
let dram_param_3 = reg_to_values.val_as_token("DRAM Reg3", 0xF800_6020);
let dram_param_4 = reg_to_values.val_as_token("DRAM Reg4", 0xF800_6024);
let dram_init_param = reg_to_values.val_as_token("DRAM Init Param", 0xF800_6028);
let dram_emr = reg_to_values.val_as_token("DRAM EMR", 0xF800_602C);
let dram_emr_mr = reg_to_values.val_as_token("DRAM EMR MR", 0xF800_6030);
let dram_burst8_rdwr = reg_to_values.val_as_token("DRAM Burst8 RDWR", 0xF800_6034);
let dram_disable_dq = reg_to_values.val_as_token("DRAM Disable DQ", 0xF800_6038);
let dram_addr_map_bank = reg_to_values.val_as_token("DRAM Addr Map Bank", 0xF800_603C);
let dram_addr_map_col = reg_to_values.val_as_token("DRAM Addr Map Col", 0xF800_6040);
let dram_addr_map_row = reg_to_values.val_as_token("DRAM Addr Map Row", 0xF800_6044);
let dram_odt = reg_to_values.val_as_token("DRAM ODT", 0xF800_6048);
let phy_cmd_timeout_rddata_cpt = reg_to_values.val_as_token("PHY CMD Timeout", 0xF800_6050);
let dll_calib = reg_to_values.val_as_token("DLL Calib", 0xF800_6058);
let odt_delay_hold = reg_to_values.val_as_token("ODT Delay Hold", 0xF800_605C);
let ctrl_reg1 = reg_to_values.val_as_token("CTRL Reg 1", 0xF800_6060);
let ctrl_reg2 = reg_to_values.val_as_token("CTRL Reg 2", 0xF800_6064);
let ctrl_reg3 = reg_to_values.val_as_token("CTRL Reg 3", 0xF800_6068);
let ctrl_reg4 = reg_to_values.val_as_token("CTRL Reg 4", 0xF800_606C);
let ctrl_reg5 = reg_to_values.val_as_token("CTRL Reg 5", 0xF800_6078);
let ctrl_reg6 = reg_to_values.val_as_token("CTRL Reg 6", 0xF800_607C);
let che_t_zq = reg_to_values.val_as_token("CHE T ZQ", 0xF800_60A4);
let che_t_zq_short_interval_reg =
reg_to_values.val_as_token("CHE T ZQ Short Interval", 0xF800_60A8);
let deep_powerdown = reg_to_values.val_as_token("Deep Powerdown", 0xF800_60AC);
let reg_2c = reg_to_values.val_as_token("Reg 2C", 0xF800_60B0);
let reg_2d = reg_to_values.val_as_token("Reg 2D", 0xF800_60B4);
let dfi_timing = reg_to_values.val_as_token("DFI Timing", 0xF800_60B8);
let che_ecc_ctrl = reg_to_values.val_as_token("CHE ECC CTRL", 0xF800_60C4);
let ecc_scrub = reg_to_values.val_as_token("ECC Scrub", 0xF800_60F4);
let phy_receiver_enable = reg_to_values.val_as_token("PHY Receiver Enable", 0xF800_6114);
let phy_config_0 = reg_to_values.val_as_token("PHY Config 0", 0xF800_6118);
let phy_config_1 = reg_to_values.val_as_token("PHY Config 1", 0xF800_611C);
let phy_config_2 = reg_to_values.val_as_token("PHY Config 2", 0xF800_6120);
let phy_config_3 = reg_to_values.val_as_token("PHY Config 3", 0xF800_6124);
let phy_init_ratio_0 = reg_to_values.val_as_token("PHY Init Ratio 0", 0xF800_612C);
let phy_init_ratio_1 = reg_to_values.val_as_token("PHY Init Ratio 1", 0xF800_6130);
let phy_init_ratio_2 = reg_to_values.val_as_token("PHY Init Ratio 2", 0xF800_6134);
let phy_init_ratio_3 = reg_to_values.val_as_token("PHY Init Ratio 3", 0xF800_6138);
let phy_rd_dqs_config_0 = reg_to_values.val_as_token("PHY RD DQS Config 0", 0xF800_6140);
let phy_rd_dqs_config_1 = reg_to_values.val_as_token("PHY RD DQS Config 1", 0xF800_6144);
let phy_rd_dqs_config_2 = reg_to_values.val_as_token("PHY RD DQS Config 2", 0xF800_6148);
let phy_rd_dqs_config_3 = reg_to_values.val_as_token("PHY RD DQS Config 3", 0xF800_614C);
let phy_wr_dqs_config_0 = reg_to_values.val_as_token("PHY WR DQS Config 0", 0xF800_6154);
let phy_wr_dqs_config_1 = reg_to_values.val_as_token("PHY WR DQS Config 1", 0xF800_6158);
let phy_wr_dqs_config_2 = reg_to_values.val_as_token("PHY WR DQS Config 2", 0xF800_615C);
let phy_wr_dqs_config_3 = reg_to_values.val_as_token("PHY WR DQS Config 3", 0xF800_6160);
let phy_we_cfg_0 = reg_to_values.val_as_token("PHY WE Config 0", 0xF800_6168);
let phy_we_cfg_1 = reg_to_values.val_as_token("PHY WE Config 1", 0xF800_616C);
let phy_we_cfg_2 = reg_to_values.val_as_token("PHY WE Config 2", 0xF800_6170);
let phy_we_cfg_3 = reg_to_values.val_as_token("PHY WE Config 3", 0xF800_6174);
let phy_wr_data_slv_0 = reg_to_values.val_as_token("PHY WR Data Slv 0", 0xF800_617C);
let phy_wr_data_slv_1 = reg_to_values.val_as_token("PHY WR Data Slv 1", 0xF800_6180);
let phy_wr_data_slv_2 = reg_to_values.val_as_token("PHY WR Data Slv 2", 0xF800_6184);
let phy_wr_data_slv_3 = reg_to_values.val_as_token("PHY WR Data Slv 3", 0xF800_6188);
let reg64 = reg_to_values.val_as_token("Reg64", 0xF800_6190);
let reg65 = reg_to_values.val_as_token("Reg65", 0xF800_6194);
let page_mask = reg_to_values.val_as_token("Page Mask", 0xF800_6204);
let axi_priority_wr_port_0 = reg_to_values.val_as_token("AXI Priority WR Port 0", 0xF800_6208);
let axi_priority_wr_port_1 = reg_to_values.val_as_token("AXI Priority WR Port 1", 0xF800_620C);
let axi_priority_wr_port_2 = reg_to_values.val_as_token("AXI Priority WR Port 2", 0xF800_6210);
let axi_priority_wr_port_3 = reg_to_values.val_as_token("AXI Priority WR Port 3", 0xF800_6214);
let axi_priority_rd_port_0 = reg_to_values.val_as_token("AXI Priority RD Port 0", 0xF800_6218);
let axi_priority_rd_port_1 = reg_to_values.val_as_token("AXI Priority RD Port 1", 0xF800_621C);
let axi_priority_rd_port_2 = reg_to_values.val_as_token("AXI Priority RD Port 2", 0xF800_6220);
let axi_priority_rd_port_3 = reg_to_values.val_as_token("AXI Priority RD Port 3", 0xF800_6224);
let lpddr_ctrl_0 = reg_to_values.val_as_token("LPDDR CTRL 0", 0xF800_62A8);
let lpddr_ctrl_1 = reg_to_values.val_as_token("LPDDR CTRL 1", 0xF800_62AC);
let lpddr_ctrl_2 = reg_to_values.val_as_token("LPDDR CTRL 2", 0xF800_62B0);
let lpddr_ctrl_3 = reg_to_values.val_as_token("LPDDR CTRL 3", 0xF800_62B4);
let generated = quote::quote! {
//!This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program.
//!
//!This configuration file contains static DDR configuration parameters extracted from the
//!AMD ps7init.tcl file
use zynq7000::ddrc::regs;
use zynq7000_hal::ddr::DdrcConfigSet;
pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
ctrl: regs::DdrcControl::new_with_raw_value(#ddrc),
two_rank: regs::TwoRankConfig::new_with_raw_value(#two_rank),
hpr: regs::LprHprQueueControl::new_with_raw_value(#hpr),
lpr: regs::LprHprQueueControl::new_with_raw_value(#lpr),
wr: regs::WriteQueueControl::new_with_raw_value(#wr),
dram_param_0: regs::DramParamReg0::new_with_raw_value(#dram_param_0),
dram_param_1: regs::DramParamReg1::new_with_raw_value(#dram_param_1),
dram_param_2: regs::DramParamReg2::new_with_raw_value(#dram_param_2),
dram_param_3: regs::DramParamReg3::new_with_raw_value(#dram_param_3),
dram_param_4: regs::DramParamReg4::new_with_raw_value(#dram_param_4),
dram_init_param: regs::DramInitParam::new_with_raw_value(#dram_init_param),
dram_emr: regs::DramEmr::new_with_raw_value(#dram_emr),
dram_emr_mr: regs::DramEmrMr::new_with_raw_value(#dram_emr_mr),
dram_burst8_rdwr: regs::DramBurst8ReadWrite::new_with_raw_value(#dram_burst8_rdwr),
disable_dq: regs::DisableDq::new_with_raw_value(#dram_disable_dq),
dram_addr_map_bank: regs::DramAddrMapBank::new_with_raw_value(#dram_addr_map_bank),
dram_addr_map_col: regs::DramAddrMapColumn::new_with_raw_value(#dram_addr_map_col),
dram_addr_map_row: regs::DramAddrMapRow::new_with_raw_value(#dram_addr_map_row),
dram_odt: regs::DramOdt::new_with_raw_value(#dram_odt),
phy_cmd_timeout_rddata_cpt: regs::PhyCmdTimeoutRdDataCpt::new_with_raw_value(#phy_cmd_timeout_rddata_cpt),
dll_calib: regs::DllCalib::new_with_raw_value(#dll_calib),
odt_delay_hold: regs::OdtDelayHold::new_with_raw_value(#odt_delay_hold),
ctrl_reg1: regs::CtrlReg1::new_with_raw_value(#ctrl_reg1),
ctrl_reg2: regs::CtrlReg2::new_with_raw_value(#ctrl_reg2),
ctrl_reg3: regs::CtrlReg3::new_with_raw_value(#ctrl_reg3),
ctrl_reg4: regs::CtrlReg4::new_with_raw_value(#ctrl_reg4),
ctrl_reg5: regs::CtrlReg5::new_with_raw_value(#ctrl_reg5),
ctrl_reg6: regs::CtrlReg6::new_with_raw_value(#ctrl_reg6),
che_t_zq: regs::CheTZq::new_with_raw_value(#che_t_zq),
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(#che_t_zq_short_interval_reg),
deep_powerdown: regs::DeepPowerdown::new_with_raw_value(#deep_powerdown),
reg_2c: regs::Reg2c::new_with_raw_value(#reg_2c),
reg_2d: regs::Reg2d::new_with_raw_value(#reg_2d),
dfi_timing: regs::DfiTiming::new_with_raw_value(#dfi_timing),
che_ecc_ctrl: regs::CheEccControl::new_with_raw_value(#che_ecc_ctrl),
ecc_scrub: regs::EccScrub::new_with_raw_value(#ecc_scrub),
phy_receiver_enable: regs::PhyReceiverEnable::new_with_raw_value(#phy_receiver_enable),
phy_config: [
regs::PhyConfig::new_with_raw_value(#phy_config_0),
regs::PhyConfig::new_with_raw_value(#phy_config_1),
regs::PhyConfig::new_with_raw_value(#phy_config_2),
regs::PhyConfig::new_with_raw_value(#phy_config_3),
],
phy_init_ratio: [
regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_0),
regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_1),
regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_2),
regs::PhyInitRatio::new_with_raw_value(#phy_init_ratio_3),
],
phy_rd_dqs_config: [
regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_0),
regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_1),
regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_2),
regs::PhyDqsConfig::new_with_raw_value(#phy_rd_dqs_config_3),
],
phy_wr_dqs_config: [
regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_0),
regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_1),
regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_2),
regs::PhyDqsConfig::new_with_raw_value(#phy_wr_dqs_config_3),
],
phy_we_cfg: [
regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_0),
regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_1),
regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_2),
regs::PhyWriteEnableConfig::new_with_raw_value(#phy_we_cfg_3),
],
phy_wr_data_slv: [
regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_0),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_1),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_2),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(#phy_wr_data_slv_3),
],
reg64: regs::Reg64::new_with_raw_value(#reg64),
reg65: regs::Reg65::new_with_raw_value(#reg65),
page_mask: #page_mask,
axi_priority_wr_port: [
regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_0),
regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_1),
regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_2),
regs::AxiPriorityWritePort::new_with_raw_value(#axi_priority_wr_port_3),
],
axi_priority_rd_port: [
regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_0),
regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_1),
regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_2),
regs::AxiPriorityReadPort::new_with_raw_value(#axi_priority_rd_port_3),
],
lpddr_ctrl_0: regs::LpddrControl0::new_with_raw_value(#lpddr_ctrl_0),
lpddr_ctrl_1: regs::LpddrControl1::new_with_raw_value(#lpddr_ctrl_1),
lpddr_ctrl_2: regs::LpddrControl2::new_with_raw_value(#lpddr_ctrl_2),
lpddr_ctrl_3: regs::LpddrControl3::new_with_raw_value(#lpddr_ctrl_3),
};
};
std::fs::write(file_name, generated.to_string())?;
Ok(())
}
fn generate_ddriob_config(
reg_to_values: &RegisterToValueMap,
file_name: &str,
) -> std::io::Result<()> {
// Format as hex strings
let addr0 = reg_to_values.val_as_token("DDRIOB Addr 0", 0xF800_0B40);
let addr1 = reg_to_values.val_as_token("DDRIOB Addr 1", 0xF800_0B44);
let data0 = reg_to_values.val_as_token("DDRIOB Data 0", 0xF800_0B48);
let data1 = reg_to_values.val_as_token("DDRIOB Data 1", 0xF800_0B4C);
let diff0 = reg_to_values.val_as_token("DDRIOB Diff 0", 0xF800_0B50);
let diff1 = reg_to_values.val_as_token("DDRIOB Diff 1", 0xF800_0B54);
let clock = reg_to_values.val_as_token("DDRIOB Clock", 0xF800_0B58);
let generated = quote::quote! {
//!This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program.
//!
//!This configuration file contains static DDRIOB configuration parameters extracted from the
//!AMD ps7init.tcl file
use zynq7000::ddrc::regs;
use zynq7000_hal::ddr::DdriobConfigSet;
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
addr0: regs::DdriobConfig::new_with_raw_value(#addr0),
addr1: regs::DdriobConfig::new_with_raw_value(#addr1),
data0: regs::DdriobConfig::new_with_raw_value(#data0),
data1: regs::DdriobConfig::new_with_raw_value(#data1),
diff0: regs::DdriobConfig::new_with_raw_value(#diff0),
diff1: regs::DdriobConfig::new_with_raw_value(#diff1),
clock: regs::DdriobConfig::new_with_raw_value(#clock),
};
};
std::fs::write(file_name, generated.to_string())?;
Ok(())
}

View File

@@ -38,3 +38,25 @@ vivado zedboard-rust.xpr
You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and
`Load Project`.
# Generating the SDT folder from a hardware description
You can generate a hardware description by building the block design by using `Generate Bitstream`
inside the Vivado GUI and then exporting the hardware description via
`File -> Export -> Export Hardware`. This allows to generate a `*.xsa` file which describes the
hardware.
After that, you can generate the SDT output folder which contains various useful files like
the `ps7_init.tcl` script. The provided ` sdtgen.tcl` and `stdgen.py` script simplify this process.
For example, the following command generates the SDT output folder inside a folder
named `sdt_out` for a hardware description files `zedboard-rust/zedboard-rust.xsa`,
assuming that the Vitis tool suite is installed at `/tools/Xilinx/Vitis/2024.1`:
```sh
export AMD_TOOLS="/tools/Xilinx/Vitis/2024.1"
./sdtgen.py -x ./zedboard-rust/zedboard-rust.xsa
```
Run `stdgen.py -h` for more information and configuration options. The `stdgen.py` is a helper
script which will invoke `sdtgen.tcl` to generate the SDT.

View File

@@ -675,6 +675,18 @@ proc create_root_design { parentCell } {
connect_bd_net -net xlslice_0_Dout1 [get_bd_pins UART_MUX/Dout] [get_bd_pins uart_mux_0/sel]
connect_bd_net -net xlslice_1_Dout [get_bd_pins EMIO_O_0/Dout] [get_bd_pins LEDS/Din] [get_bd_pins EMIO_I/In0] [get_bd_pins UART_MUX/Din]
# Set DDR properties specified in the datasheet.
set_property -dict [list \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY0 {0.410} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY1 {0.411} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY2 {0.341} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY3 {0.358} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_0 {0.025} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_1 {0.028} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_2 {-0.009} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_3 {-0.061} \
] [get_bd_cells processing_system7_0]
# Create address segments
assign_bd_address -offset 0x43C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uart16550_0/S_AXI/Reg] -force
assign_bd_address -offset 0x42C00000 -range 0x00010000 -target_address_space [get_bd_addr_spaces processing_system7_0/Data] [get_bd_addr_segs axi_uartlite_0/S_AXI/Reg] -force

View File

@@ -1,12 +0,0 @@
[package]
name = "zynq-mmu"
description = "Zynq MMU structures"
version = "0.1.0"
edition = "2024"
[dependencies]
thiserror = { version = "2", default-features = false }
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" }
[features]
tools = []

20
zynq/.cargo/config.toml Normal file
View File

@@ -0,0 +1,20 @@
[target.armv7a-none-eabihf]
runner = "just run"
rustflags = [
"-Ctarget-cpu=cortex-a9",
"-Ctarget-feature=+vfp3",
"-Ctarget-feature=+neon",
"-Clink-arg=-Tlink.x",
# If this is not enabled, debugging / stepping can become problematic.
"-Cforce-frame-pointers=yes",
# Can be useful for debugging.
# "-Clink-args=-Map=app.map"
]
# Tier 3 target, so no pre-compiled artifacts included.
[unstable]
build-std = ["core", "alloc"]
[build]
target = "armv7a-none-eabihf"

1
zynq/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
Cargo.lock

21
zynq/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[workspace]
resolver = "3"
members = [
"zynq7000-rt",
"zynq7000-mmu",
"zynq7000",
"zynq7000-hal",
"zynq7000-embassy",
"examples/simple",
"examples/embassy",
"examples/zedboard",
"zedboard-bsp",
"zedboard-qspi-flasher",
]
exclude = [
# Exclude, can not be built with debug optimization level, too large.
"zedboard-fsbl",
]

View File

@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
cortex-ar = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
@@ -19,20 +19,36 @@ zynq7000-embassy = { path = "../../zynq7000-embassy" }
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
static_cell = "2"
critical-section = "1"
heapless = "0.8"
embedded-io = "0.6"
heapless = "0.9"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"
embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "cortex-ar-update", features = [
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = [
"arch-cortex-ar",
"executor-thread",
]}
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
[profile.release]
# cargo build/run
[profile.dev]
# default is opt-level = '0', but that makes very
# verbose machine code
opt-level = 's'
# trade compile speed for slightly better optimisations
codegen-units = 1
debug = true
lto = true
# cargo build/run --release
[profile.release]
# default is opt-level = '3', but that makes quite
# verbose machine code
opt-level = 's'
# trade compile speed for slightly better optimisations
codegen-units = 1
# Use Link Time Optimisations to further inline things across
# crates
lto = 'fat'
# Leave the debug symbols in (default is no debug info)
debug = 2

View File

@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}

View File

@@ -1,8 +1,7 @@
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 MPU. This is
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

View File

@@ -17,10 +17,10 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -44,7 +44,7 @@ const OPEN_DRAIN_PINS_MIO9_TO_MIO14: bool = false;
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -64,12 +64,12 @@ async fn main(_spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) -> ! {
info!("Flex Pin 0 state (should be low): {}", flex_pin_0.is_high());
}
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
@@ -161,8 +161,8 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -181,22 +181,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -8,18 +8,8 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{Output, PinState, mio},
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
};
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
use zynq7000::PsPeripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -37,32 +27,27 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
unsafe {
gic.enable_interrupts();
}
let mio_pins = mio::Pins::new(dp.gpio);
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up global timer counter and embassy time driver.
let gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -77,11 +62,11 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
let mut led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
loop {
info!("Hello, world!");
led.toggle().unwrap();
@@ -89,42 +74,42 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
Interrupt::Sgi(_) => (),
Interrupt::Ppi(ppi_interrupt) => {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -9,7 +9,7 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
@@ -18,7 +18,7 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, TxAsync, Uart, UartConfig, on_interrupt_tx},
uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx},
};
use zynq7000_rt as _;
@@ -38,7 +38,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -58,12 +58,12 @@ async fn main(spawner: Spawner) -> ! {
let mio_pins = mio::Pins::new(dp.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -75,11 +75,11 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
spawner.spawn(led_task(led)).unwrap();
spawner.spawn(led_task(led).unwrap());
let mut log_buf: [u8; 2048] = [0; 2048];
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
loop {
@@ -123,22 +123,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -23,10 +23,10 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::PsPeripherals;
use zynq7000::Peripherals;
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -44,7 +44,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -71,12 +71,12 @@ async fn main(_spawner: Spawner) -> ! {
pwm.set_duty_cycle_percent(50).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
@@ -111,8 +111,8 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -131,22 +131,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -0,0 +1,95 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use log::error;
use zynq7000_hal::{InteruptConfig, clocks, gic, gpio, gtc, time::Hertz};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
loop {
led.toggle().unwrap();
ticker.next().await;
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -1,5 +1,5 @@
[package]
name = "blinky"
name = "simple"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
@@ -9,11 +9,11 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
cortex-ar = "0.3"
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
embedded-io = "0.6"
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"

View File

@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}

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
}

View File

@@ -4,25 +4,33 @@
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embedded_hal::digital::StatefulOutputPin;
use zynq7000::PsPeripherals;
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
use zynq7000::Peripherals;
use zynq7000_hal::{
clocks::Clocks,
gpio::{Output, PinState, mio},
l2_cache,
priv_tim::CpuPrivateTimer,
time::Hertz,
};
use zynq7000_rt as _;
pub const LIB: Lib = Lib::Hal;
/// One user LED is MIO7
const ZEDBOARD_LED_MASK: u32 = 1 << 7;
// Define the clock frequency as a constant.
//
// Not required for the PAC mode, is required for clean delays in HAL mode.
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
#[derive(Debug)]
pub enum Lib {
Pac,
Hal,
}
const LIB: Lib = Lib::Hal;
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
@@ -48,38 +56,39 @@ pub fn main() -> ! {
}
}
Lib::Hal => {
let dp = PsPeripherals::take().unwrap();
let dp = Peripherals::take().unwrap();
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Unwrap okay, we only call this once on core 0 here.
let mut cpu_tim = CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
let mio_pins = mio::Pins::new(dp.gpio);
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::High);
loop {
led.toggle().unwrap();
for _ in 0..5_000_000 {
nop();
}
cpu_tim.delay_ms(1000);
}
}
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {}
fn irq_handler() {}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -15,13 +15,13 @@ use zynq7000_hal::{
l2_cache,
prelude::*,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
@@ -36,7 +36,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let mut dp = zynq7000::PsPeripherals::take().unwrap();
let mut dp = zynq7000::Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -49,7 +49,7 @@ pub fn main() -> ! {
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
@@ -64,7 +64,7 @@ pub fn main() -> ! {
let mio_pins = mio::Pins::new(dp.gpio);
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -92,8 +92,8 @@ pub fn main() -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -110,22 +110,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -16,7 +16,7 @@ use zynq7000_hal::{
l2_cache,
prelude::*,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000_rt as _;
@@ -37,7 +37,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let mut dp = zynq7000::PsPeripherals::take().unwrap();
let mut dp = zynq7000::Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
@@ -50,7 +50,7 @@ pub fn main() -> ! {
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = GlobalTimerCounter::new(dp.gtc, clocks.arm_clocks());
@@ -64,7 +64,7 @@ pub fn main() -> ! {
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
@@ -79,7 +79,7 @@ pub fn main() -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {boot_mode:?}");
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
@@ -94,8 +94,8 @@ pub fn main() -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -113,22 +113,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -0,0 +1,55 @@
//! Simple blinky app, showing a PAC variant and a HAL variant.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use zynq7000_rt as _;
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[unsafe(export_name = "main")]
pub fn main() -> ! {
loop {
cortex_ar::asm::nop();
}
}
#[zynq7000_rt::irq]
fn irq_handler() {}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
nop();
}
}

View File

@@ -11,15 +11,17 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2", features = ["critical-section-single-core"] }
cortex-ar = "0.3"
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
zynq7000-embassy = { path = "../../zynq7000-embassy" }
zedboard-bsp = { path = "../../zedboard-bsp" }
num_enum = { version = "0.7", default-features = false }
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
embedded-io = "0.6"
bitbybit = "1.3"
arbitrary-int = "1.3"
embedded-io = "0.7"
bitbybit = "1.4"
arbitrary-int = "2"
embedded-io-async = "0.6"
critical-section = "1"
static_cell = "2"
@@ -38,6 +40,7 @@ embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "co
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
embassy-net = { version = "0.7", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] }
embassy-sync = { version = "0.7" }
# TODO: Bump as soon as new compatible smoltcp/embassy-net version is released.
heapless = "0.8"
axi-uartlite = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uartlite.git" }
axi-uart16550 = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550.git" }

View File

@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}

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
}

View File

@@ -33,10 +33,8 @@ use embedded_io::Write;
use embedded_io_async::Write as _;
use log::{LevelFilter, debug, error, info, warn};
use rand::{RngCore, SeedableRng};
use zedboard::{
PS_CLOCK_FREQUENCY,
phy_marvell::{LatchingLinkStatus, MARVELL_88E1518_OUI},
};
use zedboard::PS_CLOCK_FREQUENCY;
use zedboard_bsp::phy_marvell;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
@@ -48,10 +46,10 @@ use zynq7000_hal::{
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut};
const USE_DHCP: bool = true;
@@ -72,8 +70,8 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r";
// Unicast address with OUI of the Marvell 88E1518 PHY.
const MAC_ADDRESS: [u8; 6] = [
0x00,
((MARVELL_88E1518_OUI >> 8) & 0xff) as u8,
(MARVELL_88E1518_OUI & 0xff) as u8,
((phy_marvell::MARVELL_88E1518_OUI >> 8) & 0xff) as u8,
(phy_marvell::MARVELL_88E1518_OUI & 0xff) as u8,
0x00,
0x00,
0x01,
@@ -208,7 +206,7 @@ async fn tcp_task(mut tcp: TcpSocket<'static>) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -236,12 +234,12 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -249,7 +247,7 @@ async fn main(spawner: Spawner) -> ! {
// Safety: We are not multi-threaded yet.
unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) };
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
static ETH_RX_BUFS: static_cell::ConstStaticCell<[AlignedBuffer; NUM_RX_SLOTS]> =
@@ -317,9 +315,8 @@ async fn main(spawner: Spawner) -> ! {
eth.set_rx_buf_descriptor_base_address(rx_descr_ref.base_addr());
eth.set_tx_buf_descriptor_base_address(tx_descr_ref.base_addr());
eth.start();
let (mut phy, phy_rev) =
zedboard::phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut())
.expect("could not auto-detect phy");
let (mut phy, phy_rev) = phy_marvell::Marvell88E1518Phy::new_autoprobe_addr(eth.mdio_mut())
.expect("could not auto-detect phy");
info!(
"Detected Marvell 88E1518 PHY with revision number: {:?}",
phy_rev
@@ -456,7 +453,8 @@ async fn main(spawner: Spawner) -> ! {
IpMode::StackReady => {
let status = phy.read_copper_status();
// Periodically check for link changes.
if status.copper_link_status() == LatchingLinkStatus::DownSinceLastRead {
if status.copper_link_status() == phy_marvell::LatchingLinkStatus::DownSinceLastRead
{
warn!("ethernet link is down.");
ip_mode = IpMode::LinkDown;
continue;
@@ -467,8 +465,8 @@ async fn main(spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -499,22 +497,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -29,7 +29,7 @@ use zynq7000_hal::{
uart,
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -48,7 +48,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -73,12 +73,12 @@ async fn main(_spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -93,7 +93,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let pin_sel = match I2C_ADDR_SEL {

View File

@@ -30,7 +30,7 @@ use zynq7000_hal::{
uart::{self, TxAsync, on_interrupt_tx},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig, spi::DelayControl};
use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -51,7 +51,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -83,12 +83,12 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -96,7 +96,7 @@ async fn main(spawner: Spawner) -> ! {
.unwrap();
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
if DEBUG_SPI_CLK_CONFIG {

View File

@@ -0,0 +1,211 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zedboard_bsp::qspi_spansion;
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r";
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
vendor: qspi::QspiVendor::WinbondAndSpansion,
operating_mode: qspi::OperatingMode::FastReadQuadOutput,
two_devices: false,
};
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
const ERASE_PROGRAM_READ_TEST: bool = false;
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let qspi_clock_config =
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
.expect("QSPI clock calculation failed");
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
periphs.qspi,
qspi_clock_config,
embedded_hal::spi::MODE_0,
qspi::IoType::LvCmos33,
gpio_pins.mio.mio1,
(
gpio_pins.mio.mio2,
gpio_pins.mio.mio3,
gpio_pins.mio.mio4,
gpio_pins.mio.mio5,
),
gpio_pins.mio.mio6,
gpio_pins.mio.mio8,
);
let qspi_io_mode = qspi.into_io_mode(false);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let rdid = spansion_qspi.read_rdid_extended();
info!(
"QSPI Info: manufacturer ID: {:?}, interface type: {:?}, density: {:?}, sector arch: {:?}, model number: {:?}",
rdid.base_id().manufacturer_id(),
rdid.base_id().memory_interface_type(),
rdid.base_id().density(),
rdid.sector_arch(),
rdid.model_number()
);
let cr1 = spansion_qspi.read_configuration_register();
info!("QSPI Configuration Register 1: {:?}", cr1);
let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1];
for (idx, byte) in write_buf.iter_mut().enumerate() {
*byte = idx as u8;
}
let mut read_buf = [0u8; 256];
if ERASE_PROGRAM_READ_TEST {
info!("performing erase, program, read test");
spansion_qspi
.erase_sector(0x10000)
.expect("erasing sector failed");
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
for read in read_buf.iter() {
assert_eq!(*read, 0xFF);
}
read_buf.fill(0);
spansion_qspi.program_page(0x10000, &write_buf).unwrap();
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
assert_eq!(read, written);
}
info!("test successful");
}
let mut spansion_lqspi = spansion_qspi.into_linear_addressed(QSPI_DEV_COMBINATION.into());
let guard = spansion_lqspi.read_guard();
unsafe {
core::ptr::copy_nonoverlapping(
(qspi::QSPI_START_ADDRESS + 0x10000) as *const u8,
read_buf.as_mut_ptr(),
256,
);
}
drop(guard);
if ERASE_PROGRAM_READ_TEST {
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
assert_eq!(
read, written,
"linear read failure, read and write missmatch"
);
}
}
let mut ticker = Ticker::every(Duration::from_millis(200));
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
loop {
mio_led.toggle().unwrap();
ticker.next().await; // Wait for the next cycle of the ticker
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -20,10 +20,10 @@ use zynq7000_hal::{
gpio::{GpioPins, Output, PinState},
gtc::GlobalTimerCounter,
l2_cache,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard blocking UART example --\n\r";
@@ -101,7 +101,7 @@ impl UartMultiplexer {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -123,12 +123,12 @@ async fn main(_spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -145,7 +145,7 @@ async fn main(_spawner: Spawner) -> ! {
// UART0 routed through EMIO to PL pins.
let mut uart_0 =
Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap();
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
@@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
@@ -218,8 +218,8 @@ async fn main(_spawner: Spawner) -> ! {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
@@ -238,22 +238,22 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -47,7 +47,7 @@ use zynq7000_hal::{
gtc::GlobalTimerCounter,
l2_cache,
time::Hertz,
uart::{ClockConfigRaw, Uart, UartConfig},
uart::{ClockConfig, Config, Uart},
};
pub enum UartMode {
@@ -62,7 +62,7 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard non-blocking UART example --\n\
#[global_allocator]
static HEAP: Heap = Heap::empty();
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000::{Peripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
@@ -166,7 +166,7 @@ impl UartMultiplexer {
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
let mut dp = PsPeripherals::take().unwrap();
let mut dp = Peripherals::take().unwrap();
l2_cache::init_with_defaults(&mut dp.l2c);
// Enable PS-PL level shifters.
@@ -194,12 +194,12 @@ async fn main(spawner: Spawner) -> ! {
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
@@ -237,7 +237,7 @@ async fn main(spawner: Spawner) -> ! {
// UART0 routed through EMIO to PL pins.
let uart_0 =
Uart::new_with_emio(dp.uart_0, UartConfig::new_with_clk_config(uart_clk_config)).unwrap();
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
// We need to call this before splitting the structure, because the interrupt signal is
@@ -254,7 +254,7 @@ async fn main(spawner: Spawner) -> ! {
)
};
let boot_mode = BootMode::new();
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
@@ -432,8 +432,8 @@ async fn uart_16550_task(uart_tx: axi_uart16550::Tx) {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
@@ -465,7 +465,7 @@ pub extern "C" fn _irq_handler() {
gic_helper.end_of_interrupt(irq_info);
}
pub fn on_interrupt_axi_uartlite() {
fn on_interrupt_axi_uartlite() {
let mut buf = [0; axi_uartlite::FIFO_DEPTH];
let mut rx = unsafe { axi_uartlite::Rx::steal(AXI_UARTLITE_BASE_ADDR as usize) };
// Handle RX first: Empty the FIFO content into local buffer.
@@ -485,7 +485,7 @@ pub fn on_interrupt_axi_uartlite() {
}
}
pub fn on_interrupt_axi_16550() {
fn on_interrupt_axi_16550() {
let mut buf = [0; axi_uart16550::FIFO_DEPTH];
let mut read_bytes = 0;
let mut rx = unsafe { axi_uart16550::Rx::steal(AXI_UAR16550_BASE_ADDR as usize) };
@@ -545,22 +545,22 @@ fn on_interrupt_uart_0() {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _abort_handler() {
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _undefined_handler() {
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _prefetch_handler() {
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}

View File

@@ -1,6 +1,5 @@
#![no_std]
use zynq7000_hal::time::Hertz;
pub mod phy_marvell;
// Define the clock frequency as a constant
pub const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);

View File

@@ -0,0 +1,140 @@
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r";
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up global timer counter and embassy time driver.
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(200));
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
let mut emio_leds: [gpio::Output; 8] = [
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
];
loop {
mio_led.toggle().unwrap();
// Create a wave pattern for emio_leds
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
ticker.next().await; // Wait for the next ticker for each toggle
}
ticker.next().await; // Wait for the next cycle of the ticker
}
}
#[zynq7000_rt::irq]
fn irq_handler() {
let mut gic_helper = gic::GicInterruptHelper::new();
let irq_info = gic_helper.acknowledge_interrupt();
match irq_info.interrupt() {
gic::Interrupt::Sgi(_) => (),
gic::Interrupt::Ppi(ppi_interrupt) => {
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
unsafe {
zynq7000_embassy::on_interrupt();
}
}
}
gic::Interrupt::Spi(_spi_interrupt) => (),
gic::Interrupt::Invalid(_) => (),
gic::Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -1,3 +1,2 @@
[toolchain]
# channel = "stable"
channel = "nightly"

View File

@@ -0,0 +1,12 @@
[package]
name = "zedboard-bsp"
version = "0.1.0"
edition = "2024"
[dependencies]
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
bitbybit = "1.4"
arbitrary-int = "2"
num_enum = { version = "0.7", default-features = false }
thiserror = { version = "2", default-features = false }

View File

@@ -0,0 +1,100 @@
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."]
#![doc = r""]
#![doc = r"This configuration file contains static DDR configuration parameters extracted from the"]
#![doc = r"AMD ps7init.tcl file"]
use zynq7000::ddrc::regs;
use zynq7000_hal::ddr::DdrcConfigSet;
pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
ctrl: regs::DdrcControl::new_with_raw_value(0x00000080),
two_rank: regs::TwoRankConfig::new_with_raw_value(0x00001082),
hpr: regs::LprHprQueueControl::new_with_raw_value(0x03c0780f),
lpr: regs::LprHprQueueControl::new_with_raw_value(0x02001001),
wr: regs::WriteQueueControl::new_with_raw_value(0x00014001),
dram_param_0: regs::DramParamReg0::new_with_raw_value(0x0004159b),
dram_param_1: regs::DramParamReg1::new_with_raw_value(0x44e458d3),
dram_param_2: regs::DramParamReg2::new_with_raw_value(0x7282bce5),
dram_param_3: regs::DramParamReg3::new_with_raw_value(0x270872d0),
dram_param_4: regs::DramParamReg4::new_with_raw_value(0x00000000),
dram_init_param: regs::DramInitParam::new_with_raw_value(0x00002007),
dram_emr: regs::DramEmr::new_with_raw_value(0x00000008),
dram_emr_mr: regs::DramEmrMr::new_with_raw_value(0x00040b30),
dram_burst8_rdwr: regs::DramBurst8ReadWrite::new_with_raw_value(0x000116d4),
disable_dq: regs::DisableDq::new_with_raw_value(0x00000000),
dram_addr_map_bank: regs::DramAddrMapBank::new_with_raw_value(0x00000777),
dram_addr_map_col: regs::DramAddrMapColumn::new_with_raw_value(0xfff00000),
dram_addr_map_row: regs::DramAddrMapRow::new_with_raw_value(0x0ff66666),
dram_odt: regs::DramOdt::new_with_raw_value(0x0003c008),
phy_cmd_timeout_rddata_cpt: regs::PhyCmdTimeoutRdDataCpt::new_with_raw_value(0x77010800),
dll_calib: regs::DllCalib::new_with_raw_value(0x00000000),
odt_delay_hold: regs::OdtDelayHold::new_with_raw_value(0x00005003),
ctrl_reg1: regs::CtrlReg1::new_with_raw_value(0x0000003e),
ctrl_reg2: regs::CtrlReg2::new_with_raw_value(0x00020000),
ctrl_reg3: regs::CtrlReg3::new_with_raw_value(0x00284141),
ctrl_reg4: regs::CtrlReg4::new_with_raw_value(0x00001610),
ctrl_reg5: regs::CtrlReg5::new_with_raw_value(0x00466111),
ctrl_reg6: regs::CtrlReg6::new_with_raw_value(0x00032222),
che_t_zq: regs::CheTZq::new_with_raw_value(0x10200802),
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x10200802),
deep_powerdown: regs::DeepPowerdown::new_with_raw_value(0x000001fe),
reg_2c: regs::Reg2c::new_with_raw_value(0x1cffffff),
reg_2d: regs::Reg2d::new_with_raw_value(0x00000200),
dfi_timing: regs::DfiTiming::new_with_raw_value(0x00200066),
che_ecc_ctrl: regs::CheEccControl::new_with_raw_value(0x00000000),
ecc_scrub: regs::EccScrub::new_with_raw_value(0x00000008),
phy_receiver_enable: regs::PhyReceiverEnable::new_with_raw_value(0x00000000),
phy_config: [
regs::PhyConfig::new_with_raw_value(0x40000001),
regs::PhyConfig::new_with_raw_value(0x40000001),
regs::PhyConfig::new_with_raw_value(0x40000001),
regs::PhyConfig::new_with_raw_value(0x40000001),
],
phy_init_ratio: [
regs::PhyInitRatio::new_with_raw_value(0x00033c03),
regs::PhyInitRatio::new_with_raw_value(0x00034003),
regs::PhyInitRatio::new_with_raw_value(0x0002f400),
regs::PhyInitRatio::new_with_raw_value(0x00030400),
],
phy_rd_dqs_config: [
regs::PhyDqsConfig::new_with_raw_value(0x00000035),
regs::PhyDqsConfig::new_with_raw_value(0x00000035),
regs::PhyDqsConfig::new_with_raw_value(0x00000035),
regs::PhyDqsConfig::new_with_raw_value(0x00000035),
],
phy_wr_dqs_config: [
regs::PhyDqsConfig::new_with_raw_value(0x00000083),
regs::PhyDqsConfig::new_with_raw_value(0x00000083),
regs::PhyDqsConfig::new_with_raw_value(0x0000007f),
regs::PhyDqsConfig::new_with_raw_value(0x00000078),
],
phy_we_cfg: [
regs::PhyWriteEnableConfig::new_with_raw_value(0x00000124),
regs::PhyWriteEnableConfig::new_with_raw_value(0x00000125),
regs::PhyWriteEnableConfig::new_with_raw_value(0x00000112),
regs::PhyWriteEnableConfig::new_with_raw_value(0x00000116),
],
phy_wr_data_slv: [
regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000c3),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000c3),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000bf),
regs::PhyWriteDataSlaveConfig::new_with_raw_value(0x000000b8),
],
reg64: regs::Reg64::new_with_raw_value(0x00040080),
reg65: regs::Reg65::new_with_raw_value(0x0001fc82),
page_mask: 0x00000000,
axi_priority_wr_port: [
regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff),
regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff),
regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff),
regs::AxiPriorityWritePort::new_with_raw_value(0x000003ff),
],
axi_priority_rd_port: [
regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff),
regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff),
regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff),
regs::AxiPriorityReadPort::new_with_raw_value(0x000003ff),
],
lpddr_ctrl_0: regs::LpddrControl0::new_with_raw_value(0x00000000),
lpddr_ctrl_1: regs::LpddrControl1::new_with_raw_value(0x00000000),
lpddr_ctrl_2: regs::LpddrControl2::new_with_raw_value(0x00005125),
lpddr_ctrl_3: regs::LpddrControl3::new_with_raw_value(0x000012a8),
};

View File

@@ -0,0 +1,15 @@
#![doc = r"This file was auto-generated by the [zynq7000-ps7init-extract](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract) program."]
#![doc = r""]
#![doc = r"This configuration file contains static DDRIOB configuration parameters extracted from the"]
#![doc = r"AMD ps7init.tcl file"]
use zynq7000::ddrc::regs;
use zynq7000_hal::ddr::DdriobConfigSet;
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
addr0: regs::DdriobConfig::new_with_raw_value(0x00000600),
addr1: regs::DdriobConfig::new_with_raw_value(0x00000600),
data0: regs::DdriobConfig::new_with_raw_value(0x00000672),
data1: regs::DdriobConfig::new_with_raw_value(0x00000672),
diff0: regs::DdriobConfig::new_with_raw_value(0x00000674),
diff1: regs::DdriobConfig::new_with_raw_value(0x00000674),
clock: regs::DdriobConfig::new_with_raw_value(0x00000600),
};

View File

@@ -0,0 +1,6 @@
#![no_std]
pub mod ddrc_config_autogen;
pub mod ddriob_config_autogen;
pub mod phy_marvell;
pub mod qspi_spansion;

View File

@@ -0,0 +1,630 @@
use core::cell::RefCell;
use arbitrary_int::{prelude::*, u24};
use zynq7000_hal::qspi::{
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
QspiLinearReadGuard,
};
pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination =
zynq7000_hal::qspi::QspiDeviceCombination {
vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion,
operating_mode: zynq7000_hal::qspi::OperatingMode::FastReadQuadOutput,
two_devices: false,
};
#[derive(Debug, Clone, Copy)]
pub enum RegisterId {
/// WRR
WriteRegisters = 0x01,
/// PP
PageProgram = 0x02,
/// READ
Read = 0x03,
/// WRDI
WriteDisable = 0x04,
/// RDSR1
ReadStatus1 = 0x05,
/// RDSR2
ReadStatus2 = 0x07,
/// WREN
WriteEnable = 0x06,
/// FAST_READ
FastRead = 0x0B,
/// SE
SectorErase = 0xD8,
/// CSLR
ClearStatus = 0x30,
/// RDCR
ReadConfig = 0x35,
/// RDID
ReadId = 0x9F,
}
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum MemoryInterfaceType {
_128Mb = 0x20,
_256Mb = 0x02,
}
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum Density {
_128Mb = 0x18,
_256Mb = 0x19,
}
#[derive(Debug, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum SectorArchictecture {
/// Uniform 256 kB sectors.
Uniform = 0x00,
/// 32 4kB sectors and 64 kB sectors.
Hybrid = 0x01,
}
pub const PAGE_SIZE: usize = 256;
#[derive(Debug, Clone, Copy)]
pub struct BaseDeviceId {
manufacturer_id: u8,
device_id: u16,
}
impl BaseDeviceId {
#[inline]
pub const fn new(manufacturer_id: u8, device_id: u16) -> Self {
BaseDeviceId {
manufacturer_id,
device_id,
}
}
#[inline]
pub const fn from_raw(raw: &[u8; 3]) -> Self {
BaseDeviceId::new(raw[0], ((raw[1] as u16) << 8) | raw[2] as u16)
}
#[inline]
pub const fn manufacturer_id(&self) -> u8 {
self.manufacturer_id
}
#[inline]
pub const fn device_id_raw(&self) -> u16 {
self.device_id
}
#[inline]
pub fn memory_interface_type(&self) -> Result<MemoryInterfaceType, u8> {
MemoryInterfaceType::try_from(((self.device_id >> 8) & 0xff) as u8).map_err(|e| e.number)
}
#[inline]
pub fn density(&self) -> Result<Density, u8> {
Density::try_from((self.device_id & 0xff) as u8).map_err(|e| e.number)
}
}
#[derive(Debug)]
pub struct ExtendedDeviceId {
base: BaseDeviceId,
id_cfi_len: u8,
sector_arch: u8,
family_id: u8,
model_number: [u8; 2],
}
impl ExtendedDeviceId {
pub const fn from_raw(raw: &[u8; 8]) -> Self {
ExtendedDeviceId {
base: BaseDeviceId::from_raw(&[raw[0], raw[1], raw[2]]),
id_cfi_len: raw[3],
sector_arch: raw[4],
family_id: raw[5],
model_number: [raw[6], raw[7]],
}
}
pub const fn id_cfi_len(&self) -> u8 {
self.id_cfi_len
}
pub const fn sector_arch_raw(&self) -> u8 {
self.sector_arch
}
pub fn sector_arch(&self) -> Result<SectorArchictecture, u8> {
SectorArchictecture::try_from(self.sector_arch_raw()).map_err(|e| e.number)
}
pub const fn family_id(&self) -> u8 {
self.family_id
}
pub const fn model_number_raw(&self) -> &[u8; 2] {
&self.model_number
}
pub const fn model_number(&self) -> [char; 2] {
[self.model_number[0] as char, self.model_number[1] as char]
}
}
impl ExtendedDeviceId {
#[inline]
pub fn base_id(&self) -> BaseDeviceId {
self.base
}
}
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
pub struct StatusRegister1 {
#[bit(7, rw)]
status_register_write_disable: bool,
#[bit(6, r)]
programming_error: bool,
#[bit(5, r)]
erase_error: bool,
#[bit(4, r)]
bp_2: bool,
#[bit(3, r)]
bp_1: bool,
#[bit(2, r)]
bp_0: bool,
#[bit(1, r)]
write_enable_latch: bool,
#[bit(0, r)]
write_in_progress: bool,
}
#[bitbybit::bitfield(u8)]
#[derive(Debug)]
pub struct ConfigRegister1 {
#[bit(7, rw)]
latency_code_1: bool,
#[bit(6, rw)]
latency_code_0: bool,
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
#[bit(5, rw)]
tbprot: bool,
#[bit(3, rw)]
bpnv: bool,
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
#[bit(2, rw)]
tbparm: bool,
#[bit(1, rw)]
quad: bool,
#[bit(0, rw)]
freeze: bool,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum AddrError {
#[error("address out of range")]
OutOfRange,
#[error("address not aligned")]
Alignment,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum EraseError {
#[error("erase error bit set in status register")]
EraseErrorBitSet,
#[error("address error: {0}")]
Addr(#[from] AddrError),
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum ProgramPageError {
#[error("programming error bit set in status register")]
ProgrammingErrorBitSet,
#[error("address error: {0}")]
Addr(#[from] AddrError),
#[error("data is larger than page size {PAGE_SIZE}")]
DataLargerThanPage,
}
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
impl QspiSpansionS25Fl256SIoMode {
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
if set_quad_bit_if_necessary {
let mut cr1 = spansion_qspi.read_configuration_register();
if cr1.quad() {
// Quad bit is already set.
return spansion_qspi;
}
cr1.set_quad(true);
// Preserve the status register by reading it first.
let sr1 = spansion_qspi.read_status_register_1();
// Safety: Only the QUAD bit was set while all other bits are preserved.
unsafe {
spansion_qspi.write_status_and_config_register(sr1, cr1);
}
}
spansion_qspi
}
pub fn into_linear_addressed(
self,
config: LinearQspiConfig,
) -> QspiSpansionS25Fl256SLinearMode {
let qspi = self.0.into_inner().into_linear_addressed(config);
QspiSpansionS25Fl256SLinearMode(qspi)
}
pub fn write_enable(&mut self) {
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::WriteEnable as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn write_disable(&mut self) {
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::WriteDisable as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
/// Write a new value for the status register.
///
/// This API may not be used if the QUAD bit (CR1\[1\]) is set.
pub fn write_status(&mut self, sr: StatusRegister1) {
self.write_enable();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(u32::from_ne_bytes([
RegisterId::WriteRegisters as u8,
sr.raw_value(),
0x00,
0x00,
]));
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
/// Write a new value for the status register. It is strongly recommended to read both
/// the status and config register first and preserve all unchanged bits.
///
/// This API must be used if the QUAD bit (CR1\[1\]) is set.
///
/// # Safety
///
/// Misuse of this API does not lead to undefined behavior. However, it writes the
/// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an
/// irreversible operation.
pub unsafe fn write_status_and_config_register(
&mut self,
sr: StatusRegister1,
cr: ConfigRegister1,
) {
self.write_enable();
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_11(u32::from_ne_bytes([
RegisterId::WriteRegisters as u8,
sr.raw_value(),
cr.raw_value(),
0x00,
]));
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn read_status_register_1(&self) -> StatusRegister1 {
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(RegisterId::ReadStatus1 as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
// little-endian architecture, so the second byte received is the MSB.
StatusRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
}
pub fn read_configuration_register(&self) -> ConfigRegister1 {
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_10(RegisterId::ReadConfig as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
// little-endian architecture, so the second byte received is the MSB.
ConfigRegister1::new_with_raw_value(((reply >> 24) & 0xff) as u8)
}
pub fn clear_status(&mut self) {
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_01(RegisterId::ClearStatus as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
}
pub fn read_rdid_base(&self) -> BaseDeviceId {
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_00(RegisterId::ReadId as u32);
transfer.start();
while !transfer.read_status().rx_above_threshold() {}
let reply = transfer.read_rx_data();
drop(transfer);
BaseDeviceId::from_raw(reply.to_ne_bytes()[1..].try_into().unwrap())
}
pub fn read_rdid_extended(&self) -> ExtendedDeviceId {
let mut reply: [u8; 12] = [0; 12];
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
transfer.write_word_txd_00(RegisterId::ReadId as u32);
transfer.write_word_txd_00(0x00);
transfer.write_word_txd_00(0x00);
transfer.start();
let mut read_index = 0;
while read_index < 3 {
if transfer.read_status().rx_above_threshold() {
reply[read_index * 4..(read_index + 1) * 4]
.copy_from_slice(&transfer.read_rx_data().to_ne_bytes());
read_index += 1;
}
}
ExtendedDeviceId::from_raw(reply[1..9].try_into().unwrap())
}
/// This function will block until the operation has completed.
pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> {
if addr + 0x10000 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if !addr.is_multiple_of(0x10000) {
return Err(AddrError::Alignment.into());
}
self.write_enable();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::SectorErase as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
transfer.start();
// Finish transfer
while !transfer.read_status().rx_above_threshold() {}
transfer.read_rx_data();
// Drive CS high to initiate the sector erase operation.
drop(transfer);
// Now poll for completion.
loop {
let rdsr1 = self.read_status_register_1();
if rdsr1.erase_error() {
// The datasheet mentions that the status should be cleared and writes
// should be disabled explicitely.
self.clear_status();
self.write_disable();
return Err(EraseError::EraseErrorBitSet);
}
if !rdsr1.write_in_progress() {
return Ok(());
}
}
}
/// This function also takes care of enabling writes before programming the page.
/// This function will block until the operation has completed.
///
/// The data length max not exceed the page size [PAGE_SIZE].
pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
if addr + data.len() as u32 > u24::MAX.as_u32() {
return Err(AddrError::OutOfRange.into());
}
if !addr.is_multiple_of(0x100) {
return Err(AddrError::Alignment.into());
}
if data.len() > PAGE_SIZE {
return Err(ProgramPageError::DataLargerThanPage);
}
self.write_enable();
let qspi = self.0.get_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::PageProgram as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index: u32 = 0;
let mut current_byte_index = 0;
let fifo_writes = data.len().div_ceil(4);
// Fill the FIFO until it is full.
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
));
current_byte_index += 4;
}
transfer.start();
let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop {
let status = transfer.read_status();
if status.rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
if !status.tx_full() {
break;
}
};
while current_byte_index < data.len() {
// Immediately fill the FIFO again with the remaining 8 bytes.
wait_for_tx_slot(&mut transfer);
let word = match core::cmp::min(4, data.len() - current_byte_index) {
1 => {
let mut bytes = [0; 4];
bytes[0] = data[current_byte_index];
u32::from_ne_bytes(bytes)
}
2 => {
let mut bytes = [0; 4];
bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]);
u32::from_ne_bytes(bytes)
}
3 => {
let mut bytes = [0; 4];
bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]);
u32::from_ne_bytes(bytes)
}
4 => u32::from_ne_bytes(
data[current_byte_index..current_byte_index + 4]
.try_into()
.unwrap(),
),
_ => unreachable!(),
};
transfer.write_word_txd_00(word);
current_byte_index += 4;
}
while read_index < data.len() as u32 {
if transfer.read_status().rx_above_threshold() {
transfer.read_rx_data();
read_index = read_index.wrapping_add(4);
}
}
drop(transfer);
// Now poll for completion.
loop {
let rdsr1 = self.read_status_register_1();
if rdsr1.programming_error() {
// The datasheet mentions that the status should be cleared and writes
// should be disabled explicitely.
self.clear_status();
self.write_disable();
return Err(ProgramPageError::ProgrammingErrorBitSet);
}
if !rdsr1.write_in_progress() {
return Ok(());
}
}
}
pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
let mut qspi = self.0.borrow_mut();
let mut transfer = qspi.transfer_guard();
let raw_word: [u8; 4] = [
RegisterId::FastRead as u8,
((addr >> 16) & 0xff) as u8,
((addr >> 8) & 0xff) as u8,
(addr & 0xff) as u8,
];
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
let mut read_index = 0;
let mut written_words = 0;
let mut bytes_to_write = buf.len();
if dummy_byte {
bytes_to_write += 1;
}
let fifo_writes = bytes_to_write.div_ceil(4);
// Fill the FIFO until it is full or all 0 bytes have been written.
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
transfer.write_word_txd_00(0);
written_words += 1;
}
transfer.start();
let mut reply_word_index = 0;
while read_index < buf.len() {
if transfer.read_status().rx_above_threshold() {
let reply = transfer.read_rx_data();
if reply_word_index == 0 {
reply_word_index += 1;
continue;
}
let reply_as_bytes = reply.to_ne_bytes();
let reply_size = core::cmp::min(buf.len() - read_index, 4);
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
(1, false) => {
buf[read_index] = reply_as_bytes[0];
1
}
(1, true) => {
buf[read_index] = reply_as_bytes[1];
1
}
(2, false) => {
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]);
2
}
(2, true) => {
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]);
2
}
(3, false) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]);
3
}
(3, true) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
3
}
(4, false) => {
buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]);
4
}
(4, true) => {
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
3
}
_ => unreachable!(),
};
reply_word_index += 1;
}
if written_words < fifo_writes && !transfer.read_status().tx_full() {
transfer.write_word_txd_00(0);
written_words += 1;
}
}
}
}
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
impl QspiSpansionS25Fl256SLinearMode {
pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS;
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
let qspi = self.0.into_io_mode(dual_flash);
QspiSpansionS25Fl256SIoMode::new(qspi, false)
}
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
self.0.read_guard()
}
}

View File

@@ -0,0 +1,22 @@
[package]
name = "zedboard-fsbl"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Rust First Stage Bootloader for the Zedboard"
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
license = "MIT OR Apache-2.0"
[dependencies]
cortex-ar = { version = "0.3", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
zynq7000-boot-image = { path = "../../zynq7000-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.7"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"
arbitrary-int = "2"

View File

@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}

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

@@ -0,0 +1,362 @@
//! Simple 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.
//!
//! It can be easily adapted to other boards by changing the static DDR/DDRIOB configuration
//! and the used QSPI memory driver.
#![no_std]
#![no_main]
use arbitrary_int::u6;
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embedded_io::Write as _;
use log::{error, info};
use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
use zynq7000_boot_image::DestinationDevice;
use zynq7000_hal::priv_tim;
use zynq7000_hal::{
BootMode,
clocks::{
Clocks,
pll::{PllConfig, configure_arm_pll, configure_io_pll},
},
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
devcfg, gic, gpio, l2_cache,
prelude::*,
qspi::{self, QSPI_START_ADDRESS},
time::Hertz,
uart::{ClockConfig, Config, Uart},
};
use zynq7000_rt as _;
// PS clock input frequency.
const PS_CLK: Hertz = Hertz::from_raw(33_333_333);
/// 1600 MHz.
const ARM_CLK: Hertz = Hertz::from_raw(1_600_000_000);
/// 1000 MHz.
const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000);
/// DDR frequency for the MT41K128M16JT-125 device.
const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333);
/// 1067 MHz.
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw());
const PERFORM_DDR_MEMTEST: bool = false;
#[derive(Debug, PartialEq, Eq)]
pub enum BootMethod {
Qspi,
SdCard,
}
pub const BOOT_METHOD: BootMethod = BootMethod::Qspi;
pub const ELF_BASE_ADDR: usize = 0x100000;
/// 8 MB reserved for application ELF.
pub const BOOT_BIN_STAGING_OFFSET: usize = 8 * 1024 * 1024;
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let boot_mode = BootMode::new_from_regs();
// The unwraps are okay here, the provided clock frequencies are standard values also used
// by other Xilinx tools.
configure_arm_pll(
boot_mode,
PllConfig::new_from_target_clock(PS_CLK, ARM_CLK).unwrap(),
);
configure_io_pll(
boot_mode,
PllConfig::new_from_target_clock(PS_CLK, IO_CLK).unwrap(),
);
let mut periphs = zynq7000::Peripherals::take().unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let clocks = Clocks::new_from_regs(PS_CLK).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
let mio_pins = gpio_pins.mio;
// Set up the UART, we are logging with it.
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut logger_uart = Uart::new_with_mio(
periphs.uart_1,
Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49),
)
.unwrap();
logger_uart
.write_all(b"-- Zedboard Rust FSBL --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
logger_uart,
log::LevelFilter::Trace,
false,
)
};
// Set up the global interrupt controller.
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
gic.enable_all_interrupts();
gic.set_all_spi_interrupt_targets_cpu0();
gic.enable();
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
info!("Configuring DDR..");
configure_ddr_for_ddr3(
periphs.ddrc,
boot_mode,
DdrClockSetupConfig {
ps_clk: PS_CLK,
ddr_clk: DDR_CLK,
ddr_3x_div: u6::new(2),
ddr_2x_div: u6::new(3),
},
&zedboard_bsp::ddriob_config_autogen::DDRIOB_CONFIG_SET_ZEDBOARD,
&zedboard_bsp::ddrc_config_autogen::DDRC_CONFIG_ZEDBOARD,
);
info!("DDR init done.");
info!("L2 cache init..");
// Set up the L2 cache now that the DDR is in normal operation mode.
l2_cache::init_with_defaults(&mut periphs.l2c);
info!("L2 cache init done.");
let priv_tim = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
if PERFORM_DDR_MEMTEST {
let ddr_base_addr = 0x100000;
info!("performing DDR memory test..");
unsafe {
memtest::walking_zero_test(ddr_base_addr, 64).expect("walking one test failed");
memtest::walking_one_test(ddr_base_addr, 64).expect("walking zero test failed");
memtest::checkerboard_test(ddr_base_addr, 64).expect("checkerboard test failed");
}
info!("DDR memory test success.");
}
if BOOT_METHOD == BootMethod::Qspi {
let qspi_clock_config = qspi::ClockConfig::calculate_with_loopback(
zynq7000::slcr::clocks::SrcSelIo::IoPll,
&clocks,
100.MHz(),
)
.expect("QSPI clock calculation failed");
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
periphs.qspi,
qspi_clock_config,
embedded_hal::spi::MODE_0,
qspi::IoType::LvCmos33,
mio_pins.mio1,
(mio_pins.mio2, mio_pins.mio3, mio_pins.mio4, mio_pins.mio5),
mio_pins.mio6,
mio_pins.mio8,
);
let qspi_io_mode = qspi.into_io_mode(false);
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let spansion_lqspi =
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
qspi_boot(spansion_lqspi, priv_tim);
}
loop {
cortex_ar::asm::nop();
}
}
fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::CpuPrivateTimer) -> ! {
let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET;
let mut boot_header_slice = unsafe {
core::slice::from_raw_parts_mut(
boot_bin_base_addr as *mut u8,
zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE,
)
};
let read_guard = qspi.read_guard();
// Currently, only boot.bin at address 0x0 of the QSPI is supported.
unsafe {
core::ptr::copy_nonoverlapping(
QspiSpansionS25Fl256SLinearMode::BASE_ADDR as *mut u8,
boot_header_slice.as_mut_ptr(),
zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE,
);
}
drop(read_guard);
let boot_header = zynq7000_boot_image::BootHeader::new(boot_header_slice).unwrap();
let fsbl_offset = boot_header.source_offset();
boot_header_slice =
unsafe { core::slice::from_raw_parts_mut(boot_bin_base_addr as *mut u8, fsbl_offset) };
// Read the rest of the boot header metadata.
let read_guard = qspi.read_guard();
unsafe {
core::ptr::copy_nonoverlapping(
(QspiSpansionS25Fl256SLinearMode::BASE_ADDR
+ zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE) as *mut u8,
boot_header_slice[zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE..].as_mut_ptr(),
fsbl_offset - zynq7000_boot_image::FIXED_BOOT_HEADER_SIZE,
);
}
drop(read_guard);
let boot_header = zynq7000_boot_image::BootHeader::new_unchecked(boot_header_slice);
let mut name_buf: [u8; 256] = [0; 256];
let mut opt_jump_addr = None;
for (index, image_header) in boot_header.image_header_iterator().unwrap().enumerate() {
let name = image_header.image_name(&mut name_buf).unwrap();
if index == 0 {
if !name.contains("fsbl") {
log::warn!("first image name did not contain FSBL string");
}
// Skip the FSBL. It is probably currently running, and we do not want to re-flash it,
// which would also lead to a self-overwrite.
log::info!("skipping FSBL image");
continue;
}
let _read_guard = qspi.read_guard();
for (partition_index, partition) in image_header
.partition_header_iterator(boot_header_slice)
.unwrap()
.enumerate()
{
let section_attrs = partition.section_attributes();
if section_attrs.destination_device().is_err() {
log::error!(
"invalid destination device ID {}",
section_attrs.destination_device().unwrap_err()
);
continue;
}
let dest_dev = section_attrs.destination_device().unwrap();
match dest_dev {
DestinationDevice::Pl => {
info!("Loading image '{name}' to PL (FPGA)..");
// Load the bitstream directly from linear mapped QSPI memory.
let boot_bin_slice = unsafe {
core::slice::from_raw_parts(
(QSPI_START_ADDRESS
+ partition
.data_offset()
.expect("invalid PL partition data offset"))
as *const _,
partition.total_partition_length().unwrap(),
)
};
// The DMA will read from the linear mapped QSPI directly, so it
// has to be configured for reads using the guard!
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
.expect("unexpected unaligned address");
log::info!("loaded bitstream successfully");
}
DestinationDevice::Ps => {
// Load the bitstream directly from linear mapped QSPI memory.
let load_addr = partition.destination_load_address();
if load_addr < 0x10_0000 {
panic!("invalid load address which is not located in DDR memory");
}
log::info!(
"Loading partition {partition_index} for '{name}' to PS with load address {load_addr}.."
);
let source_slice = unsafe {
core::slice::from_raw_parts(
(QSPI_START_ADDRESS
+ partition
.data_offset()
.expect("invalid PS partition data offset"))
as *const _,
partition.total_partition_length().unwrap(),
)
};
let target_slice = unsafe {
core::slice::from_raw_parts_mut(
load_addr as *mut u8,
partition.total_partition_length().unwrap(),
)
};
// Copy from the linear mapped QSPI to DDR,
target_slice.copy_from_slice(source_slice);
match &mut opt_jump_addr {
Some(current) => *current = core::cmp::min(*current, load_addr),
None => opt_jump_addr = Some(load_addr),
}
log::info!("load success");
}
_ => {
error!("Unsupported destination device {dest_dev:?}");
continue;
}
}
}
}
match opt_jump_addr {
Some(jump_addr) => {
log::info!("jumping to address {}", jump_addr);
zynq7000_hal::log::uart_blocking::flush();
// Some clean up and preparation for jumping to the user application.
zynq7000_hal::cache::clean_and_invalidate_data_cache();
cortex_ar::register::TlbIAll::write();
cortex_ar::register::BpIAll::write();
cortex_ar::asm::dsb();
cortex_ar::asm::isb();
let jump_func: extern "C" fn() -> ! = unsafe { core::mem::transmute(jump_addr) };
jump_func();
}
None => panic!("did not find application elf to boot inside boot binary!"),
}
}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
error!("Panic: {info:?}");
loop {}
}

View File

@@ -0,0 +1,16 @@
[package]
name = "zedboard-qspi-flasher"
version = "0.1.0"
edition = "2024"
[dependencies]
cortex-ar = { version = "0.3" }
zynq7000-rt = { path = "../zynq7000-rt" }
zynq7000 = { path = "../zynq7000" }
zynq7000-hal = { path = "../zynq7000-hal" }
zynq7000-boot-image = { path = "../../zynq7000-boot-image" }
zedboard-bsp = { path = "../zedboard-bsp" }
embedded-io = "0.7"
embedded-hal = "1"
log = "0.4"
libm = "0.2"

View File

@@ -0,0 +1,31 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}

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
}

View File

@@ -0,0 +1,151 @@
if {[info exists env(ip_address_hw_server)]} {
set ip $env(ip_address_hw_server)
} else {
set ip "localhost"
}
# absolute directory that contains *this* script
set script_dir [file dirname [info script]]
# Defaults
set boot_bin_addr 0x10000000
set boot_bin_size_addr 0x900000
set init_tcl ""
set bin ""
set bitstream ""
# Usage helper
proc usage {} {
puts "Usage: xsct qspi-flasher.tcl <init.tcl> \[-b|--bin <boot.bin>\]"
puts "Options:"
puts " -b, --bin Path to boot binary to flash"
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 "-b" || $arg eq "--bin"} { set expecting bin; 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
}
}
# Check that QSPI flasher app exists.
set flasher_app [file join $script_dir .. target armv7a-none-eabihf release zedboard-qspi-flasher]
if {![file exists $flasher_app]} {
error "QSPI flasher application not found at path: $flasher_app"
}
set flasher_app [file normalize $flasher_app]
# 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 {$bin ne ""} {
if {![file exists $bin]} {
puts "error: the boot binary file '$bin' does not exist"
exit 1
}
} elseif {[info exists env(BOOTBIN)]} {
if {[file exists $env(BOOTBIN)]} {
set bin $env(BOOTBIN)
} else {
puts "warning: BOOTBIN environment variable is set but file does not exist: $env(BOOTBIN)"
}
}
if {$bin eq ""} {
puts "error: boot.bin binary required"
usage
exit 1
}
# 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
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
puts "Download boot.bin $bin to target DDR at address $boot_bin_addr"
dow -data $bin $boot_bin_addr
# Write boot.bin binary size to specific address.
set boot_bin_size [file size $bin]
puts "Writing boot.bin size $boot_bin_size to target DDR at address $boot_bin_size_addr"
mwr ${boot_bin_size_addr} ${boot_bin_size}
puts "Flashing QSPI flasher app"
dow $flasher_app
puts "Starting QSPI flasher app"
con
puts "Success"

View File

@@ -0,0 +1,225 @@
//! QSPI flasher for the Zedboard. Assumes that external scripting took care of transferring
//! a boot binary to RAM.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
use embedded_io::Write as _;
use log::{error, info};
use zedboard_bsp::qspi_spansion;
use zynq7000_boot_image::BootHeader;
use zynq7000_hal::{
BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart,
};
use zynq7000_rt as _;
// Define the clock frequency as a constant.
//
// Not required for the PAC mode, is required for clean delays in HAL mode.
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
// TODO: Make this configurable somehow?
const BOOT_BIN_BASE_ADDR: usize = 0x1000_0000;
const BOOT_BIN_SIZE_ADDR: usize = 0x900_000;
// Maximum of 16 MB is allowed for now.
const MAX_BOOT_BIN_SIZE: usize = 16 * 1024 * 1024;
const VERIFY_PROGRAMMING: bool = true;
#[allow(dead_code)]
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
vendor: qspi::QspiVendor::WinbondAndSpansion,
operating_mode: qspi::OperatingMode::FastReadQuadOutput,
two_devices: false,
};
/// Entry point (not called like a normal main function)
#[unsafe(no_mangle)]
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI flasher --\n\r";
#[unsafe(export_name = "main")]
pub fn main() -> ! {
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
init_l2_cache: true,
level_shifter_config: Some(LevelShifterConfig::EnableAll),
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
})
.unwrap();
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// Unwrap okay, we only call this once on core 0 here.
let mut timer = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut uart = uart::Uart::new_with_mio(
periphs.uart_1,
uart::Config::new_with_clk_config(uart_clk_config),
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
)
.unwrap();
uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Info,
false,
)
};
let boot_mode = BootMode::new_from_regs();
info!("Boot mode: {:?}", boot_mode);
let qspi_clock_config =
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
.expect("QSPI clock calculation failed");
let qspi = qspi::Qspi::new_single_qspi_with_feedback(
periphs.qspi,
qspi_clock_config,
qspi::MODE_0,
qspi::IoType::LvCmos33,
gpio_pins.mio.mio1,
(
gpio_pins.mio.mio2,
gpio_pins.mio.mio3,
gpio_pins.mio.mio4,
gpio_pins.mio.mio5,
),
gpio_pins.mio.mio6,
gpio_pins.mio.mio8,
);
let qspi_io_mode = qspi.into_io_mode(false);
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let mut boot_bin_slice = unsafe {
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
};
// This perform some basic validity checks.
let _boot_header = BootHeader::new(&boot_bin_slice[0..BootHeader::FIXED_SIZED_PART])
.expect("failed to parse boot header");
let boot_bin_size =
unsafe { core::ptr::read_volatile(BOOT_BIN_SIZE_ADDR as *const u32) as usize };
if boot_bin_size == 0 || boot_bin_size > MAX_BOOT_BIN_SIZE {
panic!(
"boot binary size read at address {:#x} is invalid: found {}, must be in range [0, {}]",
BOOT_BIN_SIZE_ADDR, boot_bin_size, MAX_BOOT_BIN_SIZE
);
}
boot_bin_slice =
unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, boot_bin_size) };
info!(
"flashing boot binary with {} bytes to QSPI address 0x0",
boot_bin_size
);
let mut current_addr = 0;
let mut read_buf = [0u8; 256];
let mut next_checkpoint = 0.05;
while current_addr < boot_bin_size {
if current_addr % 0x10000 == 0 {
log::debug!("Erasing sector at address {:#x}", current_addr);
match spansion_qspi.erase_sector(current_addr as u32) {
Ok(()) => {}
Err(e) => {
error!(
"failed to erase sector at address {:#x}: {:?}",
current_addr, e
);
panic!("QSPI erase failed");
}
}
}
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
log::debug!("Programming address {:#x}", current_addr);
match spansion_qspi.program_page(current_addr as u32, write_slice) {
Ok(()) => {}
Err(e) => {
log::error!(
"failed to write data to QSPI at address {:#x}: {:?}",
current_addr,
e
);
panic!("QSPI write failed");
}
}
if VERIFY_PROGRAMMING {
spansion_qspi.read_page_fast_read(
current_addr as u32,
&mut read_buf[0..write_size],
true,
);
if &read_buf[0..write_size] != write_slice {
error!(
"data verification failed at address {:#x}: wrote {:x?}, read {:x?}",
current_addr,
&write_slice[0..core::cmp::min(16, write_size)],
&read_buf[0..core::cmp::min(16, write_size)]
);
panic!("QSPI data verification failed");
}
}
current_addr += write_size;
if current_addr as f32 / boot_bin_size as f32 >= next_checkpoint {
log::info!("Write progress {} %", libm::roundf(next_checkpoint * 100.0));
next_checkpoint += 0.05;
}
}
info!("flashing done");
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
loop {
mio_led.toggle().unwrap();
timer.delay_ms(500);
}
}
#[zynq7000_rt::irq]
pub fn irq_handler() {}
#[zynq7000_rt::exception(DataAbort)]
fn data_abort_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(Undefined)]
fn undefined_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
#[zynq7000_rt::exception(PrefetchAbort)]
fn prefetch_handler(_faulting_addr: usize) -> ! {
loop {
nop();
}
}
/// Panic handler
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {
nop();
}
}

View File

@@ -11,20 +11,21 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", rev = "79dba7000d2090d13823bfb783d9d64be8b778d2" }
cortex-ar = { version = "0.3" }
zynq7000 = { path = "../zynq7000" }
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1.0" }
bitbybit = "1.3"
arbitrary-int = "1.3"
static_assertions = "1.1"
bitbybit = "1.4"
arbitrary-int = "2"
thiserror = { version = "2", default-features = false }
num_enum = { version = "0.7", default-features = false }
ringbuf = { version = "0.4.8", default-features = false }
embedded-hal-nb = "1"
embedded-io = "0.6"
embedded-io = "0.7"
embedded-hal = "1"
embedded-hal-async = "1"
heapless = "0.8"
heapless = "0.9"
static_cell = "2"
delegate = "0.13"
paste = "1"
@@ -38,7 +39,7 @@ embassy-net-driver = "0.2"
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
vcell = "0.1"
raw-slicee = "0.1"
embedded-io-async = "0.6"
embedded-io-async = "0.7"
[features]
std = ["thiserror/std", "alloc"]

View File

@@ -1,5 +1,7 @@
//! Clock module.
use arbitrary_int::Number;
use arbitrary_int::{prelude::*, u6};
pub mod pll;
use zynq7000::slcr::{
ClockControl,
@@ -45,21 +47,67 @@ impl ArmClocks {
#[derive(Debug)]
pub struct DdrClocks {
/// DDR reference clock generated by the DDR PLL.
ref_clk: Hertz,
ddr_3x_clk: Hertz,
ddr_2x_clk: Hertz,
}
impl DdrClocks {
/// Update the DDR 3x and 2x clocks in the SLCR.
///
/// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
/// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
/// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
/// operating frequency.
///
/// # Safety
///
/// This should only be called once during start-up. It accesses the SLCR register.
pub unsafe fn configure_2x_3x_clk(ddr_3x_div: u6, ddr_2x_div: u6) {
// Safety: The DDR clock structure is a singleton.
unsafe {
crate::slcr::Slcr::with(|slcr| {
slcr.clk_ctrl().modify_ddr_clk_ctrl(|mut val| {
val.set_div_3x_clk(ddr_3x_div);
val.set_div_2x_clk(ddr_2x_div);
val
});
});
}
}
/// Update the DDR 3x and 2x clocks in the SLCR and creates a DDR clock information structure.
///
/// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
/// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
/// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
/// operating frequency.
///
/// # Safety
///
/// This should only be called once during start-up. It accesses the SLCR register.
pub unsafe fn new_with_2x_3x_init(ref_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
unsafe { Self::configure_2x_3x_clk(ddr_3x_div, ddr_2x_div) };
Self {
ref_clk,
ddr_3x_clk: ref_clk / ddr_3x_div.as_u32(),
ddr_2x_clk: ref_clk / ddr_2x_div.as_u32(),
}
}
/// Reference clock provided by DDR PLL which is used to calculate all other clock frequencies.
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
/// DDR 3x clock which is used by the DRAM and must be set to the operating frequency.
pub fn ddr_3x_clk(&self) -> Hertz {
self.ddr_3x_clk
}
/// DDR 2x clock is used by the interconnect and is typically set to 2/3 of the operating
/// frequency.
pub fn ddr_2x_clk(&self) -> Hertz {
self.ddr_2x_clk
}
@@ -178,13 +226,16 @@ pub enum ClockModuleId {
#[derive(Debug)]
pub struct DivisorZero(pub ClockModuleId);
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum ClockReadError {
/// The feedback value for the PLL clock output calculation is zero.
#[error("PLL feedback divisor is zero")]
PllFeedbackZero,
/// Detected a divisor of zero.
#[error("divisor is zero")]
DivisorZero(DivisorZero),
/// Detected a divisor that is not even.
/// Detected a divisor that is not even and should be.
#[error("divisor is not even")]
DivisorNotEven,
}
@@ -201,9 +252,9 @@ impl Clocks {
pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() };
let arm_pll_cfg = clk_regs.read_arm_pll();
let io_pll_cfg = clk_regs.read_io_pll();
let ddr_pll_cfg = clk_regs.read_ddr_pll();
let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
let io_pll_cfg = clk_regs.read_io_pll_ctrl();
let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl();
if arm_pll_cfg.fdiv().as_u32() == 0
|| io_pll_cfg.fdiv().as_u32() == 0

View File

@@ -0,0 +1,356 @@
use core::sync::atomic::AtomicBool;
use arbitrary_int::{u4, u7, u10};
use crate::{BootMode, time::Hertz};
/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MIN: u32 = 13;
/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744
pub const PLL_MUL_MAX: u32 = 66;
static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false);
static IO_PLL_INIT: AtomicBool = AtomicBool::new(false);
static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")]
pub struct MulOutOfRangeError(pub u32);
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PllConfigCtorError {
#[error("invalid input")]
InvalidInput,
#[error("pll multiplier out of range: {0}")]
MulOutOfRange(#[from] MulOutOfRangeError),
}
pub struct PllConfig {
fdiv: u7,
charge_pump: u4,
loop_resistor: u4,
lock_count: u10,
}
impl PllConfig {
pub fn new_from_target_clock(
ps_clk: Hertz,
target_clk: Hertz,
) -> Result<Self, PllConfigCtorError> {
if ps_clk.raw() == 0 {
return Err(PllConfigCtorError::InvalidInput);
}
let mul = target_clk / ps_clk;
Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange)
}
/// Create a new PLL configuration based on the multiplier value.
///
/// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744.
pub fn new(pll_mul: u32) -> Result<Self, MulOutOfRangeError> {
if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) {
return Err(MulOutOfRangeError(pll_mul));
}
Ok(match pll_mul {
13 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(750),
),
14 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(700),
),
15 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(6),
u10::new(650),
),
16 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(625),
),
17 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(575),
),
18 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(550),
),
19 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(10),
u10::new(525),
),
20 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(500),
),
21 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(475),
),
22 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(450),
),
23 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(425),
),
24..=25 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(400),
),
26 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(375),
),
27..=28 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(350),
),
29..=30 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(12),
u10::new(325),
),
31..=33 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(300),
),
34..=36 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(275),
),
37..=40 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(2),
u10::new(250),
),
41..=47 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(3),
u4::new(12),
u10::new(250),
),
48..=66 => Self::new_raw(
u7::new(pll_mul as u8),
u4::new(2),
u4::new(4),
u10::new(250),
),
_ => {
unreachable!()
}
})
}
/// Create a new PLL configuration with raw values.
///
/// It is recommended to use [Self::new] instead, which creates a configuration
/// based on a look-up table provided in the Zynq-7000 TRM.
pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self {
Self {
fdiv,
charge_pump,
loop_resistor,
lock_count,
}
}
}
/// This function configures the ARM PLL based on the provided [PllConfig].
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the IO PLL based on the provided [PllConfig].
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the DDR PLL based on the provided [PllConfig].
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
return;
}
// Safety: This will only run at most once because of the atomic boolean check.
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
}
/// This function configures the ARM PLL based on the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Arm,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
/// This function configures the IO PLL based on the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Io,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
/// This function configures the DDR PLL based on the provided [PllConfig].
///
/// # Safety
///
/// This function should only be called once during system initialization, for example in the
/// first-stage bootloader (FSBL).
pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
unsafe {
crate::slcr::Slcr::with(|slcr| {
let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl();
let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg();
configure_pll_unchecked(
boot_mode,
pll_config,
PllType::Ddr,
slcr,
pll_ctrl_reg,
pll_cfg_reg,
);
});
}
}
enum PllType {
Io,
Ddr,
Arm,
}
impl PllType {
pub const fn bit_offset_pll_locked(&self) -> usize {
match self {
PllType::Io => 2,
PllType::Ddr => 1,
PllType::Arm => 0,
}
}
}
unsafe fn configure_pll_unchecked(
boot_mode: BootMode,
cfg: PllConfig,
pll_type: PllType,
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
) {
// Step 1: Program the multiplier and other PLL configuration parameters.
// New values will only be consumed once the PLL is reset.
let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_fdiv(cfg.fdiv);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) };
pll_cfg.set_charge_pump(cfg.charge_pump);
pll_cfg.set_loop_resistor(cfg.loop_resistor);
pll_cfg.set_lock_count(cfg.lock_count);
unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) };
// Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high,
// the PLLs need to be enabled. According to the TRM, this is done by setting the
// PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL.
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed {
pll_ctrl.set_bypass_qual(false);
}
pll_ctrl.set_bypass_force(true);
pll_ctrl.set_pwrdwn(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
// Step 3: Reset the PLL. This also loads the new configuration.
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_reset(true);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
pll_ctrl.set_reset(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
& 0b1)
!= 1
{
cortex_ar::asm::nop();
}
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
pll_ctrl.set_bypass_force(false);
unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
}

View File

@@ -0,0 +1,335 @@
//! Low-level DDR configuration module.
use arbitrary_int::{prelude::*, u2, u3, u6};
use zynq7000::ddrc::{MmioDdrController, regs::*};
use zynq7000::slcr::{clocks::DciClockControl, ddriob::DdriobConfig};
use crate::{clocks::DdrClocks, time::Hertz};
const DCI_MAX_FREQ: Hertz = Hertz::from_raw(10_000_000);
// These values were extracted from the ps7_init files and are not documented in the TMR.
// zynq-rs uses the same values.. I assume they are constant.
pub const DRIVE_SLEW_ADDR_CFG: u32 = 0x0018_c61c;
pub const DRIVE_SLEW_DATA_CFG: u32 = 0x00f9_861c;
pub const DRIVE_SLEW_DIFF_CFG: u32 = 0x00f9_861c;
pub const DRIVE_SLEW_CLOCK_CFG: u32 = 0x00f9_861c;
#[derive(Debug, Clone, Copy)]
pub struct DciClkConfig {
div0: u6,
div1: u6,
}
/// Calculate the required DCI divisors for the given DDR clock.
pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
calculate_dci_divisors_with_ddr_clk(ddr_clks.ref_clk())
}
/// Calculate the required DCI divisors for the given DDR clock frequency.
pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig {
let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw());
let mut config = DciClkConfig {
div0: u6::new(u6::MAX.value()),
div1: u6::new(u6::MAX.value()),
};
let mut best_error = 0;
for divisor0 in 1..63 {
for divisor1 in 1..63 {
let current_div = (divisor0 as u32) * (divisor1 as u32);
let error = current_div.abs_diff(target_div);
if error < best_error {
config.div0 = u6::new(divisor0 as u8);
config.div1 = u6::new(divisor1 as u8);
best_error = error;
}
}
}
config
}
/// Configure the DCI module by configure its clock divisors and enabling it.
///
/// # Safety
///
/// This function writes to DCI related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
let cfg = calculate_dci_divisors(ddr_clk);
// Safety: Only writes to DCI clock related registers.
unsafe {
crate::Slcr::with(|slcr| {
slcr.clk_ctrl().write_dci_clk_ctrl(
DciClockControl::builder()
.with_divisor_1(cfg.div1)
.with_divisor_0(cfg.div0)
.with_clk_act(true)
.build(),
);
});
}
}
/// Calibrates the IOB impedance for DDR3 memory according to to TRM p.325, DDR IOB Impedance
/// calibration.
///
/// This function will also enable the DCI clock with the provided clock configuration.
/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock,
/// or you can hardcode the values if they are fixed.
///
/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
/// the user can set other configuration values which are not reliant on DDR operation before
/// polling for completion.
///
/// # Safety
///
/// This function writes to the DDR IOB related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) {
unsafe {
calibrate_iob_impedance(
dci_clk_cfg,
u3::new(0),
u2::new(0),
u3::new(0b001),
u3::new(0),
u2::new(0),
poll_for_done,
);
}
}
/// Calibrates the IOB impedance according to to TRM p.325, DDR IOB Impedance calibration.
///
/// This function will also enable the DCI clock with the provided clock configuration.
/// You can use [calculate_dci_divisors] to calculate the divisor values for the given DDR clock,
/// or you can hardcode the values if they are fixed.
///
/// Polling for completion can be disabled. The calibration takes 1-2 ms according to the TRM, so
/// the user can set other configuration values which are not reliant on DDR operation before
/// polling for completion.
///
/// # Safety
///
/// This function writes to the DDR IOB related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn calibrate_iob_impedance(
dci_clk_cfg: DciClkConfig,
pref_opt2: u3,
pref_opt1: u2,
nref_opt4: u3,
nref_opt2: u3,
nref_opt1: u2,
poll_for_done: bool,
) {
// Safety: Only writes to DDR IOB related registers.
let mut slcr = unsafe { crate::slcr::Slcr::steal() };
slcr.modify(|slcr| {
slcr.clk_ctrl().write_dci_clk_ctrl(
DciClockControl::builder()
.with_divisor_1(dci_clk_cfg.div1)
.with_divisor_0(dci_clk_cfg.div0)
.with_clk_act(true)
.build(),
);
let mut ddriob = slcr.ddriob();
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(true);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(false);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_reset(true);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_pref_opt2(pref_opt2);
val.set_pref_opt1(pref_opt1);
val.set_nref_opt4(nref_opt4);
val.set_nref_opt2(nref_opt2);
val.set_nref_opt1(nref_opt1);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_update_control(false);
val
});
ddriob.modify_dci_ctrl(|mut val| {
val.set_enable(true);
val
});
if poll_for_done {
while !slcr.ddriob().read_dci_status().done() {
// Wait for the DDR IOB impedance calibration to complete.
cortex_ar::asm::nop();
}
}
});
}
/// Static configuration for DDR IOBs.
pub struct DdriobConfigSet {
pub addr0: DdriobConfig,
pub addr1: DdriobConfig,
pub data0: DdriobConfig,
pub data1: DdriobConfig,
pub diff0: DdriobConfig,
pub diff1: DdriobConfig,
pub clock: DdriobConfig,
}
/// # Safety
///
/// This function writes to the IOB related registers. It should only be called once during
/// DDR initialization.
pub unsafe fn configure_iob(cfg_set: &DdriobConfigSet) {
// Safety: Only configures IOB related registers.
let mut slcr = unsafe { crate::slcr::Slcr::steal() };
slcr.modify(|slcr| {
let mut ddriob = slcr.ddriob();
ddriob.write_ddriob_addr0(cfg_set.addr0);
ddriob.write_ddriob_addr1(cfg_set.addr1);
ddriob.write_ddriob_data0(cfg_set.data0);
ddriob.write_ddriob_data1(cfg_set.data1);
ddriob.write_ddriob_diff0(cfg_set.diff0);
ddriob.write_ddriob_diff1(cfg_set.diff1);
ddriob.write_ddriob_clock(cfg_set.clock);
// These values were extracted from the ps7_init files and are not documented in the TRM.
// zynq-rs uses the same values.. I assume they are constant.
ddriob.write_ddriob_drive_slew_addr(DRIVE_SLEW_ADDR_CFG);
ddriob.write_ddriob_drive_slew_data(DRIVE_SLEW_DATA_CFG);
ddriob.write_ddriob_drive_slew_diff(DRIVE_SLEW_DIFF_CFG);
ddriob.write_ddriob_drive_slew_clock(DRIVE_SLEW_CLOCK_CFG);
});
}
/// Full static DDRC configuration set.
#[derive(Debug)]
pub struct DdrcConfigSet {
pub ctrl: DdrcControl,
pub two_rank: TwoRankConfig,
pub hpr: LprHprQueueControl,
pub lpr: LprHprQueueControl,
pub wr: WriteQueueControl,
pub dram_param_0: DramParamReg0,
pub dram_param_1: DramParamReg1,
pub dram_param_2: DramParamReg2,
pub dram_param_3: DramParamReg3,
pub dram_param_4: DramParamReg4,
pub dram_init_param: DramInitParam,
pub dram_emr: DramEmr,
pub dram_emr_mr: DramEmrMr,
pub dram_burst8_rdwr: DramBurst8ReadWrite,
pub disable_dq: DisableDq,
pub dram_addr_map_bank: DramAddrMapBank,
pub dram_addr_map_col: DramAddrMapColumn,
pub dram_addr_map_row: DramAddrMapRow,
pub dram_odt: DramOdt,
pub phy_cmd_timeout_rddata_cpt: PhyCmdTimeoutRdDataCpt,
pub dll_calib: DllCalib,
pub odt_delay_hold: OdtDelayHold,
pub ctrl_reg1: CtrlReg1,
pub ctrl_reg2: CtrlReg2,
pub ctrl_reg3: CtrlReg3,
pub ctrl_reg4: CtrlReg4,
pub ctrl_reg5: CtrlReg5,
pub ctrl_reg6: CtrlReg6,
pub che_t_zq: CheTZq,
pub che_t_zq_short_interval_reg: CheTZqShortInterval,
pub deep_powerdown: DeepPowerdown,
pub reg_2c: Reg2c,
pub reg_2d: Reg2d,
pub dfi_timing: DfiTiming,
pub che_ecc_ctrl: CheEccControl,
pub ecc_scrub: EccScrub,
pub phy_receiver_enable: PhyReceiverEnable,
pub phy_config: [PhyConfig; 4],
pub phy_init_ratio: [PhyInitRatio; 4],
pub phy_rd_dqs_config: [PhyDqsConfig; 4],
pub phy_wr_dqs_config: [PhyDqsConfig; 4],
pub phy_we_cfg: [PhyWriteEnableConfig; 4],
pub phy_wr_data_slv: [PhyWriteDataSlaveConfig; 4],
pub reg64: Reg64,
pub reg65: Reg65,
pub page_mask: u32,
pub axi_priority_wr_port: [AxiPriorityWritePort; 4],
pub axi_priority_rd_port: [AxiPriorityReadPort; 4],
pub lpddr_ctrl_0: LpddrControl0,
pub lpddr_ctrl_1: LpddrControl1,
pub lpddr_ctrl_2: LpddrControl2,
pub lpddr_ctrl_3: LpddrControl3,
}
/// This low-level function sets all the configuration registers.
///
/// It does NOT take care of taking the DDR controller out of reset and polling for DDR
/// configuration completion.
pub fn configure_ddr_config(ddrc: &mut MmioDdrController<'static>, cfg_set: &DdrcConfigSet) {
ddrc.write_ddrc_ctrl(cfg_set.ctrl);
// Write all configuration registers.
ddrc.write_two_rank_cfg(cfg_set.two_rank);
ddrc.write_hpr_queue_ctrl(cfg_set.hpr);
ddrc.write_lpr_queue_ctrl(cfg_set.lpr);
ddrc.write_wr_reg(cfg_set.wr);
ddrc.write_dram_param_reg0(cfg_set.dram_param_0);
ddrc.write_dram_param_reg1(cfg_set.dram_param_1);
ddrc.write_dram_param_reg2(cfg_set.dram_param_2);
ddrc.write_dram_param_reg3(cfg_set.dram_param_3);
ddrc.write_dram_param_reg4(cfg_set.dram_param_4);
ddrc.write_dram_init_param(cfg_set.dram_init_param);
ddrc.write_dram_emr(cfg_set.dram_emr);
ddrc.write_dram_emr_mr(cfg_set.dram_emr_mr);
ddrc.write_dram_burst8_rdwr(cfg_set.dram_burst8_rdwr);
ddrc.write_dram_disable_dq(cfg_set.disable_dq);
ddrc.write_phy_cmd_timeout_rddata_cpt(cfg_set.phy_cmd_timeout_rddata_cpt);
ddrc.write_dll_calib(cfg_set.dll_calib);
ddrc.write_odt_delay_hold(cfg_set.odt_delay_hold);
ddrc.write_ctrl_reg1(cfg_set.ctrl_reg1);
ddrc.write_ctrl_reg2(cfg_set.ctrl_reg2);
ddrc.write_ctrl_reg3(cfg_set.ctrl_reg3);
ddrc.write_ctrl_reg4(cfg_set.ctrl_reg4);
ddrc.write_ctrl_reg5(cfg_set.ctrl_reg5);
ddrc.write_ctrl_reg6(cfg_set.ctrl_reg6);
ddrc.write_che_t_zq(cfg_set.che_t_zq);
ddrc.write_che_t_zq_short_interval_reg(cfg_set.che_t_zq_short_interval_reg);
ddrc.write_deep_powerdown_reg(cfg_set.deep_powerdown);
ddrc.write_reg_2c(cfg_set.reg_2c);
ddrc.write_reg_2d(cfg_set.reg_2d);
ddrc.write_dfi_timing(cfg_set.dfi_timing);
ddrc.write_che_ecc_control(cfg_set.che_ecc_ctrl);
ddrc.write_ecc_scrub(cfg_set.ecc_scrub);
ddrc.write_phy_receiver_enable(cfg_set.phy_receiver_enable);
for i in 0..4 {
// Safety: Indexes are valid.
unsafe {
ddrc.write_phy_config_unchecked(i, cfg_set.phy_config[i]);
ddrc.write_phy_init_ratio_unchecked(i, cfg_set.phy_init_ratio[i]);
ddrc.write_phy_rd_dqs_cfg_unchecked(i, cfg_set.phy_rd_dqs_config[i]);
ddrc.write_phy_wr_dqs_cfg_unchecked(i, cfg_set.phy_wr_dqs_config[i]);
ddrc.write_phy_we_cfg_unchecked(i, cfg_set.phy_we_cfg[i]);
ddrc.write_phy_wr_data_slave_unchecked(i, cfg_set.phy_wr_data_slv[i]);
}
}
ddrc.write_reg_64(cfg_set.reg64);
ddrc.write_reg_65(cfg_set.reg65);
ddrc.write_page_mask(cfg_set.page_mask);
for i in 0..4 {
// Safety: Indexes are valid.
unsafe {
ddrc.write_axi_priority_wr_port_unchecked(i, cfg_set.axi_priority_wr_port[i]);
ddrc.write_axi_priority_rd_port_unchecked(i, cfg_set.axi_priority_rd_port[i]);
}
}
ddrc.write_lpddr_ctrl_0(cfg_set.lpddr_ctrl_0);
ddrc.write_lpddr_ctrl_1(cfg_set.lpddr_ctrl_1);
ddrc.write_lpddr_ctrl_2(cfg_set.lpddr_ctrl_2);
ddrc.write_lpddr_ctrl_3(cfg_set.lpddr_ctrl_3);
}

View File

@@ -0,0 +1,227 @@
use arbitrary_int::u6;
use zynq7000::ddrc::MmioDdrController;
use crate::{
BootMode,
clocks::pll::{PllConfig, configure_ddr_pll},
time::Hertz,
};
pub mod ll;
pub use ll::{DdrcConfigSet, DdriobConfigSet};
#[derive(Debug, Clone, Copy)]
pub struct DdrClockSetupConfig {
pub ps_clk: Hertz,
pub ddr_clk: Hertz,
pub ddr_3x_div: u6,
pub ddr_2x_div: u6,
}
impl DdrClockSetupConfig {
pub const fn new(ps_clk: Hertz, ddr_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
Self {
ps_clk,
ddr_clk,
ddr_3x_div,
ddr_2x_div,
}
}
}
/// This completely sets up the DDR module for DDR3 operation.
///
/// It performs the following steps accoridng to the functional programming model of the
/// DDR memory controller, TRM p.323.
///
/// 1. Configures the DDR PLL to the target clock frequency.
/// 2. Configures the DDR clocks based on the user-provided configuration.
/// 3. Configures and sets up the DDR I/O buffers (DDR IOB) module.
/// 4. Configures and sets up the DDR controller (DDRC) module.
///
/// This function consumes the DDRC register block once and thus provides a safe interface for DDR
/// initialization.
pub fn configure_ddr_for_ddr3(
mut ddrc_regs: MmioDdrController<'static>,
boot_mode: BootMode,
clk_setup_cfg: DdrClockSetupConfig,
ddriob_cfg: &DdriobConfigSet,
ddr_cfg: &DdrcConfigSet,
) {
// Set the DDR PLL output frequency to an even multiple of the operating frequency,
// as recommended by the DDR documentation.
configure_ddr_pll(
boot_mode,
PllConfig::new_from_target_clock(clk_setup_cfg.ps_clk, clk_setup_cfg.ddr_clk).unwrap(),
);
// Safety: Only done once here during start-up.
let ddr_clks = unsafe {
crate::clocks::DdrClocks::new_with_2x_3x_init(
clk_setup_cfg.ddr_clk,
clk_setup_cfg.ddr_3x_div,
clk_setup_cfg.ddr_2x_div,
)
};
let dci_clk_cfg = ll::calculate_dci_divisors(&ddr_clks);
ddrc_regs.modify_ddrc_ctrl(|mut val| {
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Reset);
val
});
// Safety: This is only called once during DDR initialization.
unsafe {
ll::configure_iob(ddriob_cfg);
// Do not wait for completion, it takes a bit of time. We can set all the DDR config registers
// before polling for completion.
ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
}
ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg);
// Safety: This is only called once during DDR initialization, and we only modify DDR related
// registers.
let slcr = unsafe { crate::slcr::Slcr::steal() };
let ddriob_shared = slcr.regs().ddriob_shared();
// Wait for DDR IOB impedance calibration to complete first.
while !ddriob_shared.read_dci_status().done() {
cortex_ar::asm::nop();
}
log::debug!("DDR IOB impedance calib done");
// Now take the DDR out of reset.
ddrc_regs.modify_ddrc_ctrl(|mut val| {
val.set_soft_reset(zynq7000::ddrc::regs::SoftReset::Active);
val
});
// Wait until the DDR setup has completed.
while ddrc_regs.read_mode_status().operating_mode()
!= zynq7000::ddrc::regs::OperatingMode::NormalOperation
{
// Wait for the soft reset to complete.
cortex_ar::asm::nop();
}
}
pub mod memtest {
#[derive(Debug, thiserror::Error)]
pub enum MemTestError {
#[error("memory address is not aligned to 4 bytes")]
AddrNotAligned,
#[error("memory test error")]
Memory {
addr: usize,
expected: u32,
found: u32,
},
}
/// # Safety
///
/// This tests writes and reads on a memory block starting at the base address
/// with the size `words` times 4.
pub unsafe fn walking_zero_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
unsafe { walking_value_test(true, base_addr, words) }
}
/// # Safety
///
/// This tests writes and reads on a memory block starting at the base address
/// with the size `words` times 4.
pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
unsafe { walking_value_test(true, base_addr, words) }
}
/// # Safety
///
/// This tests writes and reads on a memory block starting at the base address
/// with the size `words` times 4.
pub unsafe fn walking_value_test(
walking_zero: bool,
base_addr: usize,
words: usize,
) -> Result<(), MemTestError> {
if words == 0 {
return Ok(());
}
if !base_addr.is_multiple_of(4) {
return Err(MemTestError::AddrNotAligned);
}
let base_ptr = base_addr as *mut u32;
// For each bit position 0..31 generate pattern = 1 << bit
for bit in 0..32 {
let pattern = if walking_zero {
!(1u32 << bit)
} else {
1u32 << bit
};
// write pass
for i in 0..words {
unsafe {
let p = base_ptr.add(i);
core::ptr::write_volatile(p, pattern);
}
}
// read/verify pass
for i in 0..words {
let val;
unsafe {
let p = base_ptr.add(i) as *const u32;
val = core::ptr::read_volatile(p);
}
if val != pattern {
return Err(MemTestError::Memory {
addr: base_addr + i * 4,
expected: pattern,
found: val,
});
}
}
}
Ok(())
}
/// # Safety
///
/// This tests writes and reads on a memory block starting at the base address
/// with the size `words` times 4.
pub unsafe fn checkerboard_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
if words == 0 {
return Ok(());
}
if !base_addr.is_multiple_of(4) {
return Err(MemTestError::AddrNotAligned);
}
let base_ptr = base_addr as *mut u32;
let patterns = [0xAAAAAAAAu32, 0x55555555u32];
for &pattern in &patterns {
// Write pass
for i in 0..words {
let value = if i % 2 == 0 { pattern } else { !pattern };
unsafe {
core::ptr::write_volatile(base_ptr.add(i), value);
}
}
// Read/verify pass
for i in 0..words {
let expected = if i % 2 == 0 { pattern } else { !pattern };
let val = unsafe { core::ptr::read_volatile(base_ptr.add(i)) };
if val != expected {
return Err(MemTestError::Memory {
addr: base_addr + i * 4,
expected,
found: val,
});
}
}
}
Ok(())
}
}

View File

@@ -0,0 +1,64 @@
#[derive(Debug, thiserror::Error)]
#[error("unaligned address: {0}")]
pub struct UnalignedAddrError(usize);
/// Configures the bitstream using the PCAP interface in non-secure mode.
///
/// Blocking function which only returns when the bitstream configuration is complete.
pub fn configure_bitstream_non_secure(
init_pl: bool,
bitstream: &[u8],
) -> Result<(), UnalignedAddrError> {
if !(bitstream.as_ptr() as usize).is_multiple_of(64) {
return Err(UnalignedAddrError(bitstream.as_ptr() as usize));
}
if bitstream.is_empty() {
return Ok(());
}
let mut devcfg = unsafe { zynq7000::devcfg::DevCfg::new_mmio_fixed() };
devcfg.modify_control(|mut val| {
val.set_config_access_select(zynq7000::devcfg::PlConfigAccess::ConfigAccessPort);
val.set_access_port_select(zynq7000::devcfg::ConfigAccessPortSelect::Pcap);
val
});
devcfg.write_interrupt_status(zynq7000::devcfg::Interrupt::new_with_raw_value(0xFFFF_FFFF));
if init_pl {
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(true);
val
});
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(false);
val
});
while devcfg.read_status().pcfg_init() {}
devcfg.modify_control(|mut val| {
val.set_prog_b_bit(true);
val
});
devcfg.write_interrupt_status(
zynq7000::devcfg::Interrupt::ZERO.with_pl_programming_done(true),
);
}
while !devcfg.read_status().pcfg_init() {}
if !init_pl {
while devcfg.read_status().dma_command_queue_full() {}
}
devcfg.modify_misc_control(|mut val| {
val.set_loopback(false);
val
});
devcfg.modify_control(|mut val| {
val.set_pcap_rate_enable(false);
val
});
devcfg.write_dma_source_addr(bitstream.as_ptr() as u32);
devcfg.write_dma_dest_addr(0xFFFF_FFFF);
devcfg.write_dma_source_len(bitstream.len() as u32);
devcfg.write_dma_dest_len(bitstream.len() as u32);
while !devcfg.read_interrupt_status().dma_done() {}
// TODO: Check for errors.
while !devcfg.read_interrupt_status().pl_programming_done() {}
Ok(())
}

View File

@@ -1,10 +1,10 @@
use arbitrary_int::{Number, u6};
use arbitrary_int::{prelude::*, u6};
use zynq7000::{
eth::{InterruptControl, NetworkControl, RxStatus, TxStatus},
slcr::reset::EthernetReset,
};
use crate::{clocks::IoClocks, enable_amba_periph_clk, slcr::Slcr, time::Hertz};
use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::Hertz};
use super::{EthernetId, PsEthernet as _};
@@ -239,7 +239,7 @@ impl EthernetLowLevel {
EthernetId::Eth0 => crate::PeriphSelect::Gem0,
EthernetId::Eth1 => crate::PeriphSelect::Gem1,
};
enable_amba_periph_clk(periph_sel);
enable_amba_peripheral_clock(periph_sel);
}
/// Completely configures the clock based on the provided [ClockConfig].

View File

@@ -105,132 +105,132 @@ impl PsEthernet for MmioEthernet<'static> {
}
}
pub trait TxClk: MioPin {
pub trait TxClockPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxCtrl: MioPin {
pub trait TxControlPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData0: MioPin {
pub trait TxData0Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData1: MioPin {
pub trait TxData1Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData2: MioPin {
pub trait TxData2Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait TxData3: MioPin {
pub trait TxData3Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxClk: MioPin {
pub trait RxClockPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxCtrl: MioPin {
pub trait RxControlPin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData0: MioPin {
pub trait RxData0Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData1: MioPin {
pub trait RxData1Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData2: MioPin {
pub trait RxData2Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait RxData3: MioPin {
pub trait RxData3Pin: MioPin {
const ETH_ID: EthernetId;
}
pub trait MdClk: MioPin {}
pub trait MdIo: MioPin {}
pub trait MdClockPin: MioPin {}
pub trait MdIoPin: MioPin {}
impl MdClk for Pin<Mio52> {}
impl MdIo for Pin<Mio53> {}
impl MdClockPin for Pin<Mio52> {}
impl MdIoPin for Pin<Mio53> {}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxClk for Pin<Mio16> {
impl TxClockPin for Pin<Mio16> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxCtrl for Pin<Mio21> {
impl TxControlPin for Pin<Mio21> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxData0 for Pin<Mio17> {
impl TxData0Pin for Pin<Mio17> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxData1 for Pin<Mio18> {
impl TxData1Pin for Pin<Mio18> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxData2 for Pin<Mio19> {
impl TxData2Pin for Pin<Mio19> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl TxData3 for Pin<Mio20> {
impl TxData3Pin for Pin<Mio20> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxClk for Pin<Mio22> {
impl RxClockPin for Pin<Mio22> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxCtrl for Pin<Mio27> {
impl RxControlPin for Pin<Mio27> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxData0 for Pin<Mio23> {
impl RxData0Pin for Pin<Mio23> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxData1 for Pin<Mio24> {
impl RxData1Pin for Pin<Mio24> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxData2 for Pin<Mio25> {
impl RxData2Pin for Pin<Mio25> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl RxData3 for Pin<Mio26> {
impl RxData3Pin for Pin<Mio26> {
const ETH_ID: EthernetId = EthernetId::Eth0;
}
impl TxClk for Pin<Mio28> {
impl TxClockPin for Pin<Mio28> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl TxCtrl for Pin<Mio33> {
impl TxControlPin for Pin<Mio33> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl TxData0 for Pin<Mio29> {
impl TxData0Pin for Pin<Mio29> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl TxData1 for Pin<Mio30> {
impl TxData1Pin for Pin<Mio30> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl TxData2 for Pin<Mio31> {
impl TxData2Pin for Pin<Mio31> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl TxData3 for Pin<Mio32> {
impl TxData3Pin for Pin<Mio32> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxClk for Pin<Mio34> {
impl RxClockPin for Pin<Mio34> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxCtrl for Pin<Mio39> {
impl RxControlPin for Pin<Mio39> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxData0 for Pin<Mio35> {
impl RxData0Pin for Pin<Mio35> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxData1 for Pin<Mio36> {
impl RxData1Pin for Pin<Mio36> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxData2 for Pin<Mio37> {
impl RxData2Pin for Pin<Mio37> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
impl RxData3 for Pin<Mio38> {
impl RxData3Pin for Pin<Mio38> {
const ETH_ID: EthernetId = EthernetId::Eth1;
}
@@ -328,30 +328,30 @@ impl Ethernet {
/// configuring all the necessary MIO pins.
#[allow(clippy::too_many_arguments)]
pub fn new_with_mio<
TxClkPin: TxClk,
TxCtrlPin: TxCtrl,
TxData0Pin: TxData0,
TxData1Pin: TxData1,
TxData2Pin: TxData2,
TxData3Pin: TxData3,
RxClkPin: RxClk,
RxCtrlPin: RxCtrl,
RxData0Pin: RxData0,
RxData1Pin: RxData1,
RxData2Pin: RxData2,
RxData3Pin: RxData3,
MdClkPin: MdClk,
MdIoPin: MdIo,
TxClock: TxClockPin,
TxControl: TxControlPin,
TxData0: TxData0Pin,
TxData1: TxData1Pin,
TxData2: TxData2Pin,
TxData3: TxData3Pin,
RxClock: RxClockPin,
RxControl: RxControlPin,
RxData0: RxData0Pin,
RxData1: RxData1Pin,
RxData2: RxData2Pin,
RxData3: RxData3Pin,
MdClock: MdClockPin,
MdIo: MdIoPin,
>(
mut ll: ll::EthernetLowLevel,
config: EthernetConfig,
tx_clk: TxClkPin,
tx_ctrl: TxCtrlPin,
tx_data: (TxData0Pin, TxData1Pin, TxData2Pin, TxData3Pin),
rx_clk: RxClkPin,
rx_ctrl: RxCtrlPin,
rx_data: (RxData0Pin, RxData1Pin, RxData2Pin, RxData3Pin),
md_pins: Option<(MdClkPin, MdIoPin)>,
tx_clk: TxClock,
tx_ctrl: TxControl,
tx_data: (TxData0, TxData1, TxData2, TxData3),
rx_clk: RxClock,
rx_ctrl: RxControl,
rx_data: (RxData0, RxData1, RxData2, RxData3),
md_pins: Option<(MdClock, MdIo)>,
) -> Self {
Self::common_init(&mut ll, config.mac_address);
let tx_mio_config = zynq7000::slcr::mio::Config::builder()

View File

@@ -4,7 +4,7 @@ use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::AtomicBool};
use crate::{cache::clean_and_invalidate_data_cache_range, eth::AlignedBuffer};
pub use super::shared::Ownership;
use arbitrary_int::{Number, u2, u3, u13, u30};
use arbitrary_int::{prelude::*, u2, u3, u13, u30};
use vcell::VolatileCell;
static RX_DESCR_TAKEN: AtomicBool = AtomicBool::new(false);

View File

@@ -6,7 +6,7 @@
//! # Examples
//!
//! - [GTC ticks](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/gtc-ticks.rs)
use arbitrary_int::Number;
use arbitrary_int::prelude::*;
use cortex_ar::interrupt;
use zynq7000::gic::{

Some files were not shown because too many files have changed in this diff Show More