finished QSPI flasher
Some checks failed
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
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
Some checks failed
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
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
This commit is contained in:
@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
|||||||
categories = ["embedded", "no-std", "hardware-support"]
|
categories = ["embedded", "no-std", "hardware-support"]
|
||||||
|
|
||||||
[dependencies]
|
[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-rt = { path = "../../zynq7000-rt" }
|
||||||
zynq7000 = { path = "../../zynq7000" }
|
zynq7000 = { path = "../../zynq7000" }
|
||||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||||
@@ -20,7 +20,7 @@ dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev =
|
|||||||
static_cell = "2"
|
static_cell = "2"
|
||||||
critical-section = "1"
|
critical-section = "1"
|
||||||
heapless = "0.9"
|
heapless = "0.9"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use embassy_time::{Duration, Ticker};
|
|||||||
use embedded_hal::digital::StatefulOutputPin;
|
use embedded_hal::digital::StatefulOutputPin;
|
||||||
use embedded_io::Write;
|
use embedded_io::Write;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use zynq7000_hal::{clocks, gic, gpio, gtc, time::Hertz, uart, BootMode, InteruptConfig};
|
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
|
||||||
|
|
||||||
use zynq7000_rt as _;
|
use zynq7000_rt as _;
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[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-rt = { path = "../../zynq7000-rt" }
|
||||||
zynq7000 = { path = "../../zynq7000" }
|
zynq7000 = { path = "../../zynq7000" }
|
||||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
|||||||
categories = ["embedded", "no-std", "hardware-support"]
|
categories = ["embedded", "no-std", "hardware-support"]
|
||||||
|
|
||||||
[dependencies]
|
[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-rt = { path = "../../zynq7000-rt" }
|
||||||
zynq7000 = { path = "../../zynq7000" }
|
zynq7000 = { path = "../../zynq7000" }
|
||||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||||
@@ -19,7 +19,7 @@ zynq7000-embassy = { path = "../../zynq7000-embassy" }
|
|||||||
zedboard-bsp = { path = "../../zedboard-bsp" }
|
zedboard-bsp = { path = "../../zedboard-bsp" }
|
||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
|
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
bitbybit = "1.4"
|
bitbybit = "1.4"
|
||||||
arbitrary-int = "2"
|
arbitrary-int = "2"
|
||||||
embedded-io-async = "0.6"
|
embedded-io-async = "0.6"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use embedded_io::Write;
|
|||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use zedboard::PS_CLOCK_FREQUENCY;
|
use zedboard::PS_CLOCK_FREQUENCY;
|
||||||
use zedboard_bsp::qspi_spansion;
|
use zedboard_bsp::qspi_spansion;
|
||||||
use zynq7000_hal::{clocks, gic, gpio, gtc, prelude::*, qspi, uart, BootMode};
|
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart};
|
||||||
|
|
||||||
use zynq7000_rt as _;
|
use zynq7000_rt as _;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin;
|
|||||||
use embedded_io::Write;
|
use embedded_io::Write;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use zedboard::PS_CLOCK_FREQUENCY;
|
use zedboard::PS_CLOCK_FREQUENCY;
|
||||||
use zynq7000_hal::{clocks, gic, gpio, gtc, uart, BootMode};
|
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
|
||||||
|
|
||||||
use zynq7000_rt as _;
|
use zynq7000_rt as _;
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ pub enum SectorArchictecture {
|
|||||||
Hybrid = 0x01,
|
Hybrid = 0x01,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const PAGE_SIZE: usize = 256;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BaseDeviceId {
|
pub struct BaseDeviceId {
|
||||||
manufacturer_id: u8,
|
manufacturer_id: u8,
|
||||||
@@ -221,6 +223,8 @@ pub enum ProgramPageError {
|
|||||||
ProgrammingErrorBitSet,
|
ProgrammingErrorBitSet,
|
||||||
#[error("address error: {0}")]
|
#[error("address error: {0}")]
|
||||||
Addr(#[from] AddrError),
|
Addr(#[from] AddrError),
|
||||||
|
#[error("data is larger than page size {PAGE_SIZE}")]
|
||||||
|
DataLargerThanPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
|
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
|
||||||
@@ -428,14 +432,17 @@ impl QspiSpansionS25Fl256SIoMode {
|
|||||||
/// This function also takes care of enabling writes before programming the page.
|
/// This function also takes care of enabling writes before programming the page.
|
||||||
/// This function will block until the operation has completed.
|
/// This function will block until the operation has completed.
|
||||||
///
|
///
|
||||||
/// TODO: Allow smaller write size
|
/// The data length max not exceed the page size [PAGE_SIZE].
|
||||||
pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> {
|
pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
|
||||||
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
||||||
return Err(AddrError::OutOfRange.into());
|
return Err(AddrError::OutOfRange.into());
|
||||||
}
|
}
|
||||||
if !addr.is_multiple_of(0x100) {
|
if !addr.is_multiple_of(0x100) {
|
||||||
return Err(AddrError::Alignment.into());
|
return Err(AddrError::Alignment.into());
|
||||||
}
|
}
|
||||||
|
if data.len() > PAGE_SIZE {
|
||||||
|
return Err(ProgramPageError::DataLargerThanPage);
|
||||||
|
}
|
||||||
self.write_enable();
|
self.write_enable();
|
||||||
let qspi = self.0.get_mut();
|
let qspi = self.0.get_mut();
|
||||||
let mut transfer = qspi.transfer_guard();
|
let mut transfer = qspi.transfer_guard();
|
||||||
@@ -448,8 +455,9 @@ impl QspiSpansionS25Fl256SIoMode {
|
|||||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||||
let mut read_index: u32 = 0;
|
let mut read_index: u32 = 0;
|
||||||
let mut current_byte_index = 0;
|
let mut current_byte_index = 0;
|
||||||
|
let fifo_writes = data.len().div_ceil(4);
|
||||||
// Fill the FIFO until it is full.
|
// Fill the FIFO until it is full.
|
||||||
for _ in 0..FIFO_DEPTH - 1 {
|
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||||
data[current_byte_index..current_byte_index + 4]
|
data[current_byte_index..current_byte_index + 4]
|
||||||
.try_into()
|
.try_into()
|
||||||
@@ -470,25 +478,38 @@ impl QspiSpansionS25Fl256SIoMode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Immediately fill the FIFO again with the remaining 8 bytes.
|
while current_byte_index < data.len() {
|
||||||
wait_for_tx_slot(&mut transfer);
|
// Immediately fill the FIFO again with the remaining 8 bytes.
|
||||||
|
wait_for_tx_slot(&mut transfer);
|
||||||
|
|
||||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
let word = match core::cmp::min(4, data.len() - current_byte_index) {
|
||||||
data[current_byte_index..current_byte_index + 4]
|
1 => {
|
||||||
.try_into()
|
let mut bytes = [0; 4];
|
||||||
.unwrap(),
|
bytes[0] = data[current_byte_index];
|
||||||
));
|
u32::from_ne_bytes(bytes)
|
||||||
current_byte_index += 4;
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
wait_for_tx_slot(&mut transfer);
|
while read_index < data.len() as u32 {
|
||||||
|
|
||||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
|
||||||
data[current_byte_index..current_byte_index + 4]
|
|
||||||
.try_into()
|
|
||||||
.unwrap(),
|
|
||||||
));
|
|
||||||
|
|
||||||
while read_index < 256 {
|
|
||||||
if transfer.read_status().rx_above_threshold() {
|
if transfer.read_status().rx_above_threshold() {
|
||||||
transfer.read_rx_data();
|
transfer.read_rx_data();
|
||||||
read_index = read_index.wrapping_add(4);
|
read_index = read_index.wrapping_add(4);
|
||||||
@@ -528,11 +549,7 @@ impl QspiSpansionS25Fl256SIoMode {
|
|||||||
if dummy_byte {
|
if dummy_byte {
|
||||||
bytes_to_write += 1;
|
bytes_to_write += 1;
|
||||||
}
|
}
|
||||||
let fifo_writes = if bytes_to_write.is_multiple_of(4) {
|
let fifo_writes = bytes_to_write.div_ceil(4);
|
||||||
bytes_to_write / 4
|
|
||||||
} else {
|
|
||||||
(bytes_to_write / 4) + 1
|
|
||||||
};
|
|
||||||
// Fill the FIFO until it is full or all 0 bytes have been written.
|
// 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) {
|
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||||
transfer.write_word_txd_00(0);
|
transfer.write_word_txd_00(0);
|
||||||
|
|||||||
@@ -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
|
You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and
|
||||||
`Load Project`.
|
`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.
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[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-rt = { path = "../zynq7000-rt" }
|
||||||
zynq7000 = { path = "../zynq7000" }
|
zynq7000 = { path = "../zynq7000" }
|
||||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||||
zynq-boot-image = { path = "../zynq-boot-image" }
|
zynq-boot-image = { path = "../zynq-boot-image" }
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ use zynq7000_hal::{
|
|||||||
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
||||||
},
|
},
|
||||||
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
||||||
gic, gpio, l2_cache,
|
devcfg, gic, gpio, l2_cache,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
qspi,
|
qspi::{self, QSPI_START_ADDRESS},
|
||||||
time::Hertz,
|
time::Hertz,
|
||||||
uart::{ClockConfig, Config, Uart},
|
uart::{ClockConfig, Config, Uart},
|
||||||
};
|
};
|
||||||
@@ -225,7 +225,19 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! {
|
|||||||
match dest_dev {
|
match dest_dev {
|
||||||
DestinationDevice::Pl => {
|
DestinationDevice::Pl => {
|
||||||
info!("Loading image '{name}' to PL (FPGA)..");
|
info!("Loading image '{name}' to PL (FPGA)..");
|
||||||
// TODO: Load the bitstream.
|
// 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(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
|
||||||
|
.expect("unexpected unaligned address");
|
||||||
}
|
}
|
||||||
DestinationDevice::Ps => {
|
DestinationDevice::Ps => {
|
||||||
// TODO: Load the binary into DDR. Jump at lowest load address after all
|
// TODO: Load the binary into DDR. Jump at lowest load address after all
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "main" }
|
cortex-ar = { version = "0.3" }
|
||||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||||
zynq7000 = { path = "../zynq7000" }
|
zynq7000 = { path = "../zynq7000" }
|
||||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||||
|
zynq-boot-image = { path = "../zynq-boot-image" }
|
||||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|||||||
31
zedboard-qspi-flasher/build.rs
Normal file
31
zedboard-qspi-flasher/build.rs
Normal 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");
|
||||||
|
}
|
||||||
22
zedboard-qspi-flasher/memory.x
Normal file
22
zedboard-qspi-flasher/memory.x
Normal 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
|
||||||
|
}
|
||||||
151
zedboard-qspi-flasher/qspi-flasher.tcl
Normal file
151
zedboard-qspi-flasher/qspi-flasher.tcl
Normal 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 app; 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 as BOOTBIN environment"
|
||||||
|
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"
|
||||||
@@ -7,10 +7,11 @@ use core::panic::PanicInfo;
|
|||||||
use cortex_ar::asm::nop;
|
use cortex_ar::asm::nop;
|
||||||
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
|
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
|
||||||
use embedded_io::Write as _;
|
use embedded_io::Write as _;
|
||||||
use log::info;
|
use log::{error, info};
|
||||||
use zedboard_bsp::qspi_spansion;
|
use zedboard_bsp::qspi_spansion;
|
||||||
|
use zynq_boot_image::BootHeader;
|
||||||
use zynq7000_hal::{
|
use zynq7000_hal::{
|
||||||
clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, BootMode, LevelShifterConfig,
|
BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart,
|
||||||
};
|
};
|
||||||
use zynq7000_rt as _;
|
use zynq7000_rt as _;
|
||||||
|
|
||||||
@@ -19,6 +20,15 @@ use zynq7000_rt as _;
|
|||||||
// Not required for the PAC mode, is required for clean delays in HAL mode.
|
// 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);
|
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)]
|
#[allow(dead_code)]
|
||||||
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
||||||
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
||||||
@@ -75,6 +85,7 @@ pub fn main() -> ! {
|
|||||||
let boot_mode = BootMode::new_from_regs();
|
let boot_mode = BootMode::new_from_regs();
|
||||||
info!("Boot mode: {:?}", boot_mode);
|
info!("Boot mode: {:?}", boot_mode);
|
||||||
|
|
||||||
|
/*
|
||||||
let qspi_clock_config =
|
let qspi_clock_config =
|
||||||
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
|
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
|
||||||
.expect("QSPI clock calculation failed");
|
.expect("QSPI clock calculation failed");
|
||||||
@@ -96,7 +107,78 @@ pub fn main() -> ! {
|
|||||||
|
|
||||||
let qspi_io_mode = qspi.into_io_mode(false);
|
let qspi_io_mode = qspi.into_io_mode(false);
|
||||||
|
|
||||||
let _spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
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];
|
||||||
|
while current_addr < boot_bin_size {
|
||||||
|
if current_addr % 0x10000 == 0 {
|
||||||
|
info!("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];
|
||||||
|
info!("Programming address {:#x}", current_addr);
|
||||||
|
match spansion_qspi.program_page(current_addr as u32, write_slice) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
info!("flashing done");
|
||||||
|
*/
|
||||||
|
|
||||||
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "2", default-features = false }
|
||||||
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" }
|
cortex-ar = { version = "0.3" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tools = []
|
tools = []
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"]
|
|||||||
categories = ["embedded", "no-std", "hardware-support"]
|
categories = ["embedded", "no-std", "hardware-support"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" }
|
cortex-ar = { version = "0.3" }
|
||||||
zynq7000 = { path = "../zynq7000" }
|
zynq7000 = { path = "../zynq7000" }
|
||||||
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
|
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ thiserror = { version = "2", default-features = false }
|
|||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
ringbuf = { version = "0.4.8", default-features = false }
|
ringbuf = { version = "0.4.8", default-features = false }
|
||||||
embedded-hal-nb = "1"
|
embedded-hal-nb = "1"
|
||||||
embedded-io = "0.6"
|
embedded-io = "0.7"
|
||||||
embedded-hal = "1"
|
embedded-hal = "1"
|
||||||
embedded-hal-async = "1"
|
embedded-hal-async = "1"
|
||||||
heapless = "0.9"
|
heapless = "0.9"
|
||||||
@@ -39,7 +39,7 @@ embassy-net-driver = "0.2"
|
|||||||
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||||
vcell = "0.1"
|
vcell = "0.1"
|
||||||
raw-slicee = "0.1"
|
raw-slicee = "0.1"
|
||||||
embedded-io-async = "0.6"
|
embedded-io-async = "0.7"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = ["thiserror/std", "alloc"]
|
std = ["thiserror/std", "alloc"]
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ extern crate alloc;
|
|||||||
|
|
||||||
use slcr::Slcr;
|
use slcr::Slcr;
|
||||||
use zynq7000::{
|
use zynq7000::{
|
||||||
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
|
||||||
SpiClockPhase, SpiClockPolarity,
|
SpiClockPhase, SpiClockPolarity,
|
||||||
|
slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|||||||
@@ -202,4 +202,9 @@ impl embedded_io_async::Write for TxAsync {
|
|||||||
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
Ok(self.write(buf).await)
|
Ok(self.write(buf).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This implementation does not do anything.
|
||||||
|
async fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ categories = ["embedded", "no-std", "hardware-support"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-a-rt = { version = "0.1", optional = true, features = ["vfp-dp"] }
|
cortex-a-rt = { version = "0.1", optional = true, features = ["vfp-dp"] }
|
||||||
cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar.git", branch = "bump-arbitrary-int" }
|
cortex-ar = { version = "0.3" }
|
||||||
arbitrary-int = "2"
|
arbitrary-int = "2"
|
||||||
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
|
zynq-mmu = { path = "../zynq-mmu", version = "0.1.0" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user