init commit

This commit is contained in:
Robin Müller 2025-02-19 11:00:04 +01:00
commit 1bbfc98a0a
103 changed files with 21431 additions and 0 deletions

33
.cargo/def-config.toml Normal file
View File

@ -0,0 +1,33 @@
[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.
# Absolute path to the Vitis install directory.
# AMD_TOOLS = "/tools/Xilinx/Vitis/2024.1"
# Absolute path to the PS7 initialization TCL script.
# TCL_INIT_SCRIPT = "/home/$user/$project/$sdt_dir/ps7_init.tcl"
# Absolute path to the Zynq bitstream file, which will be flashed to the target before
# running the application. You only need to do this once for unchanged bitstream as long as you
# do not reset the whole board.
# ZYNQ_BITSTREAM = "/home/$user/$project/$sdt_dir/bitstream.bit"

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/target
/app.map
/xsct-output.log
/.vscode
/Cargo.lock
/.cargo/config.toml

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[workspace]
resolver = "3"
members = [
"zynq7000-rt",
"zynq7000",
"zynq7000-hal",
"zynq7000-embassy",
"examples/simple",
"examples/embassy",
"examples/zedboard",
]
exclude = ["experiments"]

201
LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
LICENSE-MIT Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Robin A. Mueller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
NOTICE Normal file
View File

@ -0,0 +1 @@
This software contains code developed at the University of Stuttgart.

164
README.md Normal file
View File

@ -0,0 +1,164 @@
Zynq 7000 Bare-Metal Rust Support
=========
This crate collection provides support to write bare-metal Rust applications for the AMD Zynq 7000
family of SoCs.
# List of crates
This workspace contains the following released crates:
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/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)
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.
It also contains the following helper crates:
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/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.
# Using the `.cargo/config.toml` file
Use the following command to have a starting `config.toml` file
```sh
cp .cargo/def-config.toml .cargo/config.toml
```
You then can adapt the `config.toml` to your needs. For example, you can configure runners
to conveniently flash with `cargo run`.
# Using the sample VS Code files
Use the following command to have a starting configuration for VS Code:
```sh
cp -rT vscode .vscode
```
You can then adapt the files in `.vscode` to your needs.
# Building the blinky example
Building an application requires `nightly` to build the `core` and `alloc` library
because the `thumbv7a-none-eabihf` Rust target only has Tier 3 support
If you have not installed it yet, you can do so with
```sh
rustup toolchain install nightly
```
Assuming you have the following segments inside your `.cargo/config.toml`
```toml
[target.armv7a-none-eabihf]
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"
```
You can build the blinky example app using
```sh
cargo build --example simple
```
## Flashing, running and debugging the software
This repository was only tested with the [Zedboard](https://digilent.com/reference/programmable-logic/zedboard/start)
but should be easily adaptable to other Zynq7000 based platforms and boards.
If you want to test and use this crate on a Zedboard, make sure that the board jumpers on the
Zedboard are configured for JTAG boot.
### Pre-Requisites
- [`xsct`](https://docs.amd.com/r/en-US/ug1165-zynq-embedded-design-tutorial/XSCT-Xilinx-Software-Command-Tool)
tool installation. You have to install the [Vitis tool](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vitis.html)
to get access to this tool. You can minimize the download size by de-selecting all SoC families
that you do not require. Vitis also includes a Vivado installation which is required to do
anything with the FPGA.
- [`hw_server`](https://docs.amd.com/r/en-US/ug908-vivado-programming-debugging/Connecting-to-a-Hardware-Target-Using-hw_server)
installation which is generally included with Vitis/Vivado. This tool starts a GDB server
which will be used to connect to and debug the target board. It also allows initializing the
processing system and uploading the FPGA design via JTAG.
- A valid `ps7_init.tcl` script to configure the processing system. This is necessary to even
be able flashing application into the DDR via JTAG. There are multiple ways to generate this
startup script, but the recommended way here is to the the `sdtgen` tool included in `xsct` which
also generates a bitstream for the FPGA configuration. You can find a sample `ps7_init.tcl`
script inside the `scripts` folder. However, it is strongly recommended to get familiar with
Vivado and generate the SDT folder yourself.
- `gdb-multiarch` installation to debug applications.
- `python3` installation to use the provided tooling.
### Programming and Debug Flow
1. Start the `hw_server` application first. This is required for other tooling provided by this
repository as well.
2. The provided `scripts/zynq7000-init.py` script can be used to initialize the processing system
with the `ps7_init.tcl` script, program the bitstream, and load an ELF file to the DDR.
You can run `scripts/zynq7000-init.py` to get some help and additional information.
Here is an example command to initialize the processing system without loading a bitstream
using the provided initialization script (adapt `AMD_TOOLS` to your system):
```sh
export AMD_TOOLS="/tools/Xilinx/Vitis/2024.1"
./scripts/zynq7000-init.py --itcl ./scripts/ps7_init.tcl
```
3. Assuming you have managed to build the blinky example, you can flash the example now
using GDB:
```sh
gdb-multiarch -q -x gdb.gdb target/armv7a-none-eabihf/debug/blinky
```
You can use the `-tui` argument to also have a terminal UI.
This repository provides a `scripts/runner.sh` which performs all the steps specified above.
The `.cargo/def-config.toml` script contains the runner and some template environmental
variables that need to be set for this to work. The command above also loaded the app, but
this task can be performed by the `zynq7000-init.py` wrapper as well.
# Using VS Code
The provided VS Code configuration files can be used to perform the CLI steps specified above
in a GUI completely and to also have a graphical debugging interface. You can use this
as a starting point for your own application. You need to adapt the `options.env` variables
in `.vscode/tasks.json` for this to work.
# Embedded Rust
If you have not done this yet, it is recommended to read some of the excellent resources available
to learn Rust:
- [Rust Embedded Book](https://docs.rust-embedded.org/book/)
- [Rust Discovery Book](https://docs.rust-embedded.org/discovery/)

View File

@ -0,0 +1,32 @@
[package]
name = "embassy-examples"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Embassy examples for the Zynq7000 SoC"
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"
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { path = "/home/rmueller/Rust/cortex-ar/cortex-ar", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
zynq7000-embassy = { path = "../../zynq7000-embassy" }
static_cell = "2"
critical-section = "1"
heapless = "0.8"
embedded-io = "0.6"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"
embassy-executor = { path = "/home/rmueller/Rust/embassy/embassy-executor", features = [
"arch-cortex-ar",
"executor-thread",
"task-arena-size-65536"
]}
embassy-time = { path = "/home/rmueller/Rust/embassy/embassy-time", version = "0.4" }

View File

@ -0,0 +1,151 @@
//! Example which uses the UART1 to send log messages.
#![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 zynq7000::PsPeripherals;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{Mio7, MioPin, MioPins, Output, PinState},
gtc::Gtc,
time::Hertz,
uart::{ClkConfigRaw, TxAsync, Uart, UartConfig, on_interrupt_tx},
};
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();
}
#[unsafe(export_name = "main")]
#[embassy_executor::main]
async fn main(spawner: Spawner) -> ! {
let dp = PsPeripherals::take().unwrap();
// 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();
}
// Set up global timer counter and embassy time driver.
let gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
let mio_pins = MioPins::new(dp.gpio);
// Set up the UART, we are logging with it.
let uart_clk_config = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Logging example --\n\r")
.unwrap();
uart.flush().unwrap();
let (tx, _rx) = uart.split();
let mut logger = TxAsync::new(tx);
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let led = mio_pins.mio7.into_output(PinState::Low);
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 {
let next_frame_len = frame_queue.receive().await;
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
logger.write(&log_buf[0..next_frame_len]).await;
}
}
#[embassy_executor::task]
async fn led_task(mut mio_led: MioPin<Mio7, Output>) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
loop {
mio_led.toggle().unwrap();
info!("Toggling LED");
ticker.next().await;
}
}
#[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) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
}
}
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 {}
}

View File

@ -0,0 +1,159 @@
//! PWM example which uses a PWM pin routed through EMIO.
//!
//! This example puts the PWM output on the EMIO channel of TTC0 channel 0.
//!
//! On the Zedboard, the PWM waveform output will be on the W12 pin of PMOD JB1. The Zedboard
//! reference FPGA design must be flashed onto the Zedboard for this to work.
#![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, pwm::SetDutyCycle};
use embedded_io::Write;
use fugit::RateExtU32;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{MioPins, PinState},
gtc::Gtc,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000::PsPeripherals;
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 dp = PsPeripherals::take().unwrap();
// 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 = MioPins::new(dp.gpio);
// Set up global timer counter and embassy time driver.
let gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
// Unwrap is okay, the address is definitely valid.
let ttc_0 = zynq7000_hal::ttc::Ttc::new(dp.ttc_0).unwrap();
let mut pwm =
zynq7000_hal::ttc::Pwm::new_with_cpu_clk(ttc_0.ch0, clocks.arm_clocks(), 1000.Hz())
.unwrap();
pwm.set_duty_cycle_percent(50).unwrap();
// Set up the UART, we are logging with it.
let uart_clk_config = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
.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(1000));
let mut led = mio_pins.mio7.into_output(PinState::Low);
let mut current_duty = 0;
loop {
led.toggle().unwrap();
pwm.set_duty_cycle_percent(current_duty).unwrap();
info!("Setting duty cycle to {}%", current_duty);
current_duty += 5;
if current_duty > 100 {
current_duty = 0;
}
ticker.next().await;
}
}
#[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 {}
}

View File

@ -0,0 +1,138 @@
#![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 zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{MioPins, PinState},
gtc::Gtc,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000::PsPeripherals;
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();
}
// TODO: The export name is ignored..
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
let dp = PsPeripherals::take().unwrap();
// 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 = MioPins::new(dp.gpio);
// Set up global timer counter and embassy time driver.
let gtc = Gtc::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 = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
.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(1000));
let mut led = mio_pins.mio7.into_output(PinState::Low);
loop {
info!("Hello, world!");
led.toggle().unwrap();
ticker.next().await;
}
}
#[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 {}
}

View File

@ -0,0 +1,19 @@
[package]
name = "blinky"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Simple examples for the Zynq7000 SoC"
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 = { git = "https://github.com/us-irs/cortex-ar.git", branch = "cortex-a-addition", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
embedded-io = "0.6"
embedded-hal = "1"
fugit = "0.3"
log = "0.4"

View File

@ -0,0 +1,138 @@
//! Example which uses the global timer for a simple tick counter.
#![no_std]
#![no_main]
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
use cortex_ar::asm::nop;
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{MioPins, PinState},
gtc::Gtc,
prelude::*,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
/// 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 dp = zynq7000::PsPeripherals::take().unwrap();
// 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();
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
let ticks = gtc.frequency_to_ticks(1000.Hz());
gtc.set_auto_increment_value(ticks);
gtc.set_comparator(gtc.read_timer() + ticks as u64);
gtc.enable_auto_increment();
gtc.enable_interrupt();
gtc.enable();
// This structure holds all MIO pins.
let mio_pins = MioPins::new(dp.gpio);
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 GTC Ticks example --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart,
log::LevelFilter::Trace,
false,
)
};
let mut led = mio_pins.mio7.into_output(PinState::Low);
loop {
info!(
"MS_TICKS: {}",
MS_TICKS.load(core::sync::atomic::Ordering::Relaxed)
);
led.toggle().unwrap();
for _ in 0..5_000_000 {
nop();
}
}
}
#[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 {
MS_TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
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 {}
}

View File

@ -0,0 +1,141 @@
//! Example which uses the UART1 to send log messages.
#![no_std]
#![no_main]
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
use cortex_ar::asm::nop;
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{MioPins, PinState},
gtc::Gtc,
prelude::*,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
/// 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 dp = zynq7000::PsPeripherals::take().unwrap();
// 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();
// Enable interrupt exception.
unsafe { gic.enable_interrupts() };
// Set up the UART, we are logging with it.
let uart_clk_config = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let mut gtc = Gtc::new(dp.gtc, clocks.arm_clocks());
let ticks = gtc.frequency_to_ticks(1000.Hz());
gtc.set_auto_increment_value(ticks);
gtc.set_comparator(gtc.read_timer() + ticks as u64);
gtc.enable_auto_increment();
gtc.enable_interrupt();
gtc.enable();
let mio_pins = MioPins::new(dp.gpio);
let uart_tx = mio_pins.mio48.into_uart();
let uart_rx = mio_pins.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Logging example --\n\r")
.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 led = mio_pins.mio7.into_output(PinState::Low);
loop {
let gtc = gtc.read_timer();
info!("Hello, world!");
info!("GTC ticks: {}", gtc);
led.toggle().unwrap();
for _ in 0..5_000_000 {
nop();
}
}
}
#[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 {
// TODO: Call embassy on interrupt handler here soon.
MS_TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}
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 {}
}

View File

@ -0,0 +1,90 @@
//! 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 embedded_hal::digital::StatefulOutputPin;
use zynq7000::PsPeripherals;
use zynq7000_hal::gpio::{MioPins, PinState};
use zynq7000_rt as _;
/// One user LED is MIO7
const ZEDBOARD_LED_MASK: u32 = 1 << 7;
#[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) -> ! {
if cpu_id != 0 {
panic!("unexpected CPU ID {}", cpu_id);
}
main();
}
#[unsafe(export_name = "main")]
pub fn main() -> ! {
match LIB {
Lib::Pac => {
let mut gpio = unsafe { zynq7000::gpio::Gpio::new_mmio_fixed() };
gpio.bank_0().modify_dirm(|v| v | ZEDBOARD_LED_MASK);
gpio.bank_0().modify_out_en(|v| v | ZEDBOARD_LED_MASK);
loop {
gpio.modify_out_0(|v| v ^ ZEDBOARD_LED_MASK);
for _ in 0..5_000_000 {
nop();
}
}
}
Lib::Hal => {
let dp = PsPeripherals::take().unwrap();
let mio_pins = MioPins::new(dp.gpio);
let mut led = mio_pins.mio7.into_output(PinState::High);
loop {
led.toggle().unwrap();
for _ in 0..5_000_000 {
nop();
}
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _irq_handler() {}
#[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) -> ! {
loop {
nop();
}
}

View File

@ -0,0 +1,38 @@
[package]
name = "zedboard"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Embassy examples for the Zynq7000 SoC"
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"
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { git = "https://github.com/us-irs/cortex-ar.git", branch = "cortex-a-addition", features = ["critical-section-single-core"] }
zynq7000-rt = { path = "../../zynq7000-rt" }
zynq7000 = { path = "../../zynq7000" }
zynq7000-hal = { path = "../../zynq7000-hal" }
zynq7000-embassy = { path = "../../zynq7000-embassy" }
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
embedded-io = "0.6"
arbitrary-int = "1.3"
embedded-io-async = "0.6"
critical-section = "1"
static_cell = "2"
embedded-alloc = "0.6"
embedded-hal = "1"
embedded-hal-async = "1"
fugit = "0.3"
log = "0.4"
embassy-executor = { path = "/home/rmueller/Rust/embassy/embassy-executor", features = [
"arch-cortex-ar",
"executor-thread"
]}
embassy-time = { path = "/home/rmueller/Rust/embassy/embassy-time", version = "0.4" }
heapless = "0.8"
axi-uartlite = { path = "/home/rmueller/Rust/axi-uartlite-rs" }
axi-uart16550 = { path = "/home/rmueller/Rust/axi-uart16550-rs" }

View File

@ -0,0 +1,202 @@
//! PS I2C example using a L3GD20H sensor.
//!
//! External HW connections:
//!
//! - SCK pin to JE4 (MIO 12)
//! - SDA pin to JE1 (MIO 13)
//! - SDO / SA0 pin to JE3 (MIO 11) to select I2C address.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs;
use embedded_io::Write;
use l3gd20::i2c::I2cAddr;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{EmioPin, GpioPins, PinState},
gtc::Gtc,
i2c,
time::Hertz,
uart,
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
const I2C_ADDR_SEL: I2cAddr = I2cAddr::Sa0Low;
/// 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) -> ! {
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
let dp = PsPeripherals::take().unwrap();
// 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 = Gtc::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 = uart::ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = gpio_pins.mio.mio48.into_uart();
let uart_rx = gpio_pins.mio.mio49.into_uart();
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Zedboard I2C L3GD20H example --\n\r")
.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 sck_pin = gpio_pins.mio.mio12.into_i2c();
let sda_pin = gpio_pins.mio.mio13.into_i2c();
let pin_sel = match I2C_ADDR_SEL {
I2cAddr::Sa0Low => PinState::Low,
I2cAddr::Sa0High => PinState::High,
};
let _sa0_pin = gpio_pins.mio.mio11.into_output(pin_sel);
// The CS pin must be pulled high.
let _cs_pin = gpio_pins.mio.mio10.into_output(PinState::High);
let clk_config = i2c::calculate_divisors(
clocks.arm_clocks().cpu_1x_clk(),
i2c::I2cSpeed::Normal100kHz,
)
.unwrap();
let i2c = i2c::I2c::new_with_mio(dp.i2c_1, clk_config, (sck_pin, sda_pin)).unwrap();
let mut l3gd20 = l3gd20::i2c::L3gd20::new(i2c, l3gd20::i2c::I2cAddr::Sa0Low).unwrap();
let who_am_i = l3gd20.who_am_i().unwrap();
info!("L3GD20 WHO_AM_I: 0x{:02X}", who_am_i);
let mut delay = Delay;
let mut ticker = Ticker::every(Duration::from_millis(400));
let mut mio_led = gpio_pins.mio.mio7.into_output(PinState::Low);
let mut emio_leds: [EmioPin; 8] = [
gpio_pins.emio.take(0).unwrap(),
gpio_pins.emio.take(1).unwrap(),
gpio_pins.emio.take(2).unwrap(),
gpio_pins.emio.take(3).unwrap(),
gpio_pins.emio.take(4).unwrap(),
gpio_pins.emio.take(5).unwrap(),
gpio_pins.emio.take(6).unwrap(),
gpio_pins.emio.take(7).unwrap(),
];
for (idx, led) in emio_leds.iter_mut().enumerate() {
if idx % 2 == 0 {
led.into_output(PinState::High);
} else {
led.into_output(PinState::Low);
}
}
// Power up time for the sensor to get good readings.
delay.delay_ms(50).await;
loop {
mio_led.toggle().unwrap();
let measurements = l3gd20.all().unwrap();
info!("L3GD20: {:?}", measurements);
info!("L3GD20 Temp: {:?}", measurements.temp_celcius());
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
}
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 {}
}

View File

@ -0,0 +1,293 @@
//! PS SPI example using a L3GD20H sensor.
//!
//! External HW connections:
//!
//! - SCK pin to JE4 (MIO 12)
//! - MOSI pin to JE2 (MIO 10)
//! - MISO pin to JE3 (MIO 11)
//! - SS pin to JE1 (MIO 13)
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_ar::asm::nop;
use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_hal_async::delay::DelayNs;
use embedded_io::Write;
use log::{error, info};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{DynMioPin, EmioPin, GpioPins, PinState},
gtc::Gtc,
spi::{self, SpiAsync, SpiId, SpiWithHwCs, SpiWithHwCsAsync, on_interrupt},
time::Hertz,
uart::{self, TxAsync, on_interrupt_tx},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig, spi::DelayControl};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
const DEBUG_SPI_CLK_CONFIG: bool = false;
const BLOCKING: bool = 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();
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
let dp = PsPeripherals::take().unwrap();
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
let mut clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
// SPI reference clock must be larger than the CPU 1x clock.
let spi_ref_clk_div = spi::calculate_largest_allowed_spi_ref_clk_divisor(&clocks)
.unwrap()
.value()
- 1;
spi::configure_spi_ref_clk(&mut clocks, arbitrary_int::u6::new(spi_ref_clk_div as u8));
// 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 = Gtc::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 = uart::ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = gpio_pins.mio.mio48.into_uart();
let uart_rx = gpio_pins.mio.mio49.into_uart();
let mut uart = uart::Uart::new_with_mio(
dp.uart_1,
uart::UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
uart.write_all(b"-- Zynq 7000 Zedboard SPI L3GD20H example --\n\r")
.unwrap();
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
if DEBUG_SPI_CLK_CONFIG {
info!(
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
clocks.arm_clocks().cpu_1x_clk(),
clocks.io_clocks().ref_clk(),
clocks.io_clocks().spi_clk(),
spi_ref_clk_div
);
}
let sck_pin = gpio_pins.mio.mio12.into_spi();
let mosi_pin = gpio_pins.mio.mio10.into_spi();
let miso_pin = gpio_pins.mio.mio11.into_spi();
let ss_pin = gpio_pins.mio.mio13.into_spi();
let mut spi = spi::Spi::new_one_hw_cs(
dp.spi_1,
clocks.io_clocks(),
spi::Config::new(
// 10 MHz maximum rating of the sensor.
zynq7000::spi::BaudDivSelect::By64,
//l3gd20::MODE,
embedded_hal::spi::MODE_3,
spi::SlaveSelectConfig::AutoWithAutoStart,
),
(sck_pin, mosi_pin, miso_pin),
ss_pin,
)
.unwrap();
let mod_id = spi.regs().read_mod_id();
assert_eq!(mod_id, spi::MODULE_ID);
assert!(spi.sclk() <= Hertz::from_raw(10_000_000));
let min_delay = (spi.sclk().raw() * 5) / 1_000_000_000;
spi.inner().configure_delays(
DelayControl::builder()
.with_inter_word_cs_deassert(0)
.with_between_cs_assertion(0)
.with_inter_word(0)
.with_cs_to_first_bit(min_delay as u8)
.build(),
);
let mio_led = gpio_pins.mio.mio7.into_output(PinState::Low).downgrade();
let mut emio_leds: [EmioPin; 8] = [
gpio_pins.emio.take(0).unwrap(),
gpio_pins.emio.take(1).unwrap(),
gpio_pins.emio.take(2).unwrap(),
gpio_pins.emio.take(3).unwrap(),
gpio_pins.emio.take(4).unwrap(),
gpio_pins.emio.take(5).unwrap(),
gpio_pins.emio.take(6).unwrap(),
gpio_pins.emio.take(7).unwrap(),
];
for (idx, led) in emio_leds.iter_mut().enumerate() {
if idx % 2 == 0 {
led.into_output(PinState::High);
} else {
led.into_output(PinState::Low);
}
}
spawner.spawn(logger_task(uart)).unwrap();
if BLOCKING {
blocking_application(mio_led, emio_leds, spi).await;
} else {
non_blocking_application(mio_led, emio_leds, spi).await;
}
}
#[embassy_executor::task]
pub async fn logger_task(uart: uart::Uart) {
let (tx, _) = uart.split();
let mut tx_async = TxAsync::new(tx);
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
let mut log_buf: [u8; 2048] = [0; 2048];
loop {
let next_frame_len = frame_queue.receive().await;
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
tx_async.write(&log_buf[0..next_frame_len]).await;
}
}
pub async fn blocking_application(
mut mio_led: DynMioPin,
mut emio_leds: [EmioPin; 8],
spi: spi::Spi,
) -> ! {
let mut delay = Delay;
let spi_dev = SpiWithHwCs::new(spi, spi::ChipSelect::Slave0, delay.clone());
let mut l3gd20 = l3gd20::spi::L3gd20::new(spi_dev).unwrap();
let who_am_i = l3gd20.who_am_i().unwrap();
info!("L3GD20 WHO_AM_I: 0x{:02X}", who_am_i);
let mut ticker = Ticker::every(Duration::from_millis(400));
// Power up time for the sensor to get good readings.
delay.delay_ms(50).await;
loop {
mio_led.toggle().unwrap();
let measurements = l3gd20.all().unwrap();
info!("L3GD20: {:?}", measurements);
info!("L3GD20 Temp: {:?}", measurements.temp_celcius());
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
}
ticker.next().await; // Wait for the next cycle of the ticker
}
}
pub async fn non_blocking_application(
mut mio_led: DynMioPin,
mut emio_leds: [EmioPin; 8],
spi: spi::Spi,
) -> ! {
let mut delay = Delay;
let spi_async = SpiAsync::new(spi);
let spi_dev = SpiWithHwCsAsync::new(spi_async, spi::ChipSelect::Slave0, delay.clone());
let mut l3gd20 = l3gd20::asynchronous::spi::L3gd20::new(spi_dev)
.await
.unwrap();
let who_am_i = l3gd20.who_am_i().await.unwrap();
info!("L3GD20 WHO_AM_I: 0x{:02X}", who_am_i);
let mut ticker = Ticker::every(Duration::from_millis(400));
// Power up time for the sensor to get good readings.
delay.delay_ms(50).await;
loop {
mio_led.toggle().unwrap();
let measurements = l3gd20.all().await.unwrap();
info!("L3GD20: {:?}", measurements);
info!("L3GD20 Temp: {:?}", measurements.temp_celcius());
for led in emio_leds.iter_mut() {
led.toggle().unwrap();
}
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) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Spi1 {
on_interrupt(SpiId::Spi1);
} else if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
}
}
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 {}
}

View File

@ -0,0 +1,269 @@
#![no_std]
#![no_main]
use axi_uart16550::AxiUart16550;
use axi_uartlite::AxiUartlite;
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 fugit::RateExtU32;
use log::{error, info};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{EmioPin, GpioPins, PinState},
gtc::Gtc,
uart::{ClkConfigRaw, Uart, UartConfig},
};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard blocking UART example --\n\r";
const AXI_UARTLITE_BASE_ADDR: u32 = 0x42C0_0000;
const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
/// 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();
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum UartSel {
Uart0 = 0b000,
Uartlite = 0b001,
Uart16550 = 0b010,
Uart0ToUartlite = 0b011,
Uart0ToUart16550 = 0b100,
UartliteToUart16550 = 0b101,
}
pub struct UartMultiplexer {
sel_pins: [EmioPin; 3],
}
impl UartMultiplexer {
pub fn new(mut sel_pins: [EmioPin; 3]) -> Self {
for pin in sel_pins.iter_mut() {
pin.into_output(PinState::Low);
}
Self { sel_pins }
}
pub fn select(&mut self, sel: UartSel) {
// TODO: A pin group switcher would be nice to do this in one go.
match sel {
UartSel::Uart0 => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::Uartlite => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_high().unwrap();
}
UartSel::Uart16550 => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_high().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::Uart0ToUartlite => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_high().unwrap();
self.sel_pins[0].set_high().unwrap();
}
UartSel::Uart0ToUart16550 => {
self.sel_pins[2].set_high().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::UartliteToUart16550 => {
self.sel_pins[2].set_high().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_high().unwrap();
}
}
}
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
let dp = PsPeripherals::take().unwrap();
// 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 = Gtc::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 = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = gpio_pins.mio.mio48.into_uart();
let uart_rx = gpio_pins.mio.mio49.into_uart();
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
log_uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Safety: Co-operative multi-tasking is used.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
log_uart,
log::LevelFilter::Trace,
false,
)
};
// 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();
// Safety: Valid address of AXI UARTLITE.
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
// TODO: Can we determine/read the clock frequency to the FPGAs as well?
let (clk_config, error) =
axi_uart16550::ClkConfig::new_autocalc_with_error(100.MHz(), 115200).unwrap();
assert!(error < 0.02);
let mut uart_16550 = unsafe {
AxiUart16550::new(
AXI_UAR16550_BASE_ADDR,
axi_uart16550::UartConfig::new_with_clk_config(clk_config),
)
};
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut mio_led = gpio_pins.mio.mio7.into_output(PinState::Low);
let mut emio_leds: [EmioPin; 8] = [
gpio_pins.emio.take(0).unwrap(),
gpio_pins.emio.take(1).unwrap(),
gpio_pins.emio.take(2).unwrap(),
gpio_pins.emio.take(3).unwrap(),
gpio_pins.emio.take(4).unwrap(),
gpio_pins.emio.take(5).unwrap(),
gpio_pins.emio.take(6).unwrap(),
gpio_pins.emio.take(7).unwrap(),
];
for led in emio_leds.iter_mut() {
led.into_output(PinState::Low);
}
let mut uart_mux = UartMultiplexer::new([
gpio_pins.emio.take(8).unwrap(),
gpio_pins.emio.take(9).unwrap(),
gpio_pins.emio.take(10).unwrap(),
]);
let mut current_sel = UartSel::Uart0;
uart_mux.select(current_sel);
let mut led_idx = 0;
loop {
mio_led.toggle().unwrap();
emio_leds[led_idx].toggle().unwrap();
led_idx += 1;
if led_idx >= emio_leds.len() {
led_idx = 0;
}
uart_0
.write_all("Hello, World from UART0!\n\r".as_bytes())
.unwrap();
uartlite
.write_all("Hello, World from AXI UARTLITE!\n\r".as_bytes())
.unwrap();
uart_16550
.write_all("Hello, World from AXI UART16550!\n\r".as_bytes())
.unwrap();
uart_0.flush().unwrap();
uartlite.flush().unwrap();
uart_16550.flush().unwrap();
match current_sel {
UartSel::Uart0 => current_sel = UartSel::Uartlite,
UartSel::Uartlite => current_sel = UartSel::Uart16550,
UartSel::Uart16550 => current_sel = UartSel::Uart0,
UartSel::Uart0ToUartlite | UartSel::Uart0ToUart16550 | UartSel::UartliteToUart16550 => {
}
}
uart_mux.select(current_sel);
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 {}
}

View File

@ -0,0 +1,575 @@
//! This example application shows usage of the non-blocking APIs offered for
//! various UART peripherals which are part of the PS or the sample bitstream.
//!
//! This example ONLY works with the provided `zedboard-fpga-design`.
//! The example FPGA design contains the following components relevant for this design
//!
//! - An AXI UARTLite peripheral, with the interrupt signal connected to the PS
//! - An AXI UART16550 peripheral, with the interrupt signal connected to the PS
//! - A custom UART multiplexer component. This multiplexer has various configuration modes
//! configurable via 3 EMIO pins:
//! 1. Route UART0 RX to pin JA1 and TX to JA2.
//! 2. Route AXI UARTLite RX to pin JA1 and TX to JA2.
//! 3. Route AXI UART16550 RX to pin JA1 and TX to JA2.
//! 4. Route UART0 to AXI UARTLite.
//! 5. Route UART0 to AXI 16550.
//! 6. Route AXI UARTLite to AXI 16550.
//! - Options 4, 5 and 6 allow to test and show the non-blocking API without the need for external
//! hardware.
//!
//! This example runs one embassy task for each UART, which periodically sends variable sized
//! string content out via its TX port. Each UART peripheral also permanently listens to all
//! incoming RX data via interrupts. Received data will be sent from the interrupt handler
//! to the main thread and will be printed to the main console on UART 1.
#![no_std]
#![no_main]
extern crate alloc;
use alloc::format;
use axi_uart16550::AxiUart16550;
use axi_uartlite::AxiUartlite;
use core::{cell::RefCell, panic::PanicInfo};
use cortex_ar::asm::nop;
use critical_section::Mutex;
use embassy_executor::Spawner;
use embassy_time::{Duration, Ticker};
use embedded_alloc::LlffHeap as Heap;
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write as _;
use heapless::spsc::Queue;
use log::{error, info, warn};
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{EmioPin, GpioPins, Mio7, MioPin, Output, PinState},
gtc::Gtc,
time::Hertz,
uart::{ClkConfigRaw, Uart, UartConfig},
};
pub enum UartMode {
Uart0ToUartlite,
Uart0ToUart16550,
UartliteToUart16550,
}
const UART_MODE: UartMode = UartMode::Uart0ToUart16550;
const INIT_STRING: &str = "-- Zynq 7000 Zedboard non-blocking UART example --\n\r";
#[global_allocator]
static HEAP: Heap = Heap::empty();
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt as _;
// Define the clock frequency as a constant
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
const AXI_UARTLITE_BASE_ADDR: u32 = 0x42C0_0000;
const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
pub const UARTLITE_PL_INT_ID: usize = 0;
pub const UART16550_PL_INT_ID: usize = 1;
const RB_SIZE: usize = 512;
// These queues are used to send all data received in the UART interrupt handlers to the main
// processing thread.
static QUEUE_UART_0: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, RB_SIZE>> =
static_cell::ConstStaticCell::new(Queue::new());
static QUEUE_UARTLITE: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, RB_SIZE>> =
static_cell::ConstStaticCell::new(Queue::new());
static QUEUE_UART16550: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, RB_SIZE>> =
static_cell::ConstStaticCell::new(Queue::new());
// Those are all used by the interrupt handler, so we have to do the Mutex dance.
static RX_UART_0: Mutex<RefCell<Option<zynq7000_hal::uart::Rx>>> = Mutex::new(RefCell::new(None));
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
Mutex::new(RefCell::new(None));
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
Mutex::new(RefCell::new(None));
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
Mutex::new(RefCell::new(None));
/// 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();
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum UartSel {
Uart0 = 0b000,
Uartlite = 0b001,
Uart16550 = 0b010,
Uart0ToUartlite = 0b011,
Uart0ToUart16550 = 0b100,
UartliteToUart16550 = 0b101,
}
pub struct UartMultiplexer {
sel_pins: [EmioPin; 3],
}
impl UartMultiplexer {
pub fn new(mut sel_pins: [EmioPin; 3]) -> Self {
for pin in sel_pins.iter_mut() {
pin.into_output(PinState::Low);
}
Self { sel_pins }
}
pub fn select(&mut self, sel: UartSel) {
// TODO: A pin group switcher would be nice to do this in one go.
match sel {
UartSel::Uart0 => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::Uartlite => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_high().unwrap();
}
UartSel::Uart16550 => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_high().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::Uart0ToUartlite => {
self.sel_pins[2].set_low().unwrap();
self.sel_pins[1].set_high().unwrap();
self.sel_pins[0].set_high().unwrap();
}
UartSel::Uart0ToUart16550 => {
self.sel_pins[2].set_high().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_low().unwrap();
}
UartSel::UartliteToUart16550 => {
self.sel_pins[2].set_high().unwrap();
self.sel_pins[1].set_low().unwrap();
self.sel_pins[0].set_high().unwrap();
}
}
}
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(spawner: Spawner) -> ! {
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
let dp = PsPeripherals::take().unwrap();
// 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();
// AXI UARTLite documentation mentions that a rising-edge sensitive interrupt is generated,
// but the GIC configures high-level sensitivity for the PL interrupts by default. We
// need to change it to edge sensitivity.
gic.set_pl_interrupt_sensitivity(UARTLITE_PL_INT_ID, zynq7000_hal::gic::SpiSensitivity::Edge)
.unwrap();
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 = Gtc::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 = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = gpio_pins.mio.mio48.into_uart();
let uart_rx = gpio_pins.mio.mio49.into_uart();
let mut log_uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.unwrap();
log_uart.write_all(INIT_STRING.as_bytes()).unwrap();
// Initialize the allocator BEFORE you use it
{
use core::mem::MaybeUninit;
// 10 kB heap.
const HEAP_SIZE: usize = 1024 * 10;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
}
// Safety: We are not multi-threaded yet.
unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
log_uart,
log::LevelFilter::Trace,
false,
)
};
// Set up UART multiplexing before creating and configuring the UARTs.
let mut uart_mux = UartMultiplexer::new([
gpio_pins.emio.take(8).unwrap(),
gpio_pins.emio.take(9).unwrap(),
gpio_pins.emio.take(10).unwrap(),
]);
match UART_MODE {
UartMode::Uart0ToUartlite => uart_mux.select(UartSel::Uart0ToUartlite),
UartMode::Uart0ToUart16550 => uart_mux.select(UartSel::Uart0ToUart16550),
UartMode::UartliteToUart16550 => uart_mux.select(UartSel::UartliteToUart16550),
}
// 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();
// 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
// used for both TX and RX, so the API is only exposed for this structure.
uartlite.enable_interrupt();
let (clk_config, error) =
axi_uart16550::ClkConfig::new_autocalc_with_error(clocks.pl_clocks()[0], 115200).unwrap();
assert!(error < 0.02);
let _uart_16550 = unsafe {
AxiUart16550::new(
AXI_UAR16550_BASE_ADDR,
axi_uart16550::UartConfig::new_with_clk_config(clk_config),
)
};
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let mio_led = gpio_pins.mio.mio7.into_output(PinState::Low);
let mut emio_leds: [EmioPin; 8] = [
gpio_pins.emio.take(0).unwrap(),
gpio_pins.emio.take(1).unwrap(),
gpio_pins.emio.take(2).unwrap(),
gpio_pins.emio.take(3).unwrap(),
gpio_pins.emio.take(4).unwrap(),
gpio_pins.emio.take(5).unwrap(),
gpio_pins.emio.take(6).unwrap(),
gpio_pins.emio.take(7).unwrap(),
];
for led in emio_leds.iter_mut() {
led.into_output(PinState::Low);
}
let (uart_0_tx, mut uart_0_rx) = uart_0.split();
let (uartlite_tx, _uartlite_rx) = uartlite.split();
let (uart_16550_tx, mut uart_16550_rx) = _uart_16550.split();
let (uart_0_prod, mut uart_0_cons) = QUEUE_UART_0.take().split();
let (uartlite_prod, mut uartlite_cons) = QUEUE_UARTLITE.take().split();
let (uart16550_prod, mut uart16550_cons) = QUEUE_UART16550.take().split();
// Use our helper function to start RX handling.
uart_0_rx.start_interrupt_driven_reception();
// Use our helper function to start RX handling.
uart_16550_rx.start_interrupt_driven_reception();
critical_section::with(|cs| {
UART_0_PROD.borrow(cs).borrow_mut().replace(uart_0_prod);
UARTLITE_PROD.borrow(cs).borrow_mut().replace(uartlite_prod);
UART16550_PROD
.borrow(cs)
.borrow_mut()
.replace(uart16550_prod);
RX_UART_0.borrow(cs).borrow_mut().replace(uart_0_rx);
});
spawner.spawn(led_task(mio_led, emio_leds)).unwrap();
match UART_MODE {
UartMode::Uart0ToUartlite => {
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
}
UartMode::Uart0ToUart16550 => {
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
}
UartMode::UartliteToUart16550 => {
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
}
}
let mut read_buf: [u8; RB_SIZE] = [0; RB_SIZE];
let mut ticker = Ticker::every(Duration::from_millis(200));
loop {
let read_bytes = uart_0_cons.len();
(0..read_bytes).for_each(|i| {
read_buf[i] = unsafe { uart_0_cons.dequeue_unchecked() };
});
if read_bytes > 0 {
info!("Received {} bytes on UART0", read_bytes);
info!(
"Data: {:?}",
core::str::from_utf8(&read_buf[0..read_bytes]).unwrap()
);
}
let read_bytes = uartlite_cons.len();
(0..read_bytes).for_each(|i| {
read_buf[i] = unsafe { uartlite_cons.dequeue_unchecked() };
});
if read_bytes > 0 {
info!("Received {} bytes on UARTLITE", read_bytes);
info!(
"Data: {:?}",
core::str::from_utf8(&read_buf[0..read_bytes]).unwrap()
);
}
let read_bytes = uart16550_cons.len();
(0..read_bytes).for_each(|i| {
read_buf[i] = unsafe { uart16550_cons.dequeue_unchecked() };
});
if read_bytes > 0 {
info!("Received {} bytes on UART16550", read_bytes);
info!(
"Data: {:?}",
core::str::from_utf8(&read_buf[0..read_bytes]).unwrap()
);
}
ticker.next().await; // Wait for the next cycle of the ticker
}
}
fn build_print_string(prefix: &str, base_str: &str) -> alloc::string::String {
format!("{} {}\n\r", prefix, base_str)
}
#[embassy_executor::task]
async fn led_task(mut mio_led: MioPin<Mio7, Output>, mut emio_leds: [EmioPin; 8]) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut led_idx = 0;
loop {
mio_led.toggle().unwrap();
emio_leds[led_idx].toggle().unwrap();
led_idx += 1;
if led_idx >= emio_leds.len() {
led_idx = 0;
}
ticker.next().await;
}
}
#[embassy_executor::task]
async fn uartlite_task(uartlite: axi_uartlite::Tx) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
let str0 = build_print_string("UARTLITE:", "Hello World");
let str1 = build_print_string(
"UARTLITE:",
"Long Hello which should completely fill the FIFO",
);
let mut idx = 0;
let print_strs = [str0.as_bytes(), str1.as_bytes()];
let mut tx_async = axi_uartlite::tx_async::TxAsync::new(uartlite, 0).unwrap();
loop {
tx_async.write(print_strs[idx]).await;
idx += 1;
if idx == 2 {
idx = 0;
}
ticker.next().await;
}
}
#[embassy_executor::task]
async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx);
let str0 = build_print_string("UART0:", "Hello World");
let str1 = build_print_string(
"UART0:",
"Long Hello which should completely fill the 64 byte FIFO of the UART0 peripheral",
);
let mut idx = 0;
let print_strs = [str0.as_bytes(), str1.as_bytes()];
loop {
tx_async.write(print_strs[idx]).await;
idx += 1;
if idx == 2 {
idx = 0;
}
ticker.next().await;
}
}
#[embassy_executor::task]
async fn uart_16550_task(uart_tx: axi_uart16550::Tx) {
let mut ticker = Ticker::every(Duration::from_millis(1000));
let mut tx_async = axi_uart16550::TxAsync::new(uart_tx, 0, embassy_time::Delay).unwrap();
let str0 = build_print_string("UART16550:", "Hello World");
let str1 = build_print_string(
"UART16550:",
"Long Hello which should completely fill the FIFO",
);
let mut idx = 0;
let print_strs = [str0.as_bytes(), str1.as_bytes()];
loop {
tx_async.write(print_strs[idx]).await;
idx += 1;
if idx == 2 {
idx = 0;
}
ticker.next().await;
}
}
#[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) => match spi_interrupt {
zynq7000_hal::gic::SpiInterrupt::Pl0 => {
on_interrupt_axi_uartlite();
}
zynq7000_hal::gic::SpiInterrupt::Pl1 => {
on_interrupt_axi_16550();
}
zynq7000_hal::gic::SpiInterrupt::Uart0 => {
on_interrupt_uart_0();
}
_ => (),
},
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
}
gic_helper.end_of_interrupt(irq_info);
}
pub 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.
let read_bytes = rx.on_interrupt_rx(&mut buf);
// Handle TX next: Handle pending asynchronous TX operations.
let mut tx = unsafe { axi_uartlite::Tx::steal(AXI_UARTLITE_BASE_ADDR as usize) };
axi_uartlite::on_interrupt_tx(&mut tx, 0);
// Send received RX data to main task.
if read_bytes > 0 {
critical_section::with(|cs| {
let mut prod_ref_mut = UARTLITE_PROD.borrow(cs).borrow_mut();
let prod = prod_ref_mut.as_mut().unwrap();
(0..read_bytes).for_each(|i| {
prod.enqueue(buf[i]).expect("failed to enqueue byte");
});
});
}
}
pub 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) };
let iir = rx.read_iir();
if let Ok(int_id) = iir.int_id() {
match int_id {
axi_uart16550::registers::IntId2::ReceiverLineStatus => {
let errors = rx.on_interrupt_receiver_line_status(iir);
warn!("Receiver line status error: {:?}", errors);
}
axi_uart16550::registers::IntId2::RxDataAvailable
| axi_uart16550::registers::IntId2::CharTimeout => {
read_bytes = rx.on_interrupt_data_available_or_char_timeout(int_id, &mut buf);
}
axi_uart16550::registers::IntId2::ThrEmpty => {
let mut tx = unsafe { axi_uart16550::Tx::steal(AXI_UAR16550_BASE_ADDR as usize) };
axi_uart16550::tx_async::on_interrupt_tx(&mut tx, 0);
}
axi_uart16550::registers::IntId2::ModemStatus => (),
}
}
// Send received RX data to main task.
if read_bytes > 0 {
critical_section::with(|cs| {
let mut prod_ref_mut = UART16550_PROD.borrow(cs).borrow_mut();
let prod = prod_ref_mut.as_mut().unwrap();
(0..read_bytes).for_each(|i| {
prod.enqueue(buf[i]).expect("failed to enqueue byte");
});
});
}
}
fn on_interrupt_uart_0() {
let mut buf = [0; zynq7000_hal::uart::FIFO_DEPTH];
let mut read_bytes = 0;
// Handle RX first: Empty the FIFO content into local buffer.
critical_section::with(|cs| {
let mut uart0 = RX_UART_0.borrow(cs).borrow_mut();
read_bytes = uart0
.as_mut()
.unwrap()
.on_interrupt(&mut buf, true)
.read_bytes();
});
// Handle TX next: Handle pending asynchronous TX operations.
zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0);
// Send received RX data to main task.
if read_bytes > 0 {
critical_section::with(|cs| {
let mut prod_ref_mut = UART_0_PROD.borrow(cs).borrow_mut();
let prod = prod_ref_mut.as_mut().unwrap();
(0..read_bytes).for_each(|i| {
prod.enqueue(buf[i]).expect("failed to enqueue byte");
});
});
}
}
#[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 {}
}

View File

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

View File

@ -0,0 +1,158 @@
#![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::{EmioPin, GpioPins, PinState},
gtc::Gtc,
uart::{ClkConfigRaw, 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) -> ! {
// Enable PS-PL level shifters.
configure_level_shifter(LevelShifterConfig::EnableAll);
let dp = PsPeripherals::take().unwrap();
// 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 = Gtc::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 = ClkConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap()
.0;
let uart_tx = gpio_pins.mio.mio48.into_uart();
let uart_rx = gpio_pins.mio.mio49.into_uart();
let mut uart = Uart::new_with_mio(
dp.uart_1,
UartConfig::new_with_clk_config(uart_clk_config),
(uart_tx, uart_rx),
)
.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 = gpio_pins.mio.mio7.into_output(PinState::Low);
let mut emio_leds: [EmioPin; 8] = [
gpio_pins.emio.take(0).unwrap(),
gpio_pins.emio.take(1).unwrap(),
gpio_pins.emio.take(2).unwrap(),
gpio_pins.emio.take(3).unwrap(),
gpio_pins.emio.take(4).unwrap(),
gpio_pins.emio.take(5).unwrap(),
gpio_pins.emio.take(6).unwrap(),
gpio_pins.emio.take(7).unwrap(),
];
for led in emio_leds.iter_mut() {
led.into_output(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 {}
}

View File

@ -0,0 +1 @@
[build]

1
experiments/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

171
experiments/Cargo.lock generated Normal file
View File

@ -0,0 +1,171 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arbitrary-int"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5"
[[package]]
name = "bitbybit"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d317eeca82e7d88d606419a430590d83552bdceb899cb29904f63d694344b7fc"
dependencies = [
"arbitrary-int",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "derive-mmio"
version = "0.3.0"
dependencies = [
"derive-mmio-macro",
"rustversion",
]
[[package]]
name = "derive-mmio-macro"
version = "0.3.0"
dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "experiments"
version = "0.1.0"
dependencies = [
"derive-mmio",
"static_assertions",
"zynq7000",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
dependencies = [
"critical-section",
"portable-atomic",
]
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[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.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]]
name = "zynq7000"
version = "0.1.0"
dependencies = [
"arbitrary-int",
"bitbybit",
"derive-mmio",
"once_cell",
"static_assertions",
"thiserror",
]

9
experiments/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "experiments"
version = "0.1.0"
edition = "2024"
[dependencies]
static_assertions = "1.1"
derive-mmio = { path = "../../derive-mmio", default-features = false }
zynq7000 = { path = "../zynq7000", default-features = false }

11
experiments/src/main.rs Normal file
View File

@ -0,0 +1,11 @@
use zynq7000::slcr::{ClockControl, Slcr};
fn main() {
let size = core::mem::size_of::<ClockControl>();
println!("Size of ClockControl: {}", size);
let size = core::mem::size_of::<Slcr>();
println!("Size of SLCR: {}", size);
println!("Hello, world!");
}

8
gdb.gdb Normal file
View File

@ -0,0 +1,8 @@
target remote localhost:3000
# *try* to stop at the user entry point (it might be gone due to inlining)
break main
load
continue

8
memory.x Normal file
View File

@ -0,0 +1,8 @@
MEMORY
{
/* Zedboard: 512 MB DDR3. Only use 256 MB for now, should be plenty for a bare-metal app. */
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 256M
}
REGION_ALIAS("DATA", CODE);

3
rust-toolchain.toml Normal file
View File

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

1
scripts/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/xsct-output.log

847
scripts/ps7_init.tcl Normal file
View File

@ -0,0 +1,847 @@
proc ps7_pll_init_data_3_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000110 0x003FFFF0 0x000FA220
mask_write 0XF8000100 0x0007F000 0x00027000
mask_write 0XF8000100 0x00000010 0x00000010
mask_write 0XF8000100 0x00000001 0x00000001
mask_write 0XF8000100 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000001
mask_write 0XF8000100 0x00000010 0x00000000
mask_write 0XF8000120 0x1F003F30 0x1F000200
mask_write 0XF8000114 0x003FFFF0 0x0012C220
mask_write 0XF8000104 0x0007F000 0x00020000
mask_write 0XF8000104 0x00000010 0x00000010
mask_write 0XF8000104 0x00000001 0x00000001
mask_write 0XF8000104 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000002
mask_write 0XF8000104 0x00000010 0x00000000
mask_write 0XF8000124 0xFFF00003 0x0C200003
mask_write 0XF8000118 0x003FFFF0 0x001452C0
mask_write 0XF8000108 0x0007F000 0x0001E000
mask_write 0XF8000108 0x00000010 0x00000010
mask_write 0XF8000108 0x00000001 0x00000001
mask_write 0XF8000108 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000004
mask_write 0XF8000108 0x00000010 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_clock_init_data_3_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000128 0x03F03F01 0x00700F01
mask_write 0XF8000138 0x00000011 0x00000001
mask_write 0XF8000140 0x03F03F71 0x00100801
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_3_0 {} {
mask_write 0XF8006000 0x0001FFFF 0x00000080
mask_write 0XF8006004 0x0007FFFF 0x00001082
mask_write 0XF8006008 0x03FFFFFF 0x03C0780F
mask_write 0XF800600C 0x03FFFFFF 0x02001001
mask_write 0XF8006010 0x03FFFFFF 0x00014001
mask_write 0XF8006014 0x001FFFFF 0x0004159B
mask_write 0XF8006018 0xF7FFFFFF 0x44E458D3
mask_write 0XF800601C 0xFFFFFFFF 0x7282BCE5
mask_write 0XF8006020 0x7FDFFFFC 0x270872D0
mask_write 0XF8006024 0x0FFFFFC3 0x00000000
mask_write 0XF8006028 0x00003FFF 0x00002007
mask_write 0XF800602C 0xFFFFFFFF 0x00000008
mask_write 0XF8006030 0xFFFFFFFF 0x00040B30
mask_write 0XF8006034 0x13FF3FFF 0x000116D4
mask_write 0XF8006038 0x00000003 0x00000000
mask_write 0XF800603C 0x000FFFFF 0x00000777
mask_write 0XF8006040 0xFFFFFFFF 0xFFF00000
mask_write 0XF8006044 0x0FFFFFFF 0x0FF66666
mask_write 0XF8006048 0x0003F03F 0x0003C008
mask_write 0XF8006050 0xFF0F8FFF 0x77010800
mask_write 0XF8006058 0x00010000 0x00000000
mask_write 0XF800605C 0x0000FFFF 0x00005003
mask_write 0XF8006060 0x000017FF 0x0000003E
mask_write 0XF8006064 0x00021FE0 0x00020000
mask_write 0XF8006068 0x03FFFFFF 0x00284141
mask_write 0XF800606C 0x0000FFFF 0x00001610
mask_write 0XF8006078 0x03FFFFFF 0x00466111
mask_write 0XF800607C 0x000FFFFF 0x00032222
mask_write 0XF80060A4 0xFFFFFFFF 0x10200802
mask_write 0XF80060A8 0x0FFFFFFF 0x0690CB73
mask_write 0XF80060AC 0x000001FF 0x000001FE
mask_write 0XF80060B0 0x1FFFFFFF 0x1CFFFFFF
mask_write 0XF80060B4 0x00000200 0x00000200
mask_write 0XF80060B8 0x01FFFFFF 0x00200066
mask_write 0XF80060C4 0x00000003 0x00000000
mask_write 0XF80060C8 0x000000FF 0x00000000
mask_write 0XF80060DC 0x00000001 0x00000000
mask_write 0XF80060F0 0x0000FFFF 0x00000000
mask_write 0XF80060F4 0x0000000F 0x00000008
mask_write 0XF8006114 0x000000FF 0x00000000
mask_write 0XF8006118 0x7FFFFFCF 0x40000001
mask_write 0XF800611C 0x7FFFFFCF 0x40000001
mask_write 0XF8006120 0x7FFFFFCF 0x40000001
mask_write 0XF8006124 0x7FFFFFCF 0x40000001
mask_write 0XF800612C 0x000FFFFF 0x00024000
mask_write 0XF8006130 0x000FFFFF 0x00022C00
mask_write 0XF8006134 0x000FFFFF 0x00023000
mask_write 0XF8006138 0x000FFFFF 0x00024C00
mask_write 0XF8006140 0x000FFFFF 0x00000035
mask_write 0XF8006144 0x000FFFFF 0x00000035
mask_write 0XF8006148 0x000FFFFF 0x00000035
mask_write 0XF800614C 0x000FFFFF 0x00000035
mask_write 0XF8006154 0x000FFFFF 0x00000077
mask_write 0XF8006158 0x000FFFFF 0x0000007C
mask_write 0XF800615C 0x000FFFFF 0x0000007C
mask_write 0XF8006160 0x000FFFFF 0x00000075
mask_write 0XF8006168 0x001FFFFF 0x000000E5
mask_write 0XF800616C 0x001FFFFF 0x000000E0
mask_write 0XF8006170 0x001FFFFF 0x000000E1
mask_write 0XF8006174 0x001FFFFF 0x000000E8
mask_write 0XF800617C 0x000FFFFF 0x000000B7
mask_write 0XF8006180 0x000FFFFF 0x000000BC
mask_write 0XF8006184 0x000FFFFF 0x000000BC
mask_write 0XF8006188 0x000FFFFF 0x000000B5
mask_write 0XF8006190 0x6FFFFEFE 0x00040080
mask_write 0XF8006194 0x000FFFFF 0x0001FC82
mask_write 0XF8006204 0xFFFFFFFF 0x00000000
mask_write 0XF8006208 0x000703FF 0x000003FF
mask_write 0XF800620C 0x000703FF 0x000003FF
mask_write 0XF8006210 0x000703FF 0x000003FF
mask_write 0XF8006214 0x000703FF 0x000003FF
mask_write 0XF8006218 0x000F03FF 0x000003FF
mask_write 0XF800621C 0x000F03FF 0x000003FF
mask_write 0XF8006220 0x000F03FF 0x000003FF
mask_write 0XF8006224 0x000F03FF 0x000003FF
mask_write 0XF80062A8 0x00000FF5 0x00000000
mask_write 0XF80062AC 0xFFFFFFFF 0x00000000
mask_write 0XF80062B0 0x003FFFFF 0x00005125
mask_write 0XF80062B4 0x0003FFFF 0x000012A8
mask_poll 0XF8000B74 0x00002000
mask_write 0XF8006000 0x0001FFFF 0x00000081
mask_poll 0XF8006054 0x00000007
}
proc ps7_mio_init_data_3_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B00 0x00000071 0x00000001
mask_write 0XF8000B40 0x00000FFF 0x00000600
mask_write 0XF8000B44 0x00000FFF 0x00000600
mask_write 0XF8000B48 0x00000FFF 0x00000672
mask_write 0XF8000B4C 0x00000FFF 0x00000672
mask_write 0XF8000B50 0x00000FFF 0x00000674
mask_write 0XF8000B54 0x00000FFF 0x00000674
mask_write 0XF8000B58 0x00000FFF 0x00000600
mask_write 0XF8000B5C 0xFFFFFFFF 0x0018C61C
mask_write 0XF8000B60 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B64 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B68 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B6C 0x00007FFF 0x00000260
mask_write 0XF8000B70 0x00000001 0x00000001
mask_write 0XF8000B70 0x00000021 0x00000020
mask_write 0XF8000B70 0x07FEFFFF 0x00000823
mask_write 0XF8000700 0x00003FFF 0x00001600
mask_write 0XF8000704 0x00003FFF 0x00000702
mask_write 0XF8000708 0x00003FFF 0x00000702
mask_write 0XF800070C 0x00003FFF 0x00000702
mask_write 0XF8000710 0x00003FFF 0x00000702
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
mask_write 0XF8000730 0x00003FFF 0x00001600
mask_write 0XF8000734 0x00003FFF 0x00001600
mask_write 0XF8000738 0x00003FFF 0x00001600
mask_write 0XF800073C 0x00003FFF 0x00001600
mask_write 0XF8000740 0x00003FFF 0x00002902
mask_write 0XF8000744 0x00003FFF 0x00002902
mask_write 0XF8000748 0x00003FFF 0x00002902
mask_write 0XF800074C 0x00003FFF 0x00002902
mask_write 0XF8000750 0x00003FFF 0x00002902
mask_write 0XF8000754 0x00003FFF 0x00002902
mask_write 0XF8000758 0x00003FFF 0x00000903
mask_write 0XF800075C 0x00003FFF 0x00000903
mask_write 0XF8000760 0x00003FFF 0x00000903
mask_write 0XF8000764 0x00003FFF 0x00000903
mask_write 0XF8000768 0x00003FFF 0x00000903
mask_write 0XF800076C 0x00003FFF 0x00000903
mask_write 0XF8000770 0x00003FFF 0x00000304
mask_write 0XF8000774 0x00003FFF 0x00000305
mask_write 0XF8000778 0x00003FFF 0x00000304
mask_write 0XF800077C 0x00003FFF 0x00000305
mask_write 0XF8000780 0x00003FFF 0x00000304
mask_write 0XF8000784 0x00003FFF 0x00000304
mask_write 0XF8000788 0x00003FFF 0x00000304
mask_write 0XF800078C 0x00003FFF 0x00000304
mask_write 0XF8000790 0x00003FFF 0x00000305
mask_write 0XF8000794 0x00003FFF 0x00000304
mask_write 0XF8000798 0x00003FFF 0x00000304
mask_write 0XF800079C 0x00003FFF 0x00000304
mask_write 0XF80007A0 0x00003FFF 0x00000380
mask_write 0XF80007A4 0x00003FFF 0x00000380
mask_write 0XF80007A8 0x00003FFF 0x00000380
mask_write 0XF80007AC 0x00003FFF 0x00000380
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_3_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B48 0x00000180 0x00000180
mask_write 0XF8000B4C 0x00000180 0x00000180
mask_write 0XF8000B50 0x00000180 0x00000180
mask_write 0XF8000B54 0x00000180 0x00000180
mwr -force 0XF8000004 0x0000767B
mask_write 0XE0001034 0x000000FF 0x00000006
mask_write 0XE0001018 0x0000FFFF 0x0000007C
mask_write 0XE0001000 0x000001FF 0x00000017
mask_write 0XE0001004 0x000003FF 0x00000020
mask_write 0XE0000034 0x000000FF 0x00000006
mask_write 0XE0000018 0x0000FFFF 0x0000007C
mask_write 0XE0000000 0x000001FF 0x00000017
mask_write 0XE0000004 0x000003FF 0x00000020
mask_write 0XE000D000 0x00080000 0x00080000
mask_write 0XF8007000 0x20000000 0x00000000
mask_write 0XE000A244 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
mask_write 0XE000A248 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF0000
mask_delay 0XF8F00200 1
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
}
proc ps7_post_config_3_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000900 0x0000000F 0x0000000F
mask_write 0XF8000240 0xFFFFFFFF 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_debug_3_0 {} {
mwr -force 0XF8898FB0 0xC5ACCE55
mwr -force 0XF8899FB0 0xC5ACCE55
mwr -force 0XF8809FB0 0xC5ACCE55
}
proc ps7_pll_init_data_2_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000110 0x003FFFF0 0x000FA220
mask_write 0XF8000100 0x0007F000 0x00027000
mask_write 0XF8000100 0x00000010 0x00000010
mask_write 0XF8000100 0x00000001 0x00000001
mask_write 0XF8000100 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000001
mask_write 0XF8000100 0x00000010 0x00000000
mask_write 0XF8000120 0x1F003F30 0x1F000200
mask_write 0XF8000114 0x003FFFF0 0x0012C220
mask_write 0XF8000104 0x0007F000 0x00020000
mask_write 0XF8000104 0x00000010 0x00000010
mask_write 0XF8000104 0x00000001 0x00000001
mask_write 0XF8000104 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000002
mask_write 0XF8000104 0x00000010 0x00000000
mask_write 0XF8000124 0xFFF00003 0x0C200003
mask_write 0XF8000118 0x003FFFF0 0x001452C0
mask_write 0XF8000108 0x0007F000 0x0001E000
mask_write 0XF8000108 0x00000010 0x00000010
mask_write 0XF8000108 0x00000001 0x00000001
mask_write 0XF8000108 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000004
mask_write 0XF8000108 0x00000010 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_clock_init_data_2_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000128 0x03F03F01 0x00700F01
mask_write 0XF8000138 0x00000011 0x00000001
mask_write 0XF8000140 0x03F03F71 0x00100801
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_2_0 {} {
mask_write 0XF8006000 0x0001FFFF 0x00000080
mask_write 0XF8006004 0x1FFFFFFF 0x00081082
mask_write 0XF8006008 0x03FFFFFF 0x03C0780F
mask_write 0XF800600C 0x03FFFFFF 0x02001001
mask_write 0XF8006010 0x03FFFFFF 0x00014001
mask_write 0XF8006014 0x001FFFFF 0x0004159B
mask_write 0XF8006018 0xF7FFFFFF 0x44E458D3
mask_write 0XF800601C 0xFFFFFFFF 0x7282BCE5
mask_write 0XF8006020 0xFFFFFFFC 0x272872D0
mask_write 0XF8006024 0x0FFFFFFF 0x0000003C
mask_write 0XF8006028 0x00003FFF 0x00002007
mask_write 0XF800602C 0xFFFFFFFF 0x00000008
mask_write 0XF8006030 0xFFFFFFFF 0x00040B30
mask_write 0XF8006034 0x13FF3FFF 0x000116D4
mask_write 0XF8006038 0x00001FC3 0x00000000
mask_write 0XF800603C 0x000FFFFF 0x00000777
mask_write 0XF8006040 0xFFFFFFFF 0xFFF00000
mask_write 0XF8006044 0x0FFFFFFF 0x0FF66666
mask_write 0XF8006048 0x3FFFFFFF 0x0003C248
mask_write 0XF8006050 0xFF0F8FFF 0x77010800
mask_write 0XF8006058 0x0001FFFF 0x00000101
mask_write 0XF800605C 0x0000FFFF 0x00005003
mask_write 0XF8006060 0x000017FF 0x0000003E
mask_write 0XF8006064 0x00021FE0 0x00020000
mask_write 0XF8006068 0x03FFFFFF 0x00284141
mask_write 0XF800606C 0x0000FFFF 0x00001610
mask_write 0XF8006078 0x03FFFFFF 0x00466111
mask_write 0XF800607C 0x000FFFFF 0x00032222
mask_write 0XF80060A0 0x00FFFFFF 0x00008000
mask_write 0XF80060A4 0xFFFFFFFF 0x10200802
mask_write 0XF80060A8 0x0FFFFFFF 0x0690CB73
mask_write 0XF80060AC 0x000001FF 0x000001FE
mask_write 0XF80060B0 0x1FFFFFFF 0x1CFFFFFF
mask_write 0XF80060B4 0x000007FF 0x00000200
mask_write 0XF80060B8 0x01FFFFFF 0x00200066
mask_write 0XF80060C4 0x00000003 0x00000000
mask_write 0XF80060C8 0x000000FF 0x00000000
mask_write 0XF80060DC 0x00000001 0x00000000
mask_write 0XF80060F0 0x0000FFFF 0x00000000
mask_write 0XF80060F4 0x0000000F 0x00000008
mask_write 0XF8006114 0x000000FF 0x00000000
mask_write 0XF8006118 0x7FFFFFFF 0x40000001
mask_write 0XF800611C 0x7FFFFFFF 0x40000001
mask_write 0XF8006120 0x7FFFFFFF 0x40000001
mask_write 0XF8006124 0x7FFFFFFF 0x40000001
mask_write 0XF800612C 0x000FFFFF 0x00024000
mask_write 0XF8006130 0x000FFFFF 0x00022C00
mask_write 0XF8006134 0x000FFFFF 0x00023000
mask_write 0XF8006138 0x000FFFFF 0x00024C00
mask_write 0XF8006140 0x000FFFFF 0x00000035
mask_write 0XF8006144 0x000FFFFF 0x00000035
mask_write 0XF8006148 0x000FFFFF 0x00000035
mask_write 0XF800614C 0x000FFFFF 0x00000035
mask_write 0XF8006154 0x000FFFFF 0x00000077
mask_write 0XF8006158 0x000FFFFF 0x0000007C
mask_write 0XF800615C 0x000FFFFF 0x0000007C
mask_write 0XF8006160 0x000FFFFF 0x00000075
mask_write 0XF8006168 0x001FFFFF 0x000000E5
mask_write 0XF800616C 0x001FFFFF 0x000000E0
mask_write 0XF8006170 0x001FFFFF 0x000000E1
mask_write 0XF8006174 0x001FFFFF 0x000000E8
mask_write 0XF800617C 0x000FFFFF 0x000000B7
mask_write 0XF8006180 0x000FFFFF 0x000000BC
mask_write 0XF8006184 0x000FFFFF 0x000000BC
mask_write 0XF8006188 0x000FFFFF 0x000000B5
mask_write 0XF8006190 0xFFFFFFFF 0x10040080
mask_write 0XF8006194 0x000FFFFF 0x0001FC82
mask_write 0XF8006204 0xFFFFFFFF 0x00000000
mask_write 0XF8006208 0x000F03FF 0x000803FF
mask_write 0XF800620C 0x000F03FF 0x000803FF
mask_write 0XF8006210 0x000F03FF 0x000803FF
mask_write 0XF8006214 0x000F03FF 0x000803FF
mask_write 0XF8006218 0x000F03FF 0x000003FF
mask_write 0XF800621C 0x000F03FF 0x000003FF
mask_write 0XF8006220 0x000F03FF 0x000003FF
mask_write 0XF8006224 0x000F03FF 0x000003FF
mask_write 0XF80062A8 0x00000FF7 0x00000000
mask_write 0XF80062AC 0xFFFFFFFF 0x00000000
mask_write 0XF80062B0 0x003FFFFF 0x00005125
mask_write 0XF80062B4 0x0003FFFF 0x000012A8
mask_poll 0XF8000B74 0x00002000
mask_write 0XF8006000 0x0001FFFF 0x00000081
mask_poll 0XF8006054 0x00000007
}
proc ps7_mio_init_data_2_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B00 0x00000303 0x00000001
mask_write 0XF8000B40 0x00000FFF 0x00000600
mask_write 0XF8000B44 0x00000FFF 0x00000600
mask_write 0XF8000B48 0x00000FFF 0x00000672
mask_write 0XF8000B4C 0x00000FFF 0x00000672
mask_write 0XF8000B50 0x00000FFF 0x00000674
mask_write 0XF8000B54 0x00000FFF 0x00000674
mask_write 0XF8000B58 0x00000FFF 0x00000600
mask_write 0XF8000B5C 0xFFFFFFFF 0x0018C61C
mask_write 0XF8000B60 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B64 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B68 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B6C 0x00007FFF 0x00000260
mask_write 0XF8000B70 0x00000021 0x00000021
mask_write 0XF8000B70 0x00000021 0x00000020
mask_write 0XF8000B70 0x07FFFFFF 0x00000823
mask_write 0XF8000700 0x00003FFF 0x00001600
mask_write 0XF8000704 0x00003FFF 0x00000702
mask_write 0XF8000708 0x00003FFF 0x00000702
mask_write 0XF800070C 0x00003FFF 0x00000702
mask_write 0XF8000710 0x00003FFF 0x00000702
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
mask_write 0XF8000730 0x00003FFF 0x00001600
mask_write 0XF8000734 0x00003FFF 0x00001600
mask_write 0XF8000738 0x00003FFF 0x00001600
mask_write 0XF800073C 0x00003FFF 0x00001600
mask_write 0XF8000740 0x00003FFF 0x00002902
mask_write 0XF8000744 0x00003FFF 0x00002902
mask_write 0XF8000748 0x00003FFF 0x00002902
mask_write 0XF800074C 0x00003FFF 0x00002902
mask_write 0XF8000750 0x00003FFF 0x00002902
mask_write 0XF8000754 0x00003FFF 0x00002902
mask_write 0XF8000758 0x00003FFF 0x00000903
mask_write 0XF800075C 0x00003FFF 0x00000903
mask_write 0XF8000760 0x00003FFF 0x00000903
mask_write 0XF8000764 0x00003FFF 0x00000903
mask_write 0XF8000768 0x00003FFF 0x00000903
mask_write 0XF800076C 0x00003FFF 0x00000903
mask_write 0XF8000770 0x00003FFF 0x00000304
mask_write 0XF8000774 0x00003FFF 0x00000305
mask_write 0XF8000778 0x00003FFF 0x00000304
mask_write 0XF800077C 0x00003FFF 0x00000305
mask_write 0XF8000780 0x00003FFF 0x00000304
mask_write 0XF8000784 0x00003FFF 0x00000304
mask_write 0XF8000788 0x00003FFF 0x00000304
mask_write 0XF800078C 0x00003FFF 0x00000304
mask_write 0XF8000790 0x00003FFF 0x00000305
mask_write 0XF8000794 0x00003FFF 0x00000304
mask_write 0XF8000798 0x00003FFF 0x00000304
mask_write 0XF800079C 0x00003FFF 0x00000304
mask_write 0XF80007A0 0x00003FFF 0x00000380
mask_write 0XF80007A4 0x00003FFF 0x00000380
mask_write 0XF80007A8 0x00003FFF 0x00000380
mask_write 0XF80007AC 0x00003FFF 0x00000380
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_2_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B48 0x00000180 0x00000180
mask_write 0XF8000B4C 0x00000180 0x00000180
mask_write 0XF8000B50 0x00000180 0x00000180
mask_write 0XF8000B54 0x00000180 0x00000180
mwr -force 0XF8000004 0x0000767B
mask_write 0XE0001034 0x000000FF 0x00000006
mask_write 0XE0001018 0x0000FFFF 0x0000007C
mask_write 0XE0001000 0x000001FF 0x00000017
mask_write 0XE0001004 0x00000FFF 0x00000020
mask_write 0XE0000034 0x000000FF 0x00000006
mask_write 0XE0000018 0x0000FFFF 0x0000007C
mask_write 0XE0000000 0x000001FF 0x00000017
mask_write 0XE0000004 0x00000FFF 0x00000020
mask_write 0XE000D000 0x00080000 0x00080000
mask_write 0XF8007000 0x20000000 0x00000000
mask_write 0XE000A244 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
mask_write 0XE000A248 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF0000
mask_delay 0XF8F00200 1
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
}
proc ps7_post_config_2_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000900 0x0000000F 0x0000000F
mask_write 0XF8000240 0xFFFFFFFF 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_debug_2_0 {} {
mwr -force 0XF8898FB0 0xC5ACCE55
mwr -force 0XF8899FB0 0xC5ACCE55
mwr -force 0XF8809FB0 0xC5ACCE55
}
proc ps7_pll_init_data_1_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000110 0x003FFFF0 0x000FA220
mask_write 0XF8000100 0x0007F000 0x00027000
mask_write 0XF8000100 0x00000010 0x00000010
mask_write 0XF8000100 0x00000001 0x00000001
mask_write 0XF8000100 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000001
mask_write 0XF8000100 0x00000010 0x00000000
mask_write 0XF8000120 0x1F003F30 0x1F000200
mask_write 0XF8000114 0x003FFFF0 0x0012C220
mask_write 0XF8000104 0x0007F000 0x00020000
mask_write 0XF8000104 0x00000010 0x00000010
mask_write 0XF8000104 0x00000001 0x00000001
mask_write 0XF8000104 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000002
mask_write 0XF8000104 0x00000010 0x00000000
mask_write 0XF8000124 0xFFF00003 0x0C200003
mask_write 0XF8000118 0x003FFFF0 0x001452C0
mask_write 0XF8000108 0x0007F000 0x0001E000
mask_write 0XF8000108 0x00000010 0x00000010
mask_write 0XF8000108 0x00000001 0x00000001
mask_write 0XF8000108 0x00000001 0x00000000
mask_poll 0XF800010C 0x00000004
mask_write 0XF8000108 0x00000010 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_clock_init_data_1_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000128 0x03F03F01 0x00700F01
mask_write 0XF8000138 0x00000011 0x00000001
mask_write 0XF8000140 0x03F03F71 0x00100801
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_1_0 {} {
mask_write 0XF8006000 0x0001FFFF 0x00000080
mask_write 0XF8006004 0x1FFFFFFF 0x00081082
mask_write 0XF8006008 0x03FFFFFF 0x03C0780F
mask_write 0XF800600C 0x03FFFFFF 0x02001001
mask_write 0XF8006010 0x03FFFFFF 0x00014001
mask_write 0XF8006014 0x001FFFFF 0x0004159B
mask_write 0XF8006018 0xF7FFFFFF 0x44E458D3
mask_write 0XF800601C 0xFFFFFFFF 0x7282BCE5
mask_write 0XF8006020 0xFFFFFFFC 0x272872D0
mask_write 0XF8006024 0x0FFFFFFF 0x0000003C
mask_write 0XF8006028 0x00003FFF 0x00002007
mask_write 0XF800602C 0xFFFFFFFF 0x00000008
mask_write 0XF8006030 0xFFFFFFFF 0x00040B30
mask_write 0XF8006034 0x13FF3FFF 0x000116D4
mask_write 0XF8006038 0x00001FC3 0x00000000
mask_write 0XF800603C 0x000FFFFF 0x00000777
mask_write 0XF8006040 0xFFFFFFFF 0xFFF00000
mask_write 0XF8006044 0x0FFFFFFF 0x0FF66666
mask_write 0XF8006048 0x3FFFFFFF 0x0003C248
mask_write 0XF8006050 0xFF0F8FFF 0x77010800
mask_write 0XF8006058 0x0001FFFF 0x00000101
mask_write 0XF800605C 0x0000FFFF 0x00005003
mask_write 0XF8006060 0x000017FF 0x0000003E
mask_write 0XF8006064 0x00021FE0 0x00020000
mask_write 0XF8006068 0x03FFFFFF 0x00284141
mask_write 0XF800606C 0x0000FFFF 0x00001610
mask_write 0XF80060A0 0x00FFFFFF 0x00008000
mask_write 0XF80060A4 0xFFFFFFFF 0x10200802
mask_write 0XF80060A8 0x0FFFFFFF 0x0690CB73
mask_write 0XF80060AC 0x000001FF 0x000001FE
mask_write 0XF80060B0 0x1FFFFFFF 0x1CFFFFFF
mask_write 0XF80060B4 0x000007FF 0x00000200
mask_write 0XF80060B8 0x01FFFFFF 0x00200066
mask_write 0XF80060C4 0x00000003 0x00000000
mask_write 0XF80060C8 0x000000FF 0x00000000
mask_write 0XF80060DC 0x00000001 0x00000000
mask_write 0XF80060F0 0x0000FFFF 0x00000000
mask_write 0XF80060F4 0x0000000F 0x00000008
mask_write 0XF8006114 0x000000FF 0x00000000
mask_write 0XF8006118 0x7FFFFFFF 0x40000001
mask_write 0XF800611C 0x7FFFFFFF 0x40000001
mask_write 0XF8006120 0x7FFFFFFF 0x40000001
mask_write 0XF8006124 0x7FFFFFFF 0x40000001
mask_write 0XF800612C 0x000FFFFF 0x00024000
mask_write 0XF8006130 0x000FFFFF 0x00022C00
mask_write 0XF8006134 0x000FFFFF 0x00023000
mask_write 0XF8006138 0x000FFFFF 0x00024C00
mask_write 0XF8006140 0x000FFFFF 0x00000035
mask_write 0XF8006144 0x000FFFFF 0x00000035
mask_write 0XF8006148 0x000FFFFF 0x00000035
mask_write 0XF800614C 0x000FFFFF 0x00000035
mask_write 0XF8006154 0x000FFFFF 0x00000077
mask_write 0XF8006158 0x000FFFFF 0x0000007C
mask_write 0XF800615C 0x000FFFFF 0x0000007C
mask_write 0XF8006160 0x000FFFFF 0x00000075
mask_write 0XF8006168 0x001FFFFF 0x000000E5
mask_write 0XF800616C 0x001FFFFF 0x000000E0
mask_write 0XF8006170 0x001FFFFF 0x000000E1
mask_write 0XF8006174 0x001FFFFF 0x000000E8
mask_write 0XF800617C 0x000FFFFF 0x000000B7
mask_write 0XF8006180 0x000FFFFF 0x000000BC
mask_write 0XF8006184 0x000FFFFF 0x000000BC
mask_write 0XF8006188 0x000FFFFF 0x000000B5
mask_write 0XF8006190 0xFFFFFFFF 0x10040080
mask_write 0XF8006194 0x000FFFFF 0x0001FC82
mask_write 0XF8006204 0xFFFFFFFF 0x00000000
mask_write 0XF8006208 0x000F03FF 0x000803FF
mask_write 0XF800620C 0x000F03FF 0x000803FF
mask_write 0XF8006210 0x000F03FF 0x000803FF
mask_write 0XF8006214 0x000F03FF 0x000803FF
mask_write 0XF8006218 0x000F03FF 0x000003FF
mask_write 0XF800621C 0x000F03FF 0x000003FF
mask_write 0XF8006220 0x000F03FF 0x000003FF
mask_write 0XF8006224 0x000F03FF 0x000003FF
mask_write 0XF80062A8 0x00000FF7 0x00000000
mask_write 0XF80062AC 0xFFFFFFFF 0x00000000
mask_write 0XF80062B0 0x003FFFFF 0x00005125
mask_write 0XF80062B4 0x0003FFFF 0x000012A8
mask_poll 0XF8000B74 0x00002000
mask_write 0XF8006000 0x0001FFFF 0x00000081
mask_poll 0XF8006054 0x00000007
}
proc ps7_mio_init_data_1_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B00 0x00000303 0x00000001
mask_write 0XF8000B40 0x00000FFF 0x00000600
mask_write 0XF8000B44 0x00000FFF 0x00000600
mask_write 0XF8000B48 0x00000FFF 0x00000672
mask_write 0XF8000B4C 0x00000FFF 0x00000672
mask_write 0XF8000B50 0x00000FFF 0x00000674
mask_write 0XF8000B54 0x00000FFF 0x00000674
mask_write 0XF8000B58 0x00000FFF 0x00000600
mask_write 0XF8000B5C 0xFFFFFFFF 0x0018C61C
mask_write 0XF8000B60 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B64 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B68 0xFFFFFFFF 0x00F9861C
mask_write 0XF8000B6C 0x000073FF 0x00000260
mask_write 0XF8000B70 0x00000021 0x00000021
mask_write 0XF8000B70 0x00000021 0x00000020
mask_write 0XF8000B70 0x07FFFFFF 0x00000823
mask_write 0XF8000700 0x00003FFF 0x00001600
mask_write 0XF8000704 0x00003FFF 0x00000702
mask_write 0XF8000708 0x00003FFF 0x00000702
mask_write 0XF800070C 0x00003FFF 0x00000702
mask_write 0XF8000710 0x00003FFF 0x00000702
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
mask_write 0XF8000720 0x00003FFF 0x00000702
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
mask_write 0XF8000730 0x00003FFF 0x00001600
mask_write 0XF8000734 0x00003FFF 0x00001600
mask_write 0XF8000738 0x00003FFF 0x00001600
mask_write 0XF800073C 0x00003FFF 0x00001600
mask_write 0XF8000740 0x00003FFF 0x00002902
mask_write 0XF8000744 0x00003FFF 0x00002902
mask_write 0XF8000748 0x00003FFF 0x00002902
mask_write 0XF800074C 0x00003FFF 0x00002902
mask_write 0XF8000750 0x00003FFF 0x00002902
mask_write 0XF8000754 0x00003FFF 0x00002902
mask_write 0XF8000758 0x00003FFF 0x00000903
mask_write 0XF800075C 0x00003FFF 0x00000903
mask_write 0XF8000760 0x00003FFF 0x00000903
mask_write 0XF8000764 0x00003FFF 0x00000903
mask_write 0XF8000768 0x00003FFF 0x00000903
mask_write 0XF800076C 0x00003FFF 0x00000903
mask_write 0XF8000770 0x00003FFF 0x00000304
mask_write 0XF8000774 0x00003FFF 0x00000305
mask_write 0XF8000778 0x00003FFF 0x00000304
mask_write 0XF800077C 0x00003FFF 0x00000305
mask_write 0XF8000780 0x00003FFF 0x00000304
mask_write 0XF8000784 0x00003FFF 0x00000304
mask_write 0XF8000788 0x00003FFF 0x00000304
mask_write 0XF800078C 0x00003FFF 0x00000304
mask_write 0XF8000790 0x00003FFF 0x00000305
mask_write 0XF8000794 0x00003FFF 0x00000304
mask_write 0XF8000798 0x00003FFF 0x00000304
mask_write 0XF800079C 0x00003FFF 0x00000304
mask_write 0XF80007A0 0x00003FFF 0x00000380
mask_write 0XF80007A4 0x00003FFF 0x00000380
mask_write 0XF80007A8 0x00003FFF 0x00000380
mask_write 0XF80007AC 0x00003FFF 0x00000380
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
mask_write 0XF80007BC 0x00003F01 0x00000201
mask_write 0XF80007C0 0x00003FFF 0x000002E0
mask_write 0XF80007C4 0x00003FFF 0x000002E1
mask_write 0XF80007C8 0x00003FFF 0x00000200
mask_write 0XF80007CC 0x00003FFF 0x00000200
mask_write 0XF80007D0 0x00003FFF 0x00000200
mask_write 0XF80007D4 0x00003FFF 0x00000200
mask_write 0XF8000830 0x003F003F 0x002F0037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_1_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000B48 0x00000180 0x00000180
mask_write 0XF8000B4C 0x00000180 0x00000180
mask_write 0XF8000B50 0x00000180 0x00000180
mask_write 0XF8000B54 0x00000180 0x00000180
mwr -force 0XF8000004 0x0000767B
mask_write 0XE0001034 0x000000FF 0x00000006
mask_write 0XE0001018 0x0000FFFF 0x0000007C
mask_write 0XE0001000 0x000001FF 0x00000017
mask_write 0XE0001004 0x00000FFF 0x00000020
mask_write 0XE0000034 0x000000FF 0x00000006
mask_write 0XE0000018 0x0000FFFF 0x0000007C
mask_write 0XE0000000 0x000001FF 0x00000017
mask_write 0XE0000004 0x00000FFF 0x00000020
mask_write 0XE000D000 0x00080000 0x00080000
mask_write 0XF8007000 0x20000000 0x00000000
mask_write 0XE000A244 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
mask_write 0XE000A248 0x003FFFFF 0x00004000
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF0000
mask_delay 0XF8F00200 1
mask_write 0XE000A008 0xFFFFFFFF 0xBFFF4000
}
proc ps7_post_config_1_0 {} {
mwr -force 0XF8000008 0x0000DF0D
mask_write 0XF8000900 0x0000000F 0x0000000F
mask_write 0XF8000240 0xFFFFFFFF 0x00000000
mwr -force 0XF8000004 0x0000767B
}
proc ps7_debug_1_0 {} {
mwr -force 0XF8898FB0 0xC5ACCE55
mwr -force 0XF8899FB0 0xC5ACCE55
mwr -force 0XF8809FB0 0xC5ACCE55
}
set PCW_SILICON_VER_1_0 "0x0"
set PCW_SILICON_VER_2_0 "0x1"
set PCW_SILICON_VER_3_0 "0x2"
set APU_FREQ 650000000
proc mask_poll { addr mask } {
set count 1
set curval "0x[string range [mrd $addr] end-8 end]"
set maskedval [expr {$curval & $mask}]
while { $maskedval == 0 } {
set curval "0x[string range [mrd $addr] end-8 end]"
set maskedval [expr {$curval & $mask}]
set count [ expr { $count + 1 } ]
if { $count == 100000000 } {
puts "Timeout Reached. Mask poll failed at ADDRESS: $addr MASK: $mask"
break
}
}
}
proc mask_delay { addr val } {
set delay [ get_number_of_cycles_for_delay $val ]
perf_reset_and_start_timer
set curval "0x[string range [mrd $addr] end-8 end]"
set maskedval [expr {$curval < $delay}]
while { $maskedval == 1 } {
set curval "0x[string range [mrd $addr] end-8 end]"
set maskedval [expr {$curval < $delay}]
}
perf_reset_clock
}
proc ps_version { } {
set si_ver "0x[string range [mrd 0xF8007080] end-8 end]"
set mask_sil_ver "0x[expr {$si_ver >> 28}]"
return $mask_sil_ver;
}
proc ps7_post_config {} {
set saved_mode [configparams force-mem-accesses]
configparams force-mem-accesses 1
variable PCW_SILICON_VER_1_0
variable PCW_SILICON_VER_2_0
variable PCW_SILICON_VER_3_0
set sil_ver [ps_version]
if { $sil_ver == $PCW_SILICON_VER_1_0} {
ps7_post_config_1_0
} elseif { $sil_ver == $PCW_SILICON_VER_2_0 } {
ps7_post_config_2_0
} else {
ps7_post_config_3_0
}
configparams force-mem-accesses $saved_mode
}
proc ps7_debug {} {
variable PCW_SILICON_VER_1_0
variable PCW_SILICON_VER_2_0
variable PCW_SILICON_VER_3_0
set sil_ver [ps_version]
if { $sil_ver == $PCW_SILICON_VER_1_0} {
ps7_debug_1_0
} elseif { $sil_ver == $PCW_SILICON_VER_2_0 } {
ps7_debug_2_0
} else {
ps7_debug_3_0
}
}
proc ps7_init {} {
variable PCW_SILICON_VER_1_0
variable PCW_SILICON_VER_2_0
variable PCW_SILICON_VER_3_0
set sil_ver [ps_version]
if { $sil_ver == $PCW_SILICON_VER_1_0} {
ps7_mio_init_data_1_0
ps7_pll_init_data_1_0
ps7_clock_init_data_1_0
ps7_ddr_init_data_1_0
ps7_peripherals_init_data_1_0
#puts "PCW Silicon Version : 1.0"
} elseif { $sil_ver == $PCW_SILICON_VER_2_0 } {
ps7_mio_init_data_2_0
ps7_pll_init_data_2_0
ps7_clock_init_data_2_0
ps7_ddr_init_data_2_0
ps7_peripherals_init_data_2_0
#puts "PCW Silicon Version : 2.0"
} else {
ps7_mio_init_data_3_0
ps7_pll_init_data_3_0
ps7_clock_init_data_3_0
ps7_ddr_init_data_3_0
ps7_peripherals_init_data_3_0
#puts "PCW Silicon Version : 3.0"
}
}
# For delay calculation using global timer
# start timer
proc perf_start_clock { } {
#writing SCU_GLOBAL_TIMER_CONTROL register
mask_write 0xF8F00208 0x00000109 0x00000009
}
# stop timer and reset timer count regs
proc perf_reset_clock { } {
perf_disable_clock
mask_write 0xF8F00200 0xFFFFFFFF 0x00000000
mask_write 0xF8F00204 0xFFFFFFFF 0x00000000
}
# Compute mask for given delay in miliseconds
proc get_number_of_cycles_for_delay { delay } {
# GTC is always clocked at 1/2 of the CPU frequency (CPU_3x2x)
variable APU_FREQ
return [ expr ($delay * $APU_FREQ /(2 * 1000))]
}
# stop timer
proc perf_disable_clock {} {
mask_write 0xF8F00208 0xFFFFFFFF 0x00000000
}
proc perf_reset_and_start_timer {} {
perf_reset_clock
perf_start_clock
}

20
scripts/runner.sh Executable file
View File

@ -0,0 +1,20 @@
#!/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

3
scripts/unittest.sh Executable file
View File

@ -0,0 +1,3 @@
#!/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

86
scripts/xsct-init.tcl Normal file
View File

@ -0,0 +1,86 @@
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"

153
scripts/zynq7000-init.py Executable file
View File

@ -0,0 +1,153 @@
#!/usr/bin/env python3
import shlex
import argparse
import os
import subprocess
import sys
from pathlib import Path
# Define the default values
TCL_SCRIPT_NAME = "xsct-init.tcl"
SCRIPTS_DIR = "scripts"
DEFAULT_IP_ADDRESS_HW_SERVER = "localhost"
def main():
parser = argparse.ArgumentParser(
description="Wrapper script for xsct-init.tcl\n"
"It launches xsct, initialize and prepare the processing system for debugging and "
"allows loading a bitstream and/or bare-metal application to the board.",
formatter_class=argparse.RawTextHelpFormatter, # This preserves line breaks
)
parser.add_argument(
"-t",
"--tools",
# Required only if env var is not set
required=not bool(os.getenv("AMD_TOOLS")),
# Use env var if set
default=os.getenv("AMD_TOOLS"),
help="The path to the tool to use. Must point to a valid Vivado tools installation which"
"also provides xsct, for example a Vitis installation.\nThe directory where the path "
"points to should contain a shell script named settings64.sh.\nYou can also set the "
"AMD_TOOLS env variable to set this.",
)
parser.add_argument(
"--sdt",
dest="sdt",
help="Path to a SDT output folder which contains a PS7 init TCL script and a bitstream.",
)
parser.add_argument(
"--no-bit",
dest="no_bit",
action="store_true",
help="No bitstream flashing for initialization with SDT.",
)
parser.add_argument("-a", "--app", dest="app", help="Path to the app to program")
parser.add_argument(
"-i",
"--ip",
default=DEFAULT_IP_ADDRESS_HW_SERVER,
help="The IP address of the hardware server (default: localhost)",
)
parser.add_argument(
"--itcl",
dest="init_tcl",
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"
)
parser.add_argument(
"-b",
"--bit",
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"
)
args = parser.parse_args()
settings_script = os.path.join(args.tools, "settings64.sh")
if not os.path.isfile(settings_script):
print(f"Invalid tool path {args.tools}, did not find settings file.")
sys.exit(1)
# Source the settings script and check for xsdb availability
command = f"source {settings_script} && command -v xsct"
result = subprocess.run(
command,
shell=True,
capture_output=True,
executable="/bin/bash",
)
if result.returncode != 0:
print("Error: 'xsct' could not be found after sourcing settings64.sh.")
sys.exit(1)
if args.app and not os.path.isfile(args.app):
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
if args.bit:
bitstream = args.bit
if args.sdt:
sdt_path = Path(args.sdt)
# CLI bitstream argument overrides implicit SDT bitstream.
if not args.no_bit and not bitstream:
# Search for .bit files in the SDT folder
bit_files = list(sdt_path.glob("*.bit"))
if len(bit_files) == 0:
print("Error: No .bit file found in the SDT folder.")
elif len(bit_files) > 1:
print("Error: Multiple .bit files found in the SDT folder.")
bitstream = str(bit_files[0])
# Search for the ps7_init.tcl file
init_script = sdt_path / "ps7_init.tcl"
if not init_script.exists():
sys.exit("Error: ps7_init.tcl file not found in the SDT folder.")
init_tcl = str(init_script)
else:
if not args.init_tcl:
print("Error: No ps7_init.tcl file specified.")
sys.exit(1)
if args.bit:
bitstream = args.bit
init_tcl = args.init_tcl
xsct_script = Path(TCL_SCRIPT_NAME)
if not xsct_script.exists():
xsct_script = Path(os.path.join(SCRIPTS_DIR, TCL_SCRIPT_NAME))
if not xsct_script.exists():
print(f"Could not find the xsct initialization script {TCL_SCRIPT_NAME}")
sys.exit(1)
# Launch xsct with the initialization script
# 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(bitstream)
# Join safely for shell execution
xsct_cmd = shlex.join(cmd_list)
print(f"Running xsct command: {xsct_cmd}")
command = f"bash -c 'source {settings_script} && {xsct_cmd} | tee xsct-output.log'"
subprocess.run(
command,
shell=True,
check=True,
)
if __name__ == "__main__":
main()

155
vscode/launch.json Normal file
View File

@ -0,0 +1,155 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Blinky Example",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/armv7a-none-eabihf/debug/simple",
"miDebuggerServerAddress": "localhost:3000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"stopAtEntry": true,
"useExtendedRemote": true,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "None",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Load symbols for the program",
"text": "symbol-file ${workspaceFolder}/target/armv7a-none-eabihf/debug/simple",
"ignoreFailures": false
},
{
"text": "set output-radix 16"
}
],
"preLaunchTask": "flash-blinky"
},
{
"name": "Debug Logger Example",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/armv7a-none-eabihf/debug/logger",
"miDebuggerServerAddress": "localhost:3000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"stopAtEntry": true,
"useExtendedRemote": true,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "None",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Load symbols for the program",
"text": "symbol-file ${workspaceFolder}/target/armv7a-none-eabihf/debug/logger",
"ignoreFailures": false
},
{
"text": "set output-radix 16"
}
],
"preLaunchTask": "flash-logger"
},
{
"name": "Debug Embassy Example",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/armv7a-none-eabihf/debug/embassy-examples",
"miDebuggerServerAddress": "localhost:3000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"stopAtEntry": true,
"useExtendedRemote": true,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "None",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Load symbols for the program",
"text": "symbol-file ${workspaceFolder}/target/armv7a-none-eabihf/debug/embassy-examples",
"ignoreFailures": false
},
{
"text": "set output-radix 16"
}
],
"preLaunchTask": "flash-embassy"
},
{
"name": "Debug Zedboard GPIOs",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/armv7a-none-eabihf/debug/zedboard",
"miDebuggerServerAddress": "localhost:3000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"stopAtEntry": true,
"useExtendedRemote": true,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "None",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Load symbols for the program",
"text": "symbol-file ${workspaceFolder}/target/armv7a-none-eabihf/debug/zedboard",
"ignoreFailures": false
},
{
"text": "set output-radix 16"
}
],
"preLaunchTask": "flash-zed-gpios"
},
{
"name": "Debug UART Non-Blocking",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/target/armv7a-none-eabihf/debug/uart-non-blocking",
"miDebuggerServerAddress": "localhost:3000",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"stopAtEntry": true,
"useExtendedRemote": true,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "None",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Load symbols for the program",
"text": "symbol-file ${workspaceFolder}/target/armv7a-none-eabihf/debug/uart-non-blocking",
"ignoreFailures": false
},
{
"text": "set output-radix 16"
}
],
"preLaunchTask": "flash-uart-non-blocking"
}
]
}

163
vscode/tasks.json Normal file
View File

@ -0,0 +1,163 @@
{
"version": "2.0.0",
"options": {
"env": {
// Pass environment variables to the zynq7000-init.py script.
"AMD_TOOLS": "/tools/Xilinx/Vitis/2024.1",
"TCL_INIT_SCRIPT": "${workspaceFolder}/zedboard-fpga-design/sdt_out/ps7_init.tcl",
// Leading _, otherwise the Python script will always flash it.
"_ZYNQ_BITSTREAM": "${workspaceFolder}/zedboard-fpga-design/sdt_out/zedboard-rust.bit"
}
},
"tasks": [
{
"label": "zedboard-init",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"--bit",
"${BITSTREAM}"
],
"problemMatcher": []
},
{
"label": "flash-blinky",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"-a",
"${workspaceFolder}/target/armv7a-none-eabihf/debug/simple"
],
"dependsOn": [
"build-blinky"
],
"problemMatcher": []
},
{
"label": "build-blinky",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"-p",
"simple"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "flash-logger",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"-a",
"${workspaceFolder}/target/armv7a-none-eabihf/debug/logger"
],
"dependsOn": [
"build-logger"
],
"problemMatcher": []
},
{
"label": "build-logger",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"logger"
],
"group": {
"kind": "build"
}
},
{
"label": "flash-embassy",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"-a",
"${workspaceFolder}/target/armv7a-none-eabihf/debug/embassy-examples"
],
"dependsOn": [
"build-embassy"
],
"problemMatcher": []
},
{
"label": "build-embassy",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"-p",
"embassy-examples"
],
"group": {
"kind": "build"
}
},
{
"label": "flash-zed-gpios",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"-a",
"${workspaceFolder}/target/armv7a-none-eabihf/debug/zedboard",
// The bitstream does not necesarilly need to be flashed each time, but doing so here requires one
// less extra step to prepare the system.
"--bit",
"${_ZYNQ_BITSTREAM}"
],
"dependsOn": [
"build-zed-gpios"
],
"problemMatcher": []
},
{
"label": "build-zed-gpios",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"zedboard"
],
"group": {
"kind": "build"
}
},
{
"label": "flash-uart-non-blocking",
"type": "shell",
"command": "${workspaceFolder}/scripts/zynq7000-init.py",
"args": [
"-a",
"${workspaceFolder}/target/armv7a-none-eabihf/debug/uart-non-blocking",
// The bitstream does not necesarilly need to be flashed each time, but doing so here requires one
// less extra step to prepare the system.
"--bit",
"${_ZYNQ_BITSTREAM}"
],
"dependsOn": [
"build-uart-non-blocking"
],
"problemMatcher": []
},
{
"label": "build-uart-non-blocking",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"uart-non-blocking"
],
"group": {
"kind": "build"
}
}
]
}

5
zedboard-fpga-design/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/zedboard-rust
/.Xil
/vivado*
/sdt_out
/xsct-output.log

View File

@ -0,0 +1,40 @@
Zedboard FPGA design for Rust
=======
This is an example/reference design which was used to verify various components provided
by this library. To minimize the amount of HW designs required, one project is provided.
The design was kept as generic as possible. In principle, it should be possible to adapt the
hardware design to other boards with modifications.
# Pre-Requisites
- [Vivado installation](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools.html)
or [Vitis installation](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vitis.html)
which includes Vivado. This example design was created with/for Vivado 2024.1, but also might work
for newer versions.
- [Zedboard board files](https://github.com/Digilent/vivado-boards) added to the Vivado installation.
# Loading the project and the block design with the GUI
You can load the project using the batch mode of `vivado` inside the folder where you want to
create the `zedboard-rust` project:
```sh
vivado -mode batch -source <path to zedboard-rust.tcl> -tclargs --overwrite
```
for example, to create the directory directly insdie this directory:
```sh
vivado -mode batch -source zedboard-rust.tcl -tclargs --overwrite
```
This should create a `zedboard-rust` Vivado project folder containing a `zedboard-rust.xpr`
project file. You can load this project file with Vivado:
```sh
vivado zedboard-rust.xpr
```
You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and
`Load Project`.

View File

@ -0,0 +1,2 @@
set hw_file "[get_property DIRECTORY [current_project]]/zedboard-rust.xsa"
write_hw_platform -fixed -include_bit -force -file $hw_file

69
zedboard-fpga-design/sdtgen.py Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/env python3
import argparse
import os
import subprocess
import sys
def main():
parser = argparse.ArgumentParser(
description="Script to generate SDT files from a XSA file"
)
parser.add_argument(
"-t",
"--tools",
# Required only if env var is not set
required=not bool(os.getenv("AMD_TOOLS")),
# Use env var if set
default=os.getenv("AMD_TOOLS"),
help="The path to the tool to use. Must point to a valid Vivado tools installation which"
"also provides xsct, for example a Vitis installation.\nThe directory where the path "
"points to should contain a shell script named settings64.sh.\n You can also set the "
"AMD_TOOLS env variable to set this.",
)
parser.add_argument(
"-x",
"--xsa",
help="Path to the input XSA file",
default="zedboard-rust/zedboard-rust.xsa",
)
parser.add_argument(
"-o",
"--out",
default="sdt_out",
help="Directory to store the generated SDT files",
)
args = parser.parse_args()
settings_script = os.path.join(args.tools, "settings64.sh")
if not os.path.isfile(settings_script):
print(f"Invalid tool path {args.tools}, did not find settings file.")
sys.exit(1)
# Source the settings script and check for xsdb availability
command = f"source {settings_script} && command -v xsct"
result = subprocess.run(
command,
shell=True,
capture_output=True,
executable="/bin/bash",
)
if result.returncode != 0:
print("Error: 'xsct' could not be found after sourcing settings64.sh.")
sys.exit(1)
xsct_script = "sdtgen.tcl"
command = f"bash -c 'source {settings_script} && xsct {xsct_script} {args.xsa} {args.out} | tee xsct-output.log'"
subprocess.run(
command,
shell=True,
check=True,
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,4 @@
set outdir [lindex $argv 1]
set xsa [lindex $argv 0]
sdtgen set_dt_param -xsa $xsa -dir $outdir -board_dts zedboard
sdtgen generate_sdt

View File

@ -0,0 +1,88 @@
----------------------------------------------------------------------------------
-- Company: Institute of Space Systems, University of Stuttgart
-- Engineer: Robin Mueller
--
-- Description:
-- The module multiplexes three UART modules.
-- It can be used to select between UART0 through EMIO, UARTLITE or UART16550.
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity uart_mux is
port(
sys_clk : in std_logic;
uart_0_tx : in std_logic;
uart_0_rx : out std_logic;
uart_1_tx : in std_logic;
uart_1_rx : out std_logic;
uart_2_tx : in std_logic;
uart_2_rx : out std_logic;
tx_out: out std_logic;
rx_in: in std_logic;
-- "000" -> UART0
-- "001" -> UART1
-- "010" -> UART2
-- "011" -> UART0 to UART1
-- "100" -> UART0 to UART2
-- "101" -> UART1 to UART2
sel : in std_logic_vector(2 downto 0)
);
end uart_mux;
architecture Behavioral of uart_mux is
begin
switch : process(sys_clk)
begin
if rising_edge(sys_clk) then
case sel is
when "000" =>
tx_out <= uart_0_tx;
uart_0_rx <= rx_in;
uart_1_rx <= '1';
uart_2_rx <= '1';
when "001" =>
tx_out <= uart_1_tx;
uart_1_rx <= rx_in;
uart_2_rx <= '1';
uart_0_rx <= '1';
when "010" =>
tx_out <= uart_2_tx;
uart_2_rx <= rx_in;
uart_1_rx <= '1';
uart_0_rx <= '1';
when "011" =>
tx_out <= '1';
uart_1_rx <= uart_0_tx;
uart_0_rx <= uart_1_tx;
uart_2_rx <= '1';
when "100" =>
tx_out <= '1';
uart_2_rx <= uart_0_tx;
uart_0_rx <= uart_2_tx;
uart_1_rx <= '1';
when "101" =>
tx_out <= '1';
uart_1_rx <= uart_2_tx;
uart_2_rx <= uart_1_tx;
uart_0_rx <= '1';
when others =>
tx_out <= '1';
uart_0_rx <= '1';
uart_1_rx <= '1';
uart_2_rx <= '1';
end case;
end if;
end process; -- switch
end Behavioral;

View File

@ -0,0 +1,698 @@
################################################################
# This is a generated script based on design: zedboard
#
# Though there are limitations about the generated script,
# the main purpose of this utility is to make learning
# IP Integrator Tcl commands easier.
################################################################
namespace eval _tcl {
proc get_script_folder {} {
set script_path [file normalize [info script]]
set script_folder [file dirname $script_path]
return $script_folder
}
}
variable script_folder
set script_folder [_tcl::get_script_folder]
################################################################
# Check if script is running in correct Vivado version.
################################################################
set scripts_vivado_version 2024.1
set current_vivado_version [version -short]
if { [string first $scripts_vivado_version $current_vivado_version] == -1 } {
puts ""
if { [string compare $scripts_vivado_version $current_vivado_version] > 0 } {
catch {common::send_gid_msg -ssname BD::TCL -id 2042 -severity "ERROR" " This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Sourcing the script failed since it was created with a future version of Vivado."}
} else {
catch {common::send_gid_msg -ssname BD::TCL -id 2041 -severity "ERROR" "This script was generated using Vivado <$scripts_vivado_version> and is being run in <$current_vivado_version> of Vivado. Please run the script in Vivado <$scripts_vivado_version> then open the design in Vivado <$current_vivado_version>. Upgrade the design by running \"Tools => Report => Report IP Status...\", then run write_bd_tcl to create an updated script."}
}
return 1
}
################################################################
# START
################################################################
# To test this script, run the following commands from Vivado Tcl console:
# source zedboard_script.tcl
# The design that will be created by this Tcl script contains the following
# module references:
# uart_mux
# Please add the sources of those modules before sourcing this Tcl script.
# If there is no project opened, this script will create a
# project, but make sure you do not have an existing project
# <./myproj/project_1.xpr> in the current working folder.
set list_projs [get_projects -quiet]
if { $list_projs eq "" } {
create_project project_1 myproj -part xc7z020clg484-1
set_property BOARD_PART digilentinc.com:zedboard:part0:1.1 [current_project]
}
# CHANGE DESIGN NAME HERE
variable design_name
set design_name zedboard
# If you do not already have an existing IP Integrator design open,
# you can create a design using the following command:
# create_bd_design $design_name
# Creating design if needed
set errMsg ""
set nRet 0
set cur_design [current_bd_design -quiet]
set list_cells [get_bd_cells -quiet]
if { ${design_name} eq "" } {
# USE CASES:
# 1) Design_name not set
set errMsg "Please set the variable <design_name> to a non-empty value."
set nRet 1
} elseif { ${cur_design} ne "" && ${list_cells} eq "" } {
# USE CASES:
# 2): Current design opened AND is empty AND names same.
# 3): Current design opened AND is empty AND names diff; design_name NOT in project.
# 4): Current design opened AND is empty AND names diff; design_name exists in project.
if { $cur_design ne $design_name } {
common::send_gid_msg -ssname BD::TCL -id 2001 -severity "INFO" "Changing value of <design_name> from <$design_name> to <$cur_design> since current design is empty."
set design_name [get_property NAME $cur_design]
}
common::send_gid_msg -ssname BD::TCL -id 2002 -severity "INFO" "Constructing design in IPI design <$cur_design>..."
} elseif { ${cur_design} ne "" && $list_cells ne "" && $cur_design eq $design_name } {
# USE CASES:
# 5) Current design opened AND has components AND same names.
set errMsg "Design <$design_name> already exists in your project, please set the variable <design_name> to another value."
set nRet 1
} elseif { [get_files -quiet ${design_name}.bd] ne "" } {
# USE CASES:
# 6) Current opened design, has components, but diff names, design_name exists in project.
# 7) No opened design, design_name exists in project.
set errMsg "Design <$design_name> already exists in your project, please set the variable <design_name> to another value."
set nRet 2
} else {
# USE CASES:
# 8) No opened design, design_name not in project.
# 9) Current opened design, has components, but diff names, design_name not in project.
common::send_gid_msg -ssname BD::TCL -id 2003 -severity "INFO" "Currently there is no design <$design_name> in project, so creating one..."
create_bd_design $design_name
common::send_gid_msg -ssname BD::TCL -id 2004 -severity "INFO" "Making design <$design_name> as current_bd_design."
current_bd_design $design_name
}
common::send_gid_msg -ssname BD::TCL -id 2005 -severity "INFO" "Currently the variable <design_name> is equal to \"$design_name\"."
if { $nRet != 0 } {
catch {common::send_gid_msg -ssname BD::TCL -id 2006 -severity "ERROR" $errMsg}
return $nRet
}
set bCheckIPsPassed 1
##################################################################
# CHECK IPs
##################################################################
set bCheckIPs 1
if { $bCheckIPs == 1 } {
set list_check_ips "\
xilinx.com:ip:processing_system7:5.5\
xilinx.com:ip:axi_uartlite:2.0\
xilinx.com:ip:proc_sys_reset:5.0\
xilinx.com:ip:axi_uart16550:2.0\
xilinx.com:ip:xlconcat:2.1\
xilinx.com:ip:xlslice:1.0\
xilinx.com:ip:xlconstant:1.1\
"
set list_ips_missing ""
common::send_gid_msg -ssname BD::TCL -id 2011 -severity "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ."
foreach ip_vlnv $list_check_ips {
set ip_obj [get_ipdefs -all $ip_vlnv]
if { $ip_obj eq "" } {
lappend list_ips_missing $ip_vlnv
}
}
if { $list_ips_missing ne "" } {
catch {common::send_gid_msg -ssname BD::TCL -id 2012 -severity "ERROR" "The following IPs are not found in the IP Catalog:\n $list_ips_missing\n\nResolution: Please add the repository containing the IP(s) to the project." }
set bCheckIPsPassed 0
}
}
##################################################################
# CHECK Modules
##################################################################
set bCheckModules 1
if { $bCheckModules == 1 } {
set list_check_mods "\
uart_mux\
"
set list_mods_missing ""
common::send_gid_msg -ssname BD::TCL -id 2020 -severity "INFO" "Checking if the following modules exist in the project's sources: $list_check_mods ."
foreach mod_vlnv $list_check_mods {
if { [can_resolve_reference $mod_vlnv] == 0 } {
lappend list_mods_missing $mod_vlnv
}
}
if { $list_mods_missing ne "" } {
catch {common::send_gid_msg -ssname BD::TCL -id 2021 -severity "ERROR" "The following module(s) are not found in the project: $list_mods_missing" }
common::send_gid_msg -ssname BD::TCL -id 2022 -severity "INFO" "Please add source files for the missing module(s) above."
set bCheckIPsPassed 0
}
}
if { $bCheckIPsPassed != 1 } {
common::send_gid_msg -ssname BD::TCL -id 2023 -severity "WARNING" "Will not continue with creation of design due to the error(s) above."
return 3
}
##################################################################
# DESIGN PROCs
##################################################################
# Procedure to create entire design; Provide argument to make
# procedure reusable. If parentCell is "", will use root.
proc create_root_design { parentCell } {
variable script_folder
variable design_name
if { $parentCell eq "" } {
set parentCell [get_bd_cells /]
}
# Get object for parentCell
set parentObj [get_bd_cells $parentCell]
if { $parentObj == "" } {
catch {common::send_gid_msg -ssname BD::TCL -id 2090 -severity "ERROR" "Unable to find parent cell <$parentCell>!"}
return
}
# Make sure parentObj is hier blk
set parentType [get_property TYPE $parentObj]
if { $parentType ne "hier" } {
catch {common::send_gid_msg -ssname BD::TCL -id 2091 -severity "ERROR" "Parent <$parentObj> has TYPE = <$parentType>. Expected to be <hier>."}
return
}
# Save current instance; Restore later
set oldCurInst [current_bd_instance .]
# Set parent object as current
current_bd_instance $parentObj
# Create interface ports
set DDR [ create_bd_intf_port -mode Master -vlnv xilinx.com:interface:ddrx_rtl:1.0 DDR ]
set FIXED_IO [ create_bd_intf_port -mode Master -vlnv xilinx.com:display_processing_system7:fixedio_rtl:1.0 FIXED_IO ]
# Create ports
set LEDS [ create_bd_port -dir O -from 7 -to 0 LEDS ]
set SWITCHES [ create_bd_port -dir I -from 7 -to 0 SWITCHES ]
set BTTNS [ create_bd_port -dir I -from 4 -to 0 BTTNS ]
set UART_txd [ create_bd_port -dir O UART_txd ]
set UART_rxd [ create_bd_port -dir I UART_rxd ]
set TTC0_WAVEOUT [ create_bd_port -dir O TTC0_WAVEOUT ]
# Create instance: processing_system7_0, and set properties
set processing_system7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 processing_system7_0 ]
set_property -dict [list \
CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {650.000000} \
CONFIG.PCW_ACT_CAN_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_DCI_PERIPHERAL_FREQMHZ {10.158730} \
CONFIG.PCW_ACT_ENET0_PERIPHERAL_FREQMHZ {125.000000} \
CONFIG.PCW_ACT_ENET1_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_FPGA0_PERIPHERAL_FREQMHZ {100.000000} \
CONFIG.PCW_ACT_FPGA1_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_FPGA2_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_FPGA3_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_PCAP_PERIPHERAL_FREQMHZ {200.000000} \
CONFIG.PCW_ACT_QSPI_PERIPHERAL_FREQMHZ {200.000000} \
CONFIG.PCW_ACT_SDIO_PERIPHERAL_FREQMHZ {50.000000} \
CONFIG.PCW_ACT_SMC_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_SPI_PERIPHERAL_FREQMHZ {10.000000} \
CONFIG.PCW_ACT_TPIU_PERIPHERAL_FREQMHZ {200.000000} \
CONFIG.PCW_ACT_TTC0_CLK0_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_TTC0_CLK1_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_TTC0_CLK2_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_TTC1_CLK0_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_TTC1_CLK1_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_TTC1_CLK2_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_ACT_UART_PERIPHERAL_FREQMHZ {100.000000} \
CONFIG.PCW_ACT_WDT_PERIPHERAL_FREQMHZ {108.333336} \
CONFIG.PCW_APU_PERIPHERAL_FREQMHZ {650} \
CONFIG.PCW_CLK0_FREQ {100000000} \
CONFIG.PCW_CLK1_FREQ {10000000} \
CONFIG.PCW_CLK2_FREQ {10000000} \
CONFIG.PCW_CLK3_FREQ {10000000} \
CONFIG.PCW_CORE0_FIQ_INTR {0} \
CONFIG.PCW_CRYSTAL_PERIPHERAL_FREQMHZ {33.333333} \
CONFIG.PCW_DDR_RAM_HIGHADDR {0x1FFFFFFF} \
CONFIG.PCW_ENET0_ENET0_IO {MIO 16 .. 27} \
CONFIG.PCW_ENET0_GRP_MDIO_ENABLE {1} \
CONFIG.PCW_ENET0_GRP_MDIO_IO {EMIO} \
CONFIG.PCW_ENET0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_ENET0_PERIPHERAL_FREQMHZ {1000 Mbps} \
CONFIG.PCW_ENET0_RESET_ENABLE {0} \
CONFIG.PCW_ENET_RESET_ENABLE {1} \
CONFIG.PCW_ENET_RESET_SELECT {Share reset pin} \
CONFIG.PCW_EN_EMIO_GPIO {1} \
CONFIG.PCW_EN_EMIO_TTC0 {1} \
CONFIG.PCW_EN_EMIO_TTC1 {0} \
CONFIG.PCW_EN_EMIO_UART0 {1} \
CONFIG.PCW_EN_EMIO_WP_SDIO0 {1} \
CONFIG.PCW_EN_ENET0 {1} \
CONFIG.PCW_EN_GPIO {1} \
CONFIG.PCW_EN_QSPI {1} \
CONFIG.PCW_EN_SDIO0 {1} \
CONFIG.PCW_EN_TTC0 {1} \
CONFIG.PCW_EN_TTC1 {0} \
CONFIG.PCW_EN_UART0 {1} \
CONFIG.PCW_EN_UART1 {1} \
CONFIG.PCW_EN_USB0 {1} \
CONFIG.PCW_FPGA0_PERIPHERAL_FREQMHZ {100} \
CONFIG.PCW_FPGA_FCLK0_ENABLE {1} \
CONFIG.PCW_GPIO_EMIO_GPIO_ENABLE {1} \
CONFIG.PCW_GPIO_EMIO_GPIO_IO {64} \
CONFIG.PCW_GPIO_EMIO_GPIO_WIDTH {64} \
CONFIG.PCW_GPIO_MIO_GPIO_ENABLE {1} \
CONFIG.PCW_GPIO_MIO_GPIO_IO {MIO} \
CONFIG.PCW_I2C_RESET_ENABLE {1} \
CONFIG.PCW_IRQ_F2P_INTR {1} \
CONFIG.PCW_MIO_0_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_0_PULLUP {enabled} \
CONFIG.PCW_MIO_0_SLEW {slow} \
CONFIG.PCW_MIO_10_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_10_PULLUP {enabled} \
CONFIG.PCW_MIO_10_SLEW {slow} \
CONFIG.PCW_MIO_11_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_11_PULLUP {enabled} \
CONFIG.PCW_MIO_11_SLEW {slow} \
CONFIG.PCW_MIO_12_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_12_PULLUP {enabled} \
CONFIG.PCW_MIO_12_SLEW {slow} \
CONFIG.PCW_MIO_13_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_13_PULLUP {enabled} \
CONFIG.PCW_MIO_13_SLEW {slow} \
CONFIG.PCW_MIO_14_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_14_PULLUP {enabled} \
CONFIG.PCW_MIO_14_SLEW {slow} \
CONFIG.PCW_MIO_15_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_15_PULLUP {enabled} \
CONFIG.PCW_MIO_15_SLEW {slow} \
CONFIG.PCW_MIO_16_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_16_PULLUP {disabled} \
CONFIG.PCW_MIO_16_SLEW {fast} \
CONFIG.PCW_MIO_17_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_17_PULLUP {disabled} \
CONFIG.PCW_MIO_17_SLEW {fast} \
CONFIG.PCW_MIO_18_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_18_PULLUP {disabled} \
CONFIG.PCW_MIO_18_SLEW {fast} \
CONFIG.PCW_MIO_19_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_19_PULLUP {disabled} \
CONFIG.PCW_MIO_19_SLEW {fast} \
CONFIG.PCW_MIO_1_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_1_PULLUP {disabled} \
CONFIG.PCW_MIO_1_SLEW {fast} \
CONFIG.PCW_MIO_20_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_20_PULLUP {disabled} \
CONFIG.PCW_MIO_20_SLEW {fast} \
CONFIG.PCW_MIO_21_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_21_PULLUP {disabled} \
CONFIG.PCW_MIO_21_SLEW {fast} \
CONFIG.PCW_MIO_22_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_22_PULLUP {disabled} \
CONFIG.PCW_MIO_22_SLEW {fast} \
CONFIG.PCW_MIO_23_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_23_PULLUP {disabled} \
CONFIG.PCW_MIO_23_SLEW {fast} \
CONFIG.PCW_MIO_24_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_24_PULLUP {disabled} \
CONFIG.PCW_MIO_24_SLEW {fast} \
CONFIG.PCW_MIO_25_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_25_PULLUP {disabled} \
CONFIG.PCW_MIO_25_SLEW {fast} \
CONFIG.PCW_MIO_26_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_26_PULLUP {disabled} \
CONFIG.PCW_MIO_26_SLEW {fast} \
CONFIG.PCW_MIO_27_IOTYPE {HSTL 1.8V} \
CONFIG.PCW_MIO_27_PULLUP {disabled} \
CONFIG.PCW_MIO_27_SLEW {fast} \
CONFIG.PCW_MIO_28_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_28_PULLUP {disabled} \
CONFIG.PCW_MIO_28_SLEW {fast} \
CONFIG.PCW_MIO_29_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_29_PULLUP {disabled} \
CONFIG.PCW_MIO_29_SLEW {fast} \
CONFIG.PCW_MIO_2_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_2_SLEW {fast} \
CONFIG.PCW_MIO_30_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_30_PULLUP {disabled} \
CONFIG.PCW_MIO_30_SLEW {fast} \
CONFIG.PCW_MIO_31_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_31_PULLUP {disabled} \
CONFIG.PCW_MIO_31_SLEW {fast} \
CONFIG.PCW_MIO_32_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_32_PULLUP {disabled} \
CONFIG.PCW_MIO_32_SLEW {fast} \
CONFIG.PCW_MIO_33_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_33_PULLUP {disabled} \
CONFIG.PCW_MIO_33_SLEW {fast} \
CONFIG.PCW_MIO_34_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_34_PULLUP {disabled} \
CONFIG.PCW_MIO_34_SLEW {fast} \
CONFIG.PCW_MIO_35_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_35_PULLUP {disabled} \
CONFIG.PCW_MIO_35_SLEW {fast} \
CONFIG.PCW_MIO_36_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_36_PULLUP {disabled} \
CONFIG.PCW_MIO_36_SLEW {fast} \
CONFIG.PCW_MIO_37_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_37_PULLUP {disabled} \
CONFIG.PCW_MIO_37_SLEW {fast} \
CONFIG.PCW_MIO_38_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_38_PULLUP {disabled} \
CONFIG.PCW_MIO_38_SLEW {fast} \
CONFIG.PCW_MIO_39_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_39_PULLUP {disabled} \
CONFIG.PCW_MIO_39_SLEW {fast} \
CONFIG.PCW_MIO_3_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_3_SLEW {fast} \
CONFIG.PCW_MIO_40_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_40_PULLUP {disabled} \
CONFIG.PCW_MIO_40_SLEW {fast} \
CONFIG.PCW_MIO_41_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_41_PULLUP {disabled} \
CONFIG.PCW_MIO_41_SLEW {fast} \
CONFIG.PCW_MIO_42_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_42_PULLUP {disabled} \
CONFIG.PCW_MIO_42_SLEW {fast} \
CONFIG.PCW_MIO_43_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_43_PULLUP {disabled} \
CONFIG.PCW_MIO_43_SLEW {fast} \
CONFIG.PCW_MIO_44_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_44_PULLUP {disabled} \
CONFIG.PCW_MIO_44_SLEW {fast} \
CONFIG.PCW_MIO_45_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_45_PULLUP {disabled} \
CONFIG.PCW_MIO_45_SLEW {fast} \
CONFIG.PCW_MIO_46_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_46_PULLUP {enabled} \
CONFIG.PCW_MIO_46_SLEW {slow} \
CONFIG.PCW_MIO_47_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_47_PULLUP {disabled} \
CONFIG.PCW_MIO_47_SLEW {slow} \
CONFIG.PCW_MIO_48_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_48_PULLUP {disabled} \
CONFIG.PCW_MIO_48_SLEW {slow} \
CONFIG.PCW_MIO_49_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_49_PULLUP {disabled} \
CONFIG.PCW_MIO_49_SLEW {slow} \
CONFIG.PCW_MIO_4_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_4_SLEW {fast} \
CONFIG.PCW_MIO_50_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_50_PULLUP {disabled} \
CONFIG.PCW_MIO_50_SLEW {slow} \
CONFIG.PCW_MIO_51_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_51_PULLUP {disabled} \
CONFIG.PCW_MIO_51_SLEW {slow} \
CONFIG.PCW_MIO_52_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_52_PULLUP {disabled} \
CONFIG.PCW_MIO_52_SLEW {slow} \
CONFIG.PCW_MIO_53_IOTYPE {LVCMOS 1.8V} \
CONFIG.PCW_MIO_53_PULLUP {disabled} \
CONFIG.PCW_MIO_53_SLEW {slow} \
CONFIG.PCW_MIO_5_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_5_SLEW {fast} \
CONFIG.PCW_MIO_6_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_6_SLEW {fast} \
CONFIG.PCW_MIO_7_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_7_SLEW {slow} \
CONFIG.PCW_MIO_8_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_8_SLEW {fast} \
CONFIG.PCW_MIO_9_IOTYPE {LVCMOS 3.3V} \
CONFIG.PCW_MIO_9_PULLUP {enabled} \
CONFIG.PCW_MIO_9_SLEW {slow} \
CONFIG.PCW_MIO_TREE_PERIPHERALS {GPIO#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#Quad SPI Flash#GPIO#Quad SPI Flash#GPIO#GPIO#GPIO#GPIO#GPIO#GPIO#GPIO#Enet 0#Enet 0#Enet\
0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#Enet 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#USB 0#SD 0#SD 0#SD 0#SD 0#SD 0#SD 0#USB Reset#SD 0#UART 1#UART 1#GPIO#GPIO#GPIO#GPIO}\
\
CONFIG.PCW_MIO_TREE_SIGNALS {gpio[0]#qspi0_ss_b#qspi0_io[0]#qspi0_io[1]#qspi0_io[2]#qspi0_io[3]/HOLD_B#qspi0_sclk#gpio[7]#qspi_fbclk#gpio[9]#gpio[10]#gpio[11]#gpio[12]#gpio[13]#gpio[14]#gpio[15]#tx_clk#txd[0]#txd[1]#txd[2]#txd[3]#tx_ctl#rx_clk#rxd[0]#rxd[1]#rxd[2]#rxd[3]#rx_ctl#data[4]#dir#stp#nxt#data[0]#data[1]#data[2]#data[3]#clk#data[5]#data[6]#data[7]#clk#cmd#data[0]#data[1]#data[2]#data[3]#reset#cd#tx#rx#gpio[50]#gpio[51]#gpio[52]#gpio[53]}\
\
CONFIG.PCW_PRESET_BANK1_VOLTAGE {LVCMOS 1.8V} \
CONFIG.PCW_QSPI_GRP_FBCLK_ENABLE {1} \
CONFIG.PCW_QSPI_GRP_FBCLK_IO {MIO 8} \
CONFIG.PCW_QSPI_GRP_IO1_ENABLE {0} \
CONFIG.PCW_QSPI_GRP_SINGLE_SS_ENABLE {1} \
CONFIG.PCW_QSPI_GRP_SINGLE_SS_IO {MIO 1 .. 6} \
CONFIG.PCW_QSPI_GRP_SS1_ENABLE {0} \
CONFIG.PCW_QSPI_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_QSPI_PERIPHERAL_FREQMHZ {200} \
CONFIG.PCW_QSPI_QSPI_IO {MIO 1 .. 6} \
CONFIG.PCW_SD0_GRP_CD_ENABLE {1} \
CONFIG.PCW_SD0_GRP_CD_IO {MIO 47} \
CONFIG.PCW_SD0_GRP_POW_ENABLE {0} \
CONFIG.PCW_SD0_GRP_WP_ENABLE {1} \
CONFIG.PCW_SD0_GRP_WP_IO {EMIO} \
CONFIG.PCW_SD0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_SD0_SD0_IO {MIO 40 .. 45} \
CONFIG.PCW_SDIO_PERIPHERAL_FREQMHZ {50} \
CONFIG.PCW_SDIO_PERIPHERAL_VALID {1} \
CONFIG.PCW_SINGLE_QSPI_DATA_MODE {x4} \
CONFIG.PCW_TTC0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_TTC0_TTC0_IO {EMIO} \
CONFIG.PCW_TTC1_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_TTC_PERIPHERAL_FREQMHZ {50} \
CONFIG.PCW_UART0_GRP_FULL_ENABLE {0} \
CONFIG.PCW_UART0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_UART0_UART0_IO {EMIO} \
CONFIG.PCW_UART1_GRP_FULL_ENABLE {0} \
CONFIG.PCW_UART1_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_UART1_UART1_IO {MIO 48 .. 49} \
CONFIG.PCW_UART_PERIPHERAL_FREQMHZ {100} \
CONFIG.PCW_UART_PERIPHERAL_VALID {1} \
CONFIG.PCW_UIPARAM_ACT_DDR_FREQ_MHZ {533.333374} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY0 {0.176} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY1 {0.159} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY2 {0.162} \
CONFIG.PCW_UIPARAM_DDR_BOARD_DELAY3 {0.187} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_0 {-0.073} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_1 {-0.034} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_2 {-0.03} \
CONFIG.PCW_UIPARAM_DDR_DQS_TO_CLK_DELAY_3 {-0.082} \
CONFIG.PCW_UIPARAM_DDR_FREQ_MHZ {525} \
CONFIG.PCW_UIPARAM_DDR_PARTNO {MT41K128M16 JT-125} \
CONFIG.PCW_UIPARAM_DDR_TRAIN_DATA_EYE {1} \
CONFIG.PCW_UIPARAM_DDR_TRAIN_READ_GATE {1} \
CONFIG.PCW_UIPARAM_DDR_TRAIN_WRITE_LEVEL {1} \
CONFIG.PCW_USB0_PERIPHERAL_ENABLE {1} \
CONFIG.PCW_USB0_RESET_ENABLE {1} \
CONFIG.PCW_USB0_RESET_IO {MIO 46} \
CONFIG.PCW_USB0_USB0_IO {MIO 28 .. 39} \
CONFIG.PCW_USB_RESET_ENABLE {1} \
CONFIG.PCW_USB_RESET_SELECT {Share reset pin} \
CONFIG.PCW_USE_FABRIC_INTERRUPT {1} \
] $processing_system7_0
# Create instance: axi_uartlite_0, and set properties
set axi_uartlite_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_uartlite:2.0 axi_uartlite_0 ]
set_property -dict [list \
CONFIG.C_BAUDRATE {115200} \
CONFIG.UARTLITE_BOARD_INTERFACE {Custom} \
CONFIG.USE_BOARD_FLOW {true} \
] $axi_uartlite_0
# Create instance: ps7_0_axi_periph, and set properties
set ps7_0_axi_periph [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 ps7_0_axi_periph ]
set_property CONFIG.NUM_MI {2} $ps7_0_axi_periph
# Create instance: rst_ps7_0_100M, and set properties
set rst_ps7_0_100M [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 rst_ps7_0_100M ]
# Create instance: axi_uart16550_0, and set properties
set axi_uart16550_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_uart16550:2.0 axi_uart16550_0 ]
set_property -dict [list \
CONFIG.UART_BOARD_INTERFACE {Custom} \
CONFIG.USE_BOARD_FLOW {true} \
] $axi_uart16550_0
# Create instance: IRQ_F2P, and set properties
set IRQ_F2P [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 IRQ_F2P ]
# Create instance: LEDS, and set properties
set LEDS [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlslice:1.0 LEDS ]
set_property -dict [list \
CONFIG.DIN_FROM {7} \
CONFIG.DIN_WIDTH {16} \
] $LEDS
# Create instance: EMIO_O_0, and set properties
set EMIO_O_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlslice:1.0 EMIO_O_0 ]
set_property -dict [list \
CONFIG.DIN_FROM {15} \
CONFIG.DIN_WIDTH {64} \
] $EMIO_O_0
# Create instance: EMIO_O_1, and set properties
set EMIO_O_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlslice:1.0 EMIO_O_1 ]
set_property -dict [list \
CONFIG.DIN_FROM {47} \
CONFIG.DIN_TO {32} \
CONFIG.DIN_WIDTH {64} \
] $EMIO_O_1
# Create instance: EMIO_I, and set properties
set EMIO_I [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 EMIO_I ]
set_property -dict [list \
CONFIG.IN0_WIDTH {16} \
CONFIG.IN1_WIDTH {16} \
CONFIG.IN2_WIDTH {16} \
CONFIG.IN3_WIDTH {16} \
CONFIG.NUM_PORTS {4} \
] $EMIO_I
# Create instance: EMIO_I_0, and set properties
set EMIO_I_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 EMIO_I_0 ]
set_property -dict [list \
CONFIG.IN0_WIDTH {8} \
CONFIG.IN1_WIDTH {5} \
CONFIG.IN2_WIDTH {3} \
CONFIG.NUM_PORTS {3} \
] $EMIO_I_0
# Create instance: xlconstant_0, and set properties
set xlconstant_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 xlconstant_0 ]
set_property -dict [list \
CONFIG.CONST_VAL {0} \
CONFIG.CONST_WIDTH {3} \
] $xlconstant_0
# Create instance: EMIO_I_1, and set properties
set EMIO_I_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 EMIO_I_1 ]
set_property -dict [list \
CONFIG.CONST_VAL {0} \
CONFIG.CONST_WIDTH {16} \
] $EMIO_I_1
# Create instance: uart_mux_0, and set properties
set block_name uart_mux
set block_cell_name uart_mux_0
if { [catch {set uart_mux_0 [create_bd_cell -type module -reference $block_name $block_cell_name] } errmsg] } {
catch {common::send_gid_msg -ssname BD::TCL -id 2095 -severity "ERROR" "Unable to add referenced block <$block_name>. Please add the files for ${block_name}'s definition into the project."}
return 1
} elseif { $uart_mux_0 eq "" } {
catch {common::send_gid_msg -ssname BD::TCL -id 2096 -severity "ERROR" "Unable to referenced block <$block_name>. Please add the files for ${block_name}'s definition into the project."}
return 1
}
# Create instance: UART_MUX, and set properties
set UART_MUX [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlslice:1.0 UART_MUX ]
set_property -dict [list \
CONFIG.DIN_FROM {10} \
CONFIG.DIN_TO {8} \
CONFIG.DIN_WIDTH {16} \
] $UART_MUX
# Create instance: xlconstant_1, and set properties
set xlconstant_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconstant:1.1 xlconstant_1 ]
# Create interface connections
connect_bd_intf_net -intf_net processing_system7_0_DDR [get_bd_intf_ports DDR] [get_bd_intf_pins processing_system7_0/DDR]
connect_bd_intf_net -intf_net processing_system7_0_FIXED_IO [get_bd_intf_ports FIXED_IO] [get_bd_intf_pins processing_system7_0/FIXED_IO]
connect_bd_intf_net -intf_net processing_system7_0_M_AXI_GP0 [get_bd_intf_pins processing_system7_0/M_AXI_GP0] [get_bd_intf_pins ps7_0_axi_periph/S00_AXI]
connect_bd_intf_net -intf_net ps7_0_axi_periph_M00_AXI [get_bd_intf_pins ps7_0_axi_periph/M00_AXI] [get_bd_intf_pins axi_uartlite_0/S_AXI]
connect_bd_intf_net -intf_net ps7_0_axi_periph_M01_AXI [get_bd_intf_pins ps7_0_axi_periph/M01_AXI] [get_bd_intf_pins axi_uart16550_0/S_AXI]
# Create port connections
connect_bd_net -net EMIO_O_1_Dout [get_bd_pins EMIO_O_1/Dout] [get_bd_pins EMIO_I/In2]
connect_bd_net -net In0_0_1 [get_bd_ports SWITCHES] [get_bd_pins EMIO_I_0/In0]
connect_bd_net -net In1_0_1 [get_bd_ports BTTNS] [get_bd_pins EMIO_I_0/In1]
connect_bd_net -net axi_uart16550_0_ip2intc_irpt [get_bd_pins axi_uart16550_0/ip2intc_irpt] [get_bd_pins IRQ_F2P/In1]
connect_bd_net -net axi_uart16550_0_sout [get_bd_pins axi_uart16550_0/sout] [get_bd_pins uart_mux_0/uart_2_tx]
connect_bd_net -net axi_uartlite_0_interrupt [get_bd_pins axi_uartlite_0/interrupt] [get_bd_pins IRQ_F2P/In0]
connect_bd_net -net axi_uartlite_0_tx [get_bd_pins axi_uartlite_0/tx] [get_bd_pins uart_mux_0/uart_1_tx]
connect_bd_net -net processing_system7_0_FCLK_CLK0 [get_bd_pins processing_system7_0/FCLK_CLK0] [get_bd_pins processing_system7_0/M_AXI_GP0_ACLK] [get_bd_pins ps7_0_axi_periph/S00_ACLK] [get_bd_pins rst_ps7_0_100M/slowest_sync_clk] [get_bd_pins axi_uartlite_0/s_axi_aclk] [get_bd_pins ps7_0_axi_periph/M00_ACLK] [get_bd_pins ps7_0_axi_periph/ACLK] [get_bd_pins axi_uart16550_0/s_axi_aclk] [get_bd_pins ps7_0_axi_periph/M01_ACLK] [get_bd_pins uart_mux_0/sys_clk]
connect_bd_net -net processing_system7_0_FCLK_RESET0_N [get_bd_pins processing_system7_0/FCLK_RESET0_N] [get_bd_pins rst_ps7_0_100M/ext_reset_in]
connect_bd_net -net processing_system7_0_GPIO_O [get_bd_pins processing_system7_0/GPIO_O] [get_bd_pins EMIO_O_0/Din] [get_bd_pins EMIO_O_1/Din]
connect_bd_net -net processing_system7_0_TTC0_WAVE0_OUT [get_bd_pins processing_system7_0/TTC0_WAVE0_OUT] [get_bd_ports TTC0_WAVEOUT]
connect_bd_net -net processing_system7_0_UART0_TX [get_bd_pins processing_system7_0/UART0_TX] [get_bd_pins uart_mux_0/uart_0_tx]
connect_bd_net -net rst_ps7_0_100M_peripheral_aresetn [get_bd_pins rst_ps7_0_100M/peripheral_aresetn] [get_bd_pins ps7_0_axi_periph/S00_ARESETN] [get_bd_pins axi_uartlite_0/s_axi_aresetn] [get_bd_pins ps7_0_axi_periph/M00_ARESETN] [get_bd_pins ps7_0_axi_periph/ARESETN] [get_bd_pins axi_uart16550_0/s_axi_aresetn] [get_bd_pins ps7_0_axi_periph/M01_ARESETN]
connect_bd_net -net rx_in_0_1 [get_bd_ports UART_rxd] [get_bd_pins uart_mux_0/rx_in]
connect_bd_net -net uart_mux_0_tx_out [get_bd_pins uart_mux_0/tx_out] [get_bd_ports UART_txd]
connect_bd_net -net uart_mux_0_uart_0_rx [get_bd_pins uart_mux_0/uart_0_rx] [get_bd_pins processing_system7_0/UART0_RX]
connect_bd_net -net uart_mux_0_uart_1_rx [get_bd_pins uart_mux_0/uart_1_rx] [get_bd_pins axi_uartlite_0/rx]
connect_bd_net -net uart_mux_0_uart_2_rx [get_bd_pins uart_mux_0/uart_2_rx] [get_bd_pins axi_uart16550_0/sin]
connect_bd_net -net xlconcat_0_dout [get_bd_pins IRQ_F2P/dout] [get_bd_pins processing_system7_0/IRQ_F2P]
connect_bd_net -net xlconcat_1_dout [get_bd_pins EMIO_I/dout] [get_bd_pins processing_system7_0/GPIO_I]
connect_bd_net -net xlconcat_1_dout1 [get_bd_pins EMIO_I_0/dout] [get_bd_pins EMIO_I/In1]
connect_bd_net -net xlconstant_0_dout [get_bd_pins xlconstant_0/dout] [get_bd_pins EMIO_I_0/In2]
connect_bd_net -net xlconstant_1_dout [get_bd_pins EMIO_I_1/dout] [get_bd_pins EMIO_I/In3]
connect_bd_net -net xlconstant_1_dout1 [get_bd_pins xlconstant_1/dout] [get_bd_pins axi_uart16550_0/rin] [get_bd_pins axi_uart16550_0/dsrn] [get_bd_pins axi_uart16550_0/ctsn] [get_bd_pins axi_uart16550_0/dcdn]
connect_bd_net -net xlslice_0_Dout [get_bd_pins LEDS/Dout] [get_bd_ports LEDS]
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]
# 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
# Restore current instance
current_bd_instance $oldCurInst
validate_bd_design
save_bd_design
}
# End of create_root_design()
##################################################################
# MAIN FLOW
##################################################################
create_root_design ""

View File

@ -0,0 +1,75 @@
# Zedboard LD0
set_property PACKAGE_PIN T22 [get_ports {LEDS[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[0]}]
# Zedboard LD1
set_property PACKAGE_PIN T21 [get_ports {LEDS[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[1]}]
# Zedboard LD2
set_property PACKAGE_PIN U22 [get_ports {LEDS[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[2]}]
# Zedboard LD3
set_property PACKAGE_PIN U21 [get_ports {LEDS[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[3]}]
# Zedboard LD4
set_property PACKAGE_PIN V22 [get_ports {LEDS[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[4]}]
# Zedboard LD5
set_property PACKAGE_PIN W22 [get_ports {LEDS[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[5]}]
# Zedboard LD6
set_property PACKAGE_PIN U19 [get_ports {LEDS[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[6]}]
# Zedboard LD7
set_property PACKAGE_PIN U14 [get_ports {LEDS[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LEDS[7]}]
# Zedboard SW0
set_property PACKAGE_PIN F22 [get_ports {SWITCHES[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[0]}]
# Zedboard SW1
set_property PACKAGE_PIN G22 [get_ports {SWITCHES[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[1]}]
# Zedboard SW2
set_property PACKAGE_PIN H22 [get_ports {SWITCHES[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[2]}]
# Zedboard SW3
set_property PACKAGE_PIN F21 [get_ports {SWITCHES[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[3]}]
# Zedboard SW4
set_property PACKAGE_PIN H19 [get_ports {SWITCHES[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[4]}]
# Zedboard SW5
set_property PACKAGE_PIN H18 [get_ports {SWITCHES[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[5]}]
# Zedboard SW6
set_property PACKAGE_PIN H17 [get_ports {SWITCHES[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[6]}]
# Zedboard SW7
set_property PACKAGE_PIN M15 [get_ports {SWITCHES[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SWITCHES[7]}]
# Zedboard BTNC
set_property PACKAGE_PIN P16 [get_ports {BTTNS[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTTNS[0]}]
# Zedboard BTND
set_property PACKAGE_PIN R16 [get_ports {BTTNS[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTTNS[1]}]
# Zedboard BTNL
set_property PACKAGE_PIN N15 [get_ports {BTTNS[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTTNS[2]}]
# Zedboard BTNR
set_property PACKAGE_PIN R18 [get_ports {BTTNS[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTTNS[3]}]
# Zedboard BTNU
set_property PACKAGE_PIN T18 [get_ports {BTTNS[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {BTTNS[4]}]
# UART
set_property PACKAGE_PIN Y11 [get_ports UART_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_rxd]
set_property PACKAGE_PIN AA11 [get_ports UART_txd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
# TTC0 Wave Out
set_property PACKAGE_PIN W12 [get_ports {TTC0_WAVEOUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {TTC0_WAVEOUT}]

View File

@ -0,0 +1,528 @@
#*****************************************************************************************
# Vivado (TM) v2024.1 (64-bit)
#
# zedboard-rust.tcl: Tcl script for re-creating project 'zedboard-rust'
#
# Generated by Vivado on Fri Mar 14 13:10:19 CET 2025
# IP Build 5075265 on Wed May 22 21:45:21 MDT 2024
#
# This file contains the Vivado Tcl commands for re-creating the project to the state*
# when this script was generated. In order to re-create the project, please source this
# file in the Vivado Tcl Shell.
#
# * Note that the runs in the created project will be configured the same way as the
# original project, however they will not be launched automatically. To regenerate the
# run results please launch the synthesis/implementation runs as needed.
#
#*****************************************************************************************
# NOTE: In order to use this script for source control purposes, please make sure that the
# following files are added to the source control system:-
#
# 1. This project restoration tcl script (zedboard-rust.tcl) that was generated.
# 2. Constraints file in src directory
# 3. BD export file in src directory
#
#*****************************************************************************************
# Set the reference directory for source file relative paths (by default the value is script directory path)
set script_dir [file dirname [info script]]
set origin_dir "."
set constr_file [file normalize "$script_dir/src/zedboard.xdc"]
set bd_file [file normalize "$script_dir/src/zedboard-bd.tcl"]
set uart_mux_file [file normalize "$script_dir/src/uart_mux.vhd"]
# Check file required for this script exists
proc checkRequiredFiles { origin_dir} {
set status true
set files [list \
"[file normalize $constr_file]"\
"[file normalize $bd_file]"\
]
foreach ifile $files {
if { ![file isfile $ifile] } {
puts " Could not find local file $ifile "
set status false
}
}
return $status
}
# Use origin directory path location variable, if specified in the tcl shell
if { [info exists ::origin_dir_loc] } {
set origin_dir $::origin_dir_loc
}
# Set the project name
set _xil_proj_name_ "zedboard-rust"
# Use project name variable, if specified in the tcl shell
if { [info exists ::user_project_name] } {
set _xil_proj_name_ $::user_project_name
}
variable script_file
set script_file "zedboard-rust.tcl"
# Help information for this script
proc print_help {} {
variable script_file
puts "\nDescription:"
puts "Recreate a Vivado project from this script. The created project will be"
puts "functionally equivalent to the original project for which this script was"
puts "generated. The script contains commands for creating a project, filesets,"
puts "runs, adding/importing sources and setting properties on various objects.\n"
puts "Syntax:"
puts "$script_file"
puts "$script_file -tclargs \[--origin_dir <path>\]"
puts "$script_file -tclargs \[--project_name <name>\]"
puts "$script_file -tclargs \[--help\]\n"
puts "$script_file -tclargs \[--overwrite\]\n"
puts "Usage:"
puts "Name Description"
puts "-------------------------------------------------------------------------"
puts "\[--origin_dir <path>\] Determine source file paths wrt this path. Default"
puts " origin_dir path value is \".\", otherwise, the value"
puts " that was set with the \"-paths_relative_to\" switch"
puts " when this script was generated.\n"
puts "\[--project_name <name>\] Create project with the specified name. Default"
puts " name is the name of the project from where this"
puts " script was generated.\n"
puts "\[--help\] Print help information for this script"
puts "\[--overwrite\] Ovewrite the existing project if it exists"
puts "-------------------------------------------------------------------------\n"
exit 0
}
set _force ""
if { $::argc > 0 } {
for {set i 0} {$i < $::argc} {incr i} {
set option [string trim [lindex $::argv $i]]
switch -regexp -- $option {
"--origin_dir" { incr i; set origin_dir [lindex $::argv $i] }
"--project_name" { incr i; set _xil_proj_name_ [lindex $::argv $i] }
"--overwrite" { incr i; set _force "-force"}
"--help" { print_help }
default {
if { [regexp {^-} $option] } {
puts "ERROR: Unknown option '$option' specified, please type '$script_file -tclargs --help' for usage info.\n"
return 1
}
}
}
}
}
# Create project
if { $_force ne "" } {
create_project ${_xil_proj_name_} ${origin_dir}/${_xil_proj_name_} -part xc7z020clg484-1 $_force
} else {
create_project ${_xil_proj_name_} ${origin_dir}/${_xil_proj_name_} -part xc7z020clg484-1
}
# Set the directory path for the new project
set proj_dir [get_property directory [current_project]]
# Reconstruct message rules
# None
# Set project properties
set obj [current_project]
set_property -name "board_part" -value "digilentinc.com:zedboard:part0:1.1" -objects $obj
set_property -name "default_lib" -value "xil_defaultlib" -objects $obj
set_property -name "enable_resource_estimation" -value "0" -objects $obj
set_property -name "enable_vhdl_2008" -value "1" -objects $obj
set_property -name "ip_cache_permissions" -value "read write" -objects $obj
set_property -name "ip_output_repo" -value "$proj_dir/${_xil_proj_name_}.cache/ip" -objects $obj
set_property -name "mem.enable_memory_map_generation" -value "1" -objects $obj
set_property -name "platform.board_id" -value "zedboard" -objects $obj
set_property -name "revised_directory_structure" -value "1" -objects $obj
set_property -name "sim.central_dir" -value "$proj_dir/${_xil_proj_name_}.ip_user_files" -objects $obj
set_property -name "sim.ip.auto_export_scripts" -value "1" -objects $obj
set_property -name "simulator_language" -value "Mixed" -objects $obj
set_property -name "sim_compile_state" -value "1" -objects $obj
# Create 'sources_1' fileset (if not found)
if {[string equal [get_filesets -quiet sources_1] ""]} {
create_fileset -srcset sources_1
}
# Set 'sources_1' fileset object
set obj [get_filesets sources_1]
set files [list \
$uart_mux_file \
]
add_files -norecurse -fileset $obj $files
set_property file_type "VHDL" [get_files $uart_mux_file]
# Set 'sources_1' fileset file properties for remote files
# None
# Set 'sources_1' fileset file properties for local files
# Create 'constrs_1' fileset (if not found)
if {[string equal [get_filesets -quiet constrs_1] ""]} {
create_fileset -constrset constrs_1
}
add_files -fileset constrs_1 -norecurse $constr_file
# Retrieve the file object correctly
set file_obj [get_files -of_objects [get_filesets constrs_1]]
# Set the file type property
set_property -name "file_type" -value "XDC" -objects $file_obj
# Set as target constraints file.
# set_property -name "target_constrs_file" -value $constr_file -objects $file_obj
set_property target_constrs_file $constr_file [current_fileset -constrset]
set_property target_ucf $constr_file [current_fileset -constrset]
# Load block design.
set ret [source $bd_file]
if {$ret != ""} {
error "Failed to generate block design with return value $ret"
}
set design_name [get_bd_designs]
make_wrapper -files [get_files $design_name.bd] -top -import
# Create 'sim_1' fileset (if not found)
if {[string equal [get_filesets -quiet sim_1] ""]} {
create_fileset -simset sim_1
}
# Set 'sim_1' fileset object
set obj [get_filesets sim_1]
# Empty (no sources present)
# Set 'sim_1' fileset properties
set obj [get_filesets sim_1]
set_property -name "top" -value "zedboard_wrapper" -objects $obj
set_property -name "top_lib" -value "xil_defaultlib" -objects $obj
# Set 'utils_1' fileset object
set obj [get_filesets utils_1]
# Set 'utils_1' fileset file properties for remote files
# None
# Set 'utils_1' fileset file properties for local files
# None
# Set 'utils_1' fileset properties
set obj [get_filesets utils_1]
set idrFlowPropertiesConstraints ""
catch {
set idrFlowPropertiesConstraints [get_param runs.disableIDRFlowPropertyConstraints]
set_param runs.disableIDRFlowPropertyConstraints 1
}
# Create 'synth_1' run (if not found)
if {[string equal [get_runs -quiet synth_1] ""]} {
create_run -name synth_1 -part xc7z020clg484-1 -flow {Vivado Synthesis 2024} -strategy "Vivado Synthesis Defaults" -report_strategy {No Reports} -constrset constrs_1
} else {
set_property strategy "Vivado Synthesis Defaults" [get_runs synth_1]
set_property flow "Vivado Synthesis 2024" [get_runs synth_1]
}
set obj [get_runs synth_1]
set_property set_report_strategy_name 1 $obj
set_property report_strategy {Vivado Synthesis Default Reports} $obj
set_property set_report_strategy_name 0 $obj
# Create 'synth_1_synth_report_utilization_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs synth_1] synth_1_synth_report_utilization_0] "" ] } {
create_report_config -report_name synth_1_synth_report_utilization_0 -report_type report_utilization:1.0 -steps synth_design -runs synth_1
}
set obj [get_report_configs -of_objects [get_runs synth_1] synth_1_synth_report_utilization_0]
if { $obj != "" } {
}
set obj [get_runs synth_1]
set_property -name "needs_refresh" -value "1" -objects $obj
set_property -name "auto_incremental_checkpoint" -value "1" -objects $obj
set_property -name "strategy" -value "Vivado Synthesis Defaults" -objects $obj
# set the current synth run
current_run -synthesis [get_runs synth_1]
# Create 'impl_1' run (if not found)
if {[string equal [get_runs -quiet impl_1] ""]} {
create_run -name impl_1 -part xc7z020clg484-1 -flow {Vivado Implementation 2024} -strategy "Vivado Implementation Defaults" -report_strategy {No Reports} -constrset constrs_1 -parent_run synth_1
} else {
set_property strategy "Vivado Implementation Defaults" [get_runs impl_1]
set_property flow "Vivado Implementation 2024" [get_runs impl_1]
}
set obj [get_runs impl_1]
set_property set_report_strategy_name 1 $obj
set_property report_strategy {Vivado Implementation Default Reports} $obj
set_property set_report_strategy_name 0 $obj
# Create 'impl_1_init_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_init_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_init_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps init_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_init_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_opt_report_drc_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_opt_report_drc_0] "" ] } {
create_report_config -report_name impl_1_opt_report_drc_0 -report_type report_drc:1.0 -steps opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_opt_report_drc_0]
if { $obj != "" } {
}
# Create 'impl_1_opt_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_opt_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_opt_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_opt_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_power_opt_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_power_opt_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_power_opt_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps power_opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_power_opt_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_place_report_io_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_io_0] "" ] } {
create_report_config -report_name impl_1_place_report_io_0 -report_type report_io:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_io_0]
if { $obj != "" } {
}
# Create 'impl_1_place_report_utilization_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_utilization_0] "" ] } {
create_report_config -report_name impl_1_place_report_utilization_0 -report_type report_utilization:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_utilization_0]
if { $obj != "" } {
}
# Create 'impl_1_place_report_control_sets_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_control_sets_0] "" ] } {
create_report_config -report_name impl_1_place_report_control_sets_0 -report_type report_control_sets:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_control_sets_0]
if { $obj != "" } {
set_property -name "options.verbose" -value "1" -objects $obj
}
# Create 'impl_1_place_report_incremental_reuse_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_incremental_reuse_0] "" ] } {
create_report_config -report_name impl_1_place_report_incremental_reuse_0 -report_type report_incremental_reuse:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_incremental_reuse_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
}
# Create 'impl_1_place_report_incremental_reuse_1' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_incremental_reuse_1] "" ] } {
create_report_config -report_name impl_1_place_report_incremental_reuse_1 -report_type report_incremental_reuse:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_incremental_reuse_1]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
}
# Create 'impl_1_place_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_place_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps place_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_place_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_post_place_power_opt_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_post_place_power_opt_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_post_place_power_opt_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps post_place_power_opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_post_place_power_opt_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_phys_opt_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_phys_opt_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_phys_opt_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps phys_opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_phys_opt_report_timing_summary_0]
if { $obj != "" } {
set_property -name "is_enabled" -value "0" -objects $obj
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_route_report_drc_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_drc_0] "" ] } {
create_report_config -report_name impl_1_route_report_drc_0 -report_type report_drc:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_drc_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_methodology_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_methodology_0] "" ] } {
create_report_config -report_name impl_1_route_report_methodology_0 -report_type report_methodology:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_methodology_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_power_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_power_0] "" ] } {
create_report_config -report_name impl_1_route_report_power_0 -report_type report_power:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_power_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_route_status_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_route_status_0] "" ] } {
create_report_config -report_name impl_1_route_report_route_status_0 -report_type report_route_status:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_route_status_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_route_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_timing_summary_0]
if { $obj != "" } {
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
}
# Create 'impl_1_route_report_incremental_reuse_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_incremental_reuse_0] "" ] } {
create_report_config -report_name impl_1_route_report_incremental_reuse_0 -report_type report_incremental_reuse:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_incremental_reuse_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_clock_utilization_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_clock_utilization_0] "" ] } {
create_report_config -report_name impl_1_route_report_clock_utilization_0 -report_type report_clock_utilization:1.0 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_clock_utilization_0]
if { $obj != "" } {
}
# Create 'impl_1_route_report_bus_skew_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_bus_skew_0] "" ] } {
create_report_config -report_name impl_1_route_report_bus_skew_0 -report_type report_bus_skew:1.1 -steps route_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_route_report_bus_skew_0]
if { $obj != "" } {
set_property -name "options.warn_on_violation" -value "1" -objects $obj
}
# Create 'impl_1_post_route_phys_opt_report_timing_summary_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_post_route_phys_opt_report_timing_summary_0] "" ] } {
create_report_config -report_name impl_1_post_route_phys_opt_report_timing_summary_0 -report_type report_timing_summary:1.0 -steps post_route_phys_opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_post_route_phys_opt_report_timing_summary_0]
if { $obj != "" } {
set_property -name "options.max_paths" -value "10" -objects $obj
set_property -name "options.report_unconstrained" -value "1" -objects $obj
set_property -name "options.warn_on_violation" -value "1" -objects $obj
}
# Create 'impl_1_post_route_phys_opt_report_bus_skew_0' report (if not found)
if { [ string equal [get_report_configs -of_objects [get_runs impl_1] impl_1_post_route_phys_opt_report_bus_skew_0] "" ] } {
create_report_config -report_name impl_1_post_route_phys_opt_report_bus_skew_0 -report_type report_bus_skew:1.1 -steps post_route_phys_opt_design -runs impl_1
}
set obj [get_report_configs -of_objects [get_runs impl_1] impl_1_post_route_phys_opt_report_bus_skew_0]
if { $obj != "" } {
set_property -name "options.warn_on_violation" -value "1" -objects $obj
}
set obj [get_runs impl_1]
set_property -name "needs_refresh" -value "1" -objects $obj
set_property -name "strategy" -value "Vivado Implementation Defaults" -objects $obj
set_property -name "steps.write_bitstream.args.readback_file" -value "0" -objects $obj
set_property -name "steps.write_bitstream.args.verbose" -value "0" -objects $obj
# set the current impl run
current_run -implementation [get_runs impl_1]
catch {
if { $idrFlowPropertiesConstraints != {} } {
set_param runs.disableIDRFlowPropertyConstraints $idrFlowPropertiesConstraints
}
}
puts "INFO: Project created:${_xil_proj_name_}"
# Create 'drc_1' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "drc_1" ] ] ""]} {
create_dashboard_gadget -name {drc_1} -type drc
}
set obj [get_dashboard_gadgets [ list "drc_1" ] ]
set_property -name "reports" -value "impl_1#impl_1_route_report_drc_0" -objects $obj
# Create 'methodology_1' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "methodology_1" ] ] ""]} {
create_dashboard_gadget -name {methodology_1} -type methodology
}
set obj [get_dashboard_gadgets [ list "methodology_1" ] ]
set_property -name "reports" -value "impl_1#impl_1_route_report_methodology_0" -objects $obj
# Create 'power_1' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "power_1" ] ] ""]} {
create_dashboard_gadget -name {power_1} -type power
}
set obj [get_dashboard_gadgets [ list "power_1" ] ]
set_property -name "reports" -value "impl_1#impl_1_route_report_power_0" -objects $obj
# Create 'timing_1' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "timing_1" ] ] ""]} {
create_dashboard_gadget -name {timing_1} -type timing
}
set obj [get_dashboard_gadgets [ list "timing_1" ] ]
set_property -name "reports" -value "impl_1#impl_1_route_report_timing_summary_0" -objects $obj
# Create 'utilization_1' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "utilization_1" ] ] ""]} {
create_dashboard_gadget -name {utilization_1} -type utilization
}
set obj [get_dashboard_gadgets [ list "utilization_1" ] ]
set_property -name "reports" -value "synth_1#synth_1_synth_report_utilization_0" -objects $obj
set_property -name "run.step" -value "synth_design" -objects $obj
set_property -name "run.type" -value "synthesis" -objects $obj
# Create 'utilization_2' gadget (if not found)
if {[string equal [get_dashboard_gadgets [ list "utilization_2" ] ] ""]} {
create_dashboard_gadget -name {utilization_2} -type utilization
}
set obj [get_dashboard_gadgets [ list "utilization_2" ] ]
set_property -name "reports" -value "impl_1#impl_1_place_report_utilization_0" -objects $obj
move_dashboard_gadget -name {utilization_1} -row 0 -col 0
move_dashboard_gadget -name {power_1} -row 1 -col 0
move_dashboard_gadget -name {drc_1} -row 2 -col 0
move_dashboard_gadget -name {timing_1} -row 0 -col 1
move_dashboard_gadget -name {utilization_2} -row 1 -col 1
move_dashboard_gadget -name {methodology_1} -row 2 -col 1

View File

@ -0,0 +1,19 @@
[package]
name = "zynq7000-embassy"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Embassy-rs support for the Zynq7000 family of SoCs"
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"
keywords = ["no-std", "arm", "embassy", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
critical-section = "1"
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
zynq7000-hal = { path = "../zynq7000-hal" }
embassy-time-driver = { path = "/home/rmueller/Rust/embassy/embassy-time-driver", version = "0.2" }
embassy-time-queue-utils = { path = "/home/rmueller/Rust/embassy/embassy-time-queue-utils", version = "0.1" }

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Robin A. Mueller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,8 @@
# Embassy-rs support for the AMD Zynq7000 SoC family
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
AMD Zynq7000 SoC family. Currently, it contains the time driver to allow using embassy-rs. It
currently provides one driver using the global timer peripheral provided by the Zynq7000 PS for
this purpose.
The documentation contains more information on how to use this crate.

176
zynq7000-embassy/src/lib.rs Normal file
View File

@ -0,0 +1,176 @@
#![no_std]
use core::cell::{Cell, RefCell};
use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::{Driver, TICK_HZ, time_driver_impl};
use embassy_time_queue_utils::Queue;
use once_cell::sync::OnceCell;
use zynq7000_hal::{clocks::ArmClocks, gtc::Gtc, time::Hertz};
static SCALE: OnceCell<u64> = OnceCell::new();
static CPU_3X2X_CLK: OnceCell<Hertz> = OnceCell::new();
struct AlarmState {
timestamp: Cell<u64>,
}
impl AlarmState {
const fn new() -> Self {
Self {
timestamp: Cell::new(u64::MAX),
}
}
}
unsafe impl Send for AlarmState {}
/// This is the initialization method for the embassy time driver.
///
/// It should be called ONCE at system initialization.
pub fn init(arm_clocks: &ArmClocks, gtc: Gtc) {
if SCALE.get().is_some() || CPU_3X2X_CLK.get().is_some() {
return;
}
unsafe { GTC_TIME_DRIVER.init(arm_clocks, gtc) };
}
/// This interrupt handler should be called ONCE in the interrupt handler on global timer
/// interrupts.
///
/// # Safety
///
/// Needs to be called once in the global timer interrupt.
pub unsafe fn on_interrupt() {
unsafe { GTC_TIME_DRIVER.on_interrupt() };
}
pub struct GtcTimerDriver {
gtc: Mutex<RefCell<Gtc>>,
// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarms: Mutex<AlarmState>,
queue: Mutex<RefCell<Queue>>,
}
impl GtcTimerDriver {
/// This is the initialization method for the embassy time driver.
///
/// # Safety
///
/// This has to be called ONCE at system initialization.
pub unsafe fn init(&'static self, arm_clock: &ArmClocks, mut gtc: Gtc) {
CPU_3X2X_CLK.set(arm_clock.cpu_3x2x_clk()).unwrap();
SCALE
.set(arm_clock.cpu_3x2x_clk().raw() as u64 / TICK_HZ)
.unwrap();
gtc.set_cpu_3x2x_clock(arm_clock.cpu_3x2x_clk());
gtc.set_prescaler(0);
gtc.enable();
}
/// Should be called inside the IRQ handler if the IRQ ID is equal to
/// [crate::hal::gic::PpiInterrupt::GlobalTimer].
///
/// # Safety
///
/// This function has to be called once for interrupt ID
/// [crate::hal::gic::PpiInterrupt::GlobalTimer].
pub unsafe fn on_interrupt(&self) {
critical_section::with(|cs| {
self.trigger_alarm(cs);
})
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
if SCALE.get().is_none() {
return false;
}
let mut gtc = self.gtc.borrow(cs).borrow_mut();
let alarm = &self.alarms.borrow(cs);
alarm.timestamp.set(timestamp);
let t = self.now();
if timestamp <= t {
gtc.disable_interrupt();
alarm.timestamp.set(u64::MAX);
return false;
}
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
// the interrupts are enabled or not. When they are enabled at a later point, the
// right value is already set.
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
// is not missed.
//
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
// and we don't do that here.
let safe_timestamp = timestamp.max(t + 3);
let opt_comparator = safe_timestamp.checked_mul(*SCALE.get().unwrap());
if opt_comparator.is_none() {
return true;
}
gtc.set_comparator(opt_comparator.unwrap());
gtc.enable_interrupt();
true
}
fn trigger_alarm(&self, cs: CriticalSection) {
let mut gtc = self.gtc.borrow(cs).borrow_mut();
gtc.disable_interrupt();
drop(gtc);
let alarm = &self.alarms.borrow(cs);
// Setting the maximum value disables the alarm.
alarm.timestamp.set(u64::MAX);
// Call after clearing alarm, so the callback can set another alarm.
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
}
impl Driver for GtcTimerDriver {
#[inline]
fn now(&self) -> u64 {
if SCALE.get().is_none() {
return 0;
}
// This is okay, we only read the GTC and do not re-configure it, avoids the
// need for a lock.
let gtc = unsafe { Gtc::steal_fixed(Some(*CPU_3X2X_CLK.get().unwrap())) };
gtc.read_timer() / SCALE.get().unwrap()
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
})
}
}
time_driver_impl!(
// We assume ownership of the GTC, so it is okay to steal here.
static GTC_TIME_DRIVER: GtcTimerDriver = GtcTimerDriver {
gtc: Mutex::new(RefCell::new(unsafe { Gtc::steal_fixed(None)})),
alarms: Mutex::new(AlarmState::new()),
queue: Mutex::new(RefCell::new(Queue::new())),
});

45
zynq7000-hal/Cargo.toml Normal file
View File

@ -0,0 +1,45 @@
[package]
name = "zynq7000-hal"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "HAL for the Zynq7000 family of SoCs"
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"
keywords = ["no-std", "hal", "amd", "zynq7000", "xilinx", "bare-metal"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-ar = { path = "../../../Rust/cortex-ar/cortex-ar" }
zynq7000 = { path = "../zynq7000" }
arbitrary-int = "1.3"
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-hal = "1"
embedded-hal-async = "1"
heapless = "0.8"
static_cell = "2"
delegate = "0.13"
paste = "1"
nb = "1"
fugit = "0.3"
critical-section = "1"
libm = "0.2"
log = "0.4"
embassy-sync = "0.6"
raw-slice = { git = "https://egit.irs.uni-stuttgart.de/rust/raw-slice.git" }
embedded-io-async = "0.6"
[features]
std = ["thiserror/std", "alloc"]
alloc = []
# These devices have a lower pin count.
7z010-7z007s-clg225 = []
[dev-dependencies]
approx = "0.5"

201
zynq7000-hal/LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
zynq7000-hal/LICENSE-MIT Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Robin A. Mueller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

12
zynq7000-hal/README.md Normal file
View File

@ -0,0 +1,12 @@
# HAL for the AMD Zynq 7000 SoC family
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000).
It is the result of reading the datasheet for the device and encoding a type-safe layer over the
raw PAC. This crate also implements traits specified by the
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
various drivers in the embedded rust ecosystem.
The [top-level README](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs) and the documentation
contain more information on how to use this crate.

466
zynq7000-hal/src/clocks.rs Normal file
View File

@ -0,0 +1,466 @@
//! Clock module.
use arbitrary_int::Number;
use zynq7000::slcr::{
ClockControl,
clocks::{
ClockRatioSelect, DualCommonPeriphIoClkCtrl, FpgaClkControl, GigEthClkCtrl,
SingleCommonPeriphIoClkCtrl,
},
};
use super::time::Hertz;
#[derive(Debug)]
pub struct ArmClocks {
ref_clk: Hertz,
cpu_1x_clk: Hertz,
cpu_2x_clk: Hertz,
cpu_3x2x_clk: Hertz,
cpu_6x4x_clk: Hertz,
}
impl ArmClocks {
/// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
pub const fn cpu_1x_clk(&self) -> Hertz {
self.cpu_1x_clk
}
pub const fn cpu_2x_clk(&self) -> Hertz {
self.cpu_2x_clk
}
pub const fn cpu_3x2x_clk(&self) -> Hertz {
self.cpu_3x2x_clk
}
pub const fn cpu_6x4x_clk(&self) -> Hertz {
self.cpu_6x4x_clk
}
}
#[derive(Debug)]
pub struct DdrClocks {
ref_clk: Hertz,
ddr_3x_clk: Hertz,
ddr_2x_clk: Hertz,
}
impl DdrClocks {
/// 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
}
pub fn ddr_3x_clk(&self) -> Hertz {
self.ddr_3x_clk
}
pub fn ddr_2x_clk(&self) -> Hertz {
self.ddr_2x_clk
}
}
#[derive(Debug)]
pub struct IoClocks {
/// Reference clock provided by IO PLL which is used to calculate all other clock frequencies.
ref_clk: Hertz,
smc_clk: Hertz,
qspi_clk: Hertz,
sdio_clk: Hertz,
uart_clk: Hertz,
spi_clk: Hertz,
can_clk: Hertz,
pcap_2x_clk: Hertz,
trace_clk: Option<Hertz>,
}
impl IoClocks {
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
pub const fn smc_clk(&self) -> Hertz {
self.smc_clk
}
pub fn update_smc_clk(&mut self, clk: Hertz) {
self.smc_clk = clk
}
pub const fn qspi_clk(&self) -> Hertz {
self.qspi_clk
}
pub fn update_qspi_clk(&mut self, clk: Hertz) {
self.qspi_clk = clk
}
pub const fn sdio_clk(&self) -> Hertz {
self.sdio_clk
}
pub fn update_sdio_clk(&mut self, clk: Hertz) {
self.sdio_clk = clk
}
pub const fn uart_clk(&self) -> Hertz {
self.uart_clk
}
pub fn update_uart_clk(&mut self, clk: Hertz) {
self.uart_clk = clk
}
pub const fn spi_clk(&self) -> Hertz {
self.spi_clk
}
pub fn update_spi_clk(&mut self, clk: Hertz) {
self.spi_clk = clk
}
pub fn can_clk(&self) -> Hertz {
self.can_clk
}
pub fn update_can_clk(&mut self, clk: Hertz) {
self.can_clk = clk
}
pub fn pcap_2x_clk(&self) -> Hertz {
self.pcap_2x_clk
}
pub fn update_pcap_2x_clk(&mut self, clk: Hertz) {
self.pcap_2x_clk = clk
}
/// Returns [None] if the trace clock is configured to use the EMIO trace clock.
pub fn trace_clk(&self) -> Option<Hertz> {
self.trace_clk
}
}
#[derive(Debug)]
pub struct Clocks {
ps_clk: Hertz,
arm_pll_out: Hertz,
io_pll_out: Hertz,
ddr_pll_out: Hertz,
arm: ArmClocks,
ddr: DdrClocks,
io: IoClocks,
pl: [Hertz; 4],
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ClockModuleId {
Ddr,
Arm,
Smc,
Qspi,
Sdio,
Uart,
Spi,
Pcap,
Can,
Fpga,
Trace,
Gem0,
Gem1,
}
#[derive(Debug)]
pub struct DivisorZero(pub ClockModuleId);
#[derive(Debug)]
pub enum ClockReadError {
/// The feedback value for the PLL clock output calculation is zero.
PllFeedbackZero,
/// Detected a divisor of zero.
DivisorZero(DivisorZero),
/// Detected a divisor that is not even.
DivisorNotEven,
}
impl Clocks {
/// Processing system clock, which is generally dependent on the board and the used crystal.
pub fn ps_clk(&self) -> Hertz {
self.ps_clk
}
/// This generates the clock configuration by reading the SLCR clock registers.
///
/// It assumes that the clock already has been configured, for example by a first-stage
/// bootloader, or the PS7 initialization script.
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();
if arm_pll_cfg.fdiv().as_u32() == 0
|| io_pll_cfg.fdiv().as_u32() == 0
|| ddr_pll_cfg.fdiv().as_u32() == 0
{
return Err(ClockReadError::PllFeedbackZero);
}
let arm_pll_out = ps_clk_freq * arm_pll_cfg.fdiv().into();
let io_pll_out = ps_clk_freq * io_pll_cfg.fdiv().into();
let ddr_pll_out = ps_clk_freq * ddr_pll_cfg.fdiv().into();
let arm_clk_ctrl = clk_regs.read_arm_clk_ctrl();
let arm_base_clk = match arm_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelArm::ArmPll
| zynq7000::slcr::clocks::SrcSelArm::ArmPllAlt => arm_pll_out,
zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
};
let clk_sel = clk_regs.read_clk_621_true();
if arm_clk_ctrl.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
}
let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
let arm_clks = match clk_sel.sel() {
ClockRatioSelect::FourToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 4,
cpu_2x_clk: arm_clk_divided / 2,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
},
ClockRatioSelect::SixToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 6,
cpu_2x_clk: arm_clk_divided / 3,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
},
};
let ddr_clk_ctrl = clk_regs.read_ddr_clk_ctrl();
if ddr_clk_ctrl.div_3x_clk().as_u32() == 0 || ddr_clk_ctrl.div_2x_clk().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Ddr)));
}
let ddr_clks = DdrClocks {
ref_clk: ddr_pll_out,
ddr_3x_clk: ddr_pll_out / ddr_clk_ctrl.div_3x_clk().as_u32(),
ddr_2x_clk: ddr_pll_out / ddr_clk_ctrl.div_2x_clk().as_u32(),
};
let handle_common_single_clock_config = |single_block: SingleCommonPeriphIoClkCtrl,
id: ClockModuleId|
-> Result<Hertz, ClockReadError> {
if single_block.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(id)));
}
Ok(match single_block.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out / single_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out / single_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out / single_block.divisor().as_u32()
}
})
};
let handle_common_dual_clock_config = |dual_block: DualCommonPeriphIoClkCtrl,
id: ClockModuleId|
-> Result<Hertz, ClockReadError> {
if dual_block.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(id)));
}
Ok(match dual_block.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out / dual_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out / dual_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out / dual_block.divisor().as_u32()
}
})
};
let smc_clk =
handle_common_single_clock_config(clk_regs.read_smc_clk_ctrl(), ClockModuleId::Smc)?;
let qspi_clk =
handle_common_single_clock_config(clk_regs.read_lqspi_clk_ctrl(), ClockModuleId::Qspi)?;
let sdio_clk =
handle_common_dual_clock_config(clk_regs.read_sdio_clk_ctrl(), ClockModuleId::Sdio)?;
let uart_clk =
handle_common_dual_clock_config(clk_regs.read_uart_clk_ctrl(), ClockModuleId::Uart)?;
let spi_clk =
handle_common_dual_clock_config(clk_regs.read_spi_clk_ctrl(), ClockModuleId::Spi)?;
let pcap_2x_clk =
handle_common_single_clock_config(clk_regs.read_pcap_clk_ctrl(), ClockModuleId::Pcap)?;
let can_clk_ctrl = clk_regs.read_can_clk_ctrl();
let can_clk_ref_clk = match can_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => io_pll_out,
zynq7000::slcr::clocks::SrcSelIo::ArmPll => arm_pll_out,
zynq7000::slcr::clocks::SrcSelIo::DdrPll => ddr_pll_out,
};
if can_clk_ctrl.divisor_0().as_u32() == 0 || can_clk_ctrl.divisor_1().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Can)));
}
let can_clk =
can_clk_ref_clk / can_clk_ctrl.divisor_0().as_u32() / can_clk_ctrl.divisor_1().as_u32();
let trace_clk_ctrl = clk_regs.read_dbg_clk_ctrl();
if trace_clk_ctrl.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(
ClockModuleId::Trace,
)));
}
let trace_clk = match trace_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelTpiu::IoPll
| zynq7000::slcr::clocks::SrcSelTpiu::IoPllAlt => {
Some(io_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::ArmPll => {
Some(arm_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::DdrPll => {
Some(ddr_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClk
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt0
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt1
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt2 => None,
};
let calculate_fpga_clk = |fpga_clk_ctrl: FpgaClkControl| -> Result<Hertz, ClockReadError> {
if fpga_clk_ctrl.divisor_0().as_u32() == 0 || fpga_clk_ctrl.divisor_1().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(
ClockModuleId::Fpga,
)));
}
Ok(match fpga_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
})
};
Ok(Self {
ps_clk: ps_clk_freq,
io_pll_out,
ddr_pll_out,
arm_pll_out,
arm: arm_clks,
ddr: ddr_clks,
io: IoClocks {
ref_clk: io_pll_out,
smc_clk,
qspi_clk,
sdio_clk,
uart_clk,
spi_clk,
can_clk,
pcap_2x_clk,
trace_clk,
},
// TODO: There should be a mut and a non-mut getter for an inner block. We only do pure
// reads with the inner block here.
pl: [
calculate_fpga_clk(clk_regs.fpga_0_clk_ctrl().read_clk_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_1_clk_ctrl().read_clk_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_2_clk_ctrl().read_clk_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_3_clk_ctrl().read_clk_ctrl())?,
],
})
}
pub fn arm_clocks(&self) -> &ArmClocks {
&self.arm
}
pub fn ddr_clocks(&self) -> &DdrClocks {
&self.ddr
}
pub fn io_clocks(&self) -> &IoClocks {
&self.io
}
pub fn io_clocks_mut(&mut self) -> &mut IoClocks {
&mut self.io
}
/// Programmable Logic (PL) FCLK clocks.
pub fn pl_clocks(&self) -> &[Hertz; 4] {
&self.pl
}
fn calculate_gem_ref_clock(
&self,
reg: GigEthClkCtrl,
module: ClockModuleId,
) -> Result<Hertz, DivisorZero> {
let source_clk = match reg.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => self.io_pll_out,
zynq7000::slcr::clocks::SrcSelIo::ArmPll => self.arm_pll_out,
zynq7000::slcr::clocks::SrcSelIo::DdrPll => self.ddr_pll_out,
};
let div0 = reg.divisor_0().as_u32();
if div0 == 0 {
return Err(DivisorZero(module));
}
let div1 = reg.divisor_1().as_u32();
if div1 == 0 {
return Err(DivisorZero(module));
}
Ok(source_clk / reg.divisor_0().as_u32() / reg.divisor_1().as_u32())
}
/// Calculate the reference clock for GEM0.
///
/// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
/// configuration before calling this function.
///
/// It should be noted that the GEM has a separate TX and RX clock.
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
pub fn calculate_gem_0_ref_clock(&self) -> Result<Hertz, DivisorZero> {
let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem0)
}
/// Calculate the reference clock for GEM1.
///
/// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
/// configuration before calling this function.
///
/// It should be noted that the GEM has a separate TX and RX clock.
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
pub fn calculate_gem_1_ref_clock(&self) -> Result<Hertz, DivisorZero> {
let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem1)
}
}

528
zynq7000-hal/src/gic.rs Normal file
View File

@ -0,0 +1,528 @@
//! Global Interrupt Controller (GIC) module.
//!
//! The primary interface to configure and allow handling the interrupts are the
//! [GicConfigurator] and the [GicInterruptHelper] structures.
//!
//! # 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 cortex_ar::interrupt;
use zynq7000::gic::{
Dcr, Gicc, Gicd, Icr, InterruptSignalRegister, MmioGicc, MmioGicd, PriorityRegister,
};
const SPURIOUS_INTERRUPT_ID: u32 = 1023;
pub const HIGHEST_PRIORITY: u8 = 0;
pub const LOWEST_PRIORITY: u8 = 31;
/// These fixed values must be programmed according to the Zynq7000 TRM p.236.
/// Configures #32 to #47.
pub const ICFR_2_FIXED_VALUE: u32 = 0b01010101010111010101010001011111;
/// These fixed values must be programmed according to the Zynq7000 TRM p.236.
/// This configures `PL[2:0]` to high-level sensitivity.
/// Configures #48 to #63.
pub const ICFR_3_FIXED_VALUE: u32 = 0b01010101010101011101010101010101;
/// These fixed values must be programmed according to the Zynq7000 TRM p.236.
/// This configures `PL[7:3]` to high-level sensitivity.
/// Configures #64 to #79.
pub const ICFR_4_FIXED_VALUE: u32 = 0b01110101010101010101010101010101;
/// These fixed values must be programmed according to the Zynq7000 TRM p.236.
/// This configures `PL[15:8]` to high-level sensitivity.
/// Configures #80 to #95.
pub const ICFR_5_FIXED_VALUE: u32 = 0b00000011010101010101010101010101;
/// Helper value to target all interrupts which can be targetted to CPU 0
pub const TARGETS_ALL_CPU_0_IPTR_VAL: u32 = 0x01010101;
/// Helper value to target all interrupts which can be targetted to CPU 1
pub const TARGETS_ALL_CPU_1_IPTR_VAL: u32 = 0x02020202;
pub const ACTIVATE_ALL_SGIS_MASK_ISER: u32 = 0x0000_FFFF;
pub const ACTIVATE_ALL_PPIS_MASK_ISER: u32 = 0xF800_0000;
pub enum SpiSensitivity {
Level = 0b01,
Edge = 0b11,
}
pub enum TargetCpu {
None = 0b00,
Cpu0 = 0b01,
Cpu1 = 0b10,
Both = 0b11,
}
/// Private Peripheral Interrupt (PPI) which are private to the CPU.
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum PpiInterrupt {
GlobalTimer = 27,
// Interrupt signal from the PL. CPU0: `IRQF2P[18]` and CPU1: `IRQF2P[19]`
NFiq = 28,
CpuPrivateTimer = 29,
/// AWDT0 and AWDT1 for each CPU.
Awdt = 30,
// Interrupt signal from the PL. CPU0: `IRQF2P[16]` and CPU1: `IRQF2P[17]`
NIrq = 31,
}
/// Shared Peripheral Interrupt IDs.
#[derive(Debug, Eq, PartialEq, Clone, Copy, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum SpiInterrupt {
Cpu0 = 32,
Cpu1 = 33,
L2Cache = 34,
Ocm = 35,
_Reserved0 = 36,
Pmu0 = 37,
Pmu1 = 38,
Xadc = 39,
DevC = 40,
Swdt = 41,
Ttc00 = 42,
Ttc01 = 43,
Ttc02 = 44,
DmacAbort = 45,
Dmac0 = 46,
Dmac1 = 47,
Dmac2 = 48,
Dmac3 = 49,
Smc = 50,
Qspi = 51,
Gpio = 52,
Usb0 = 53,
Eth0 = 54,
Eth0Wakeup = 55,
Sdio0 = 56,
I2c0 = 57,
Spi0 = 58,
Uart0 = 59,
Can0 = 60,
Pl0 = 61,
Pl1 = 62,
Pl2 = 63,
Pl3 = 64,
Pl4 = 65,
Pl5 = 66,
Pl6 = 67,
Pl7 = 68,
Ttc10 = 69,
Ttc11 = 70,
Ttc12 = 71,
Dmac4 = 72,
Dmac5 = 73,
Dmac6 = 74,
Dmac7 = 75,
Usb1 = 76,
Eth1 = 77,
Eth1Wakeup = 78,
Sdio1 = 79,
I2c1 = 80,
Spi1 = 81,
Uart1 = 82,
Can1 = 83,
Pl8 = 84,
Pl9 = 85,
Pl10 = 86,
Pl11 = 87,
Pl12 = 88,
Pl13 = 89,
Pl14 = 90,
Pl15 = 91,
ScuParity = 92,
}
/// Interrupt ID wrapper.
#[derive(Debug, Clone, Copy)]
pub enum Interrupt {
Sgi(usize),
Ppi(PpiInterrupt),
Spi(SpiInterrupt),
/// Detects an invalid interrupt ID.
Invalid(usize),
/// Spurious interrupt (ID# 1023).
Spurious,
}
#[derive(Debug)]
pub struct InterruptInfo {
raw_reg: InterruptSignalRegister,
interrupt: Interrupt,
cpu_id: u8,
}
impl InterruptInfo {
pub fn raw_reg(&self) -> InterruptSignalRegister {
self.raw_reg
}
pub fn cpu_id(&self) -> u8 {
self.cpu_id
}
pub fn interrupt(&self) -> Interrupt {
self.interrupt
}
}
#[derive(Debug, thiserror::Error)]
#[error("Invalid priority value {0}, range is [0, 31]")]
pub struct InvalidPriorityValue(pub u8);
#[derive(Debug, thiserror::Error)]
#[error("Invalid PL interrupt ID {0}")]
pub struct InvalidPlInterruptId(pub usize);
/// Invalid Shared Peripheral Interrupt (SPI) ID.
#[derive(Debug, thiserror::Error)]
#[error("Invalid SPI interrupt ID {0}")]
pub struct InvalidSpiInterruptId(pub usize);
/// Invalid Software Generated Interrupt (SGI) ID.
#[derive(Debug, thiserror::Error)]
#[error("Invalid SGI interrupt ID {0}")]
pub struct InvalidSgiInterruptId(pub usize);
/// Higher-level GIC controller for the Zynq70000 SoC.
///
/// The flow of using this controller is as follows:
///
/// 1. Create the controller using [Self::new_with_init]. You can use the [zynq7000::PsPeripherals]
/// structure or the [zynq7000::gic::Gicc::new_mmio] and [zynq7000::gic::Gicd::new_mmio]
/// functions to create the MMIO instances. The constructor configures all PL interrupts
/// sensivities to high-level sensitivity and configures all sensitivities which are expected
/// to have a certain value. It also sets the priority mask to 0xff by calling
/// [Self::set_priority_mask] to prevent masking of the interrupts.
/// 2. Perform the configuration of the interrupt targets and the interrupt sensitivities.
/// The CPU targets are encoded with [TargetCpu] while the sensitivities are encoded by
/// the [SpiSensitivity] enum. You can use the following (helper) API to configure the
/// interrupts:
///
/// - [Self::set_spi_interrupt_cpu_target]
/// - [Self::set_all_spi_interrupt_targets_cpu0]
/// - [Self::set_pl_interrupt_sensitivity]
///
/// 3. Enable all required interrupts. The following API can be used for this:
///
/// - [Self::enable_sgi_interrupt]
/// - [Self::enable_ppi_interrupt]
/// - [Self::enable_spi_interrupt]
/// - [Self::enable_all_spi_interrupts]
/// - [Self::enable_all_ppi_interrupts]
/// - [Self::enable_all_sgi_interrupts]
/// - [Self::enable_all_interrupts]
///
/// You might also chose to enable these interrupts at run-time after the GIC was started.
/// 4. Start the GIC by calling [Self::update_ctrl_regs] with the required settings or
/// with [Self::enable] which assumes a certain configuration.
/// 5. Enable interrupts for the Cortex-AR core by calling [Self::enable_interrupts].
///
/// For the handling of the interrupts, you can use the [GicInterruptHelper] which assumes a
/// properly configured GIC.
pub struct GicConfigurator {
pub gicc: MmioGicc<'static>,
pub gicd: MmioGicd<'static>,
}
impl GicConfigurator {
/// Create a new GIC controller instance and calls [Self::initialize] to perform
/// strongly recommended initialization routines for the GIC.
#[inline]
pub fn new_with_init(gicc: MmioGicc<'static>, gicd: MmioGicd<'static>) -> Self {
let mut gic = GicConfigurator { gicc, gicd };
gic.initialize();
gic
}
/// Create a new GIC controller instance without performing any initialization routines.
///
/// # Safety
///
/// This creates the GIC without performing any of the initialization routines necessary
/// for proper operation. It also circumvents ownership checks. It is mainly intended to be
/// used inside the interrupt handler.
#[inline]
pub unsafe fn steal() -> Self {
GicConfigurator {
gicc: unsafe { Gicc::new_mmio_fixed() },
gicd: unsafe { Gicd::new_mmio_fixed() },
}
}
/// Sets up the GIC by configuring the required sensitivites for the shared peripheral
/// interrupts.
///
/// With a few exeception, the GIC expects software to set up the sensitivities
/// to fixed values. The only exceptions are the interupts coming from the programmable
/// logic. These are configured to high level sensitivity by this function.
/// If you need a different sensitivity, you need to update the bits using the
/// [Self::set_pl_interrupt_sensitivity] function.
#[inline]
pub fn initialize(&mut self) {
self.gicd.write_icfr_2_spi(ICFR_2_FIXED_VALUE);
self.gicd.write_icfr_3_spi(ICFR_3_FIXED_VALUE);
self.gicd.write_icfr_4_spi(ICFR_4_FIXED_VALUE);
self.gicd.write_icfr_5_spi(ICFR_5_FIXED_VALUE);
self.set_priority_mask(0xff);
}
/// Set the priority mask for the CPU.
///
/// Only interrupts with a higher priority than the mask will be accepted.
/// A lower numerical number means a higher priority. This means that the reset value 0x0
/// will mask all interrupts to the CPU while 0xff will unmask all interrupts.
///
/// Please note that the highest priority mask will NOT be necessarily the number of priority
/// levels: The IPRn register always sets the priority level number to the upper bits of the
/// 8-bit bitfield. See p.83 of the ARM GICv1 architecture specification.
pub fn set_priority_mask(&mut self, mask: u8) {
self.gicc
.write_pmr(PriorityRegister::new_with_raw_value(mask as u32));
}
/// Set the sensitivity of a the Programmable Logic SPI interrupts.
///
/// These are the only interrupt IDs which are configurable for SPI. They are set
/// to high-level sensitivity by default by the [Self::initialize] function. You can
/// use this method to override certain sensitivies.
#[inline]
pub fn set_pl_interrupt_sensitivity(
&mut self,
pl_int_id: usize,
sensitivity: SpiSensitivity,
) -> Result<(), InvalidPlInterruptId> {
if pl_int_id >= 16 {
return Err(InvalidPlInterruptId(pl_int_id));
}
match pl_int_id {
0..=2 => {
let pos = 26 + (pl_int_id * 2);
let mask = 0b11 << pos;
self.gicd
.modify_icfr_3_spi(|v| (v & !mask) | ((sensitivity as u32) << pos));
}
3..=7 => {
let pos = pl_int_id * 2;
let mask = 0b11 << pos;
self.gicd
.modify_icfr_4_spi(|v| (v & !mask) | ((sensitivity as u32) << pos));
}
8..=15 => {
let pos = 8 + (pl_int_id * 2);
let mask = 0b11 << pos;
self.gicd
.modify_icfr_5_spi(|v| (v & !mask) | ((sensitivity as u32) << pos));
}
_ => unreachable!(),
}
Ok(())
}
/// Set the CPU target for a SPI interrupt.
///
/// See [Self::set_all_spi_interrupt_targets_cpu0] for a utility method to handle all
/// interrupts with one core.
#[inline]
pub fn set_spi_interrupt_cpu_target(&mut self, spi_int: SpiInterrupt, target: TargetCpu) {
let spi_int_raw = spi_int as u32;
let spi_offset_to_0 = spi_int_raw as usize - 32;
// Unwrap okay, calculated index is always valid.
self.gicd
.write_iptr_spi(
spi_offset_to_0 / 4,
(target as u32) << ((spi_offset_to_0 % 4) * 8),
)
.unwrap();
}
/// Utility function to set all SGI interrupt targets to CPU0.
///
/// This is useful if only CPU0 is active in a system, or if CPU0 handles most interrupts in
/// the system.
#[inline]
pub fn set_all_spi_interrupt_targets_cpu0(&mut self) {
for i in 0..0x10 {
self.gicd
.write_iptr_spi(i, TARGETS_ALL_CPU_0_IPTR_VAL)
.unwrap();
}
}
#[inline]
pub fn enable_sgi_interrupt(&mut self, int_id: usize) -> Result<(), InvalidSpiInterruptId> {
if int_id >= 16 {
return Err(InvalidSpiInterruptId(int_id));
}
unsafe { self.gicd.write_iser_unchecked(0, 1 << int_id) };
Ok(())
}
#[inline]
pub fn enable_all_sgi_interrupts(&mut self) {
// Unwrap okay, index is valid.
self.gicd
.modify_iser(0, |mut v| {
v |= ACTIVATE_ALL_SGIS_MASK_ISER;
v
})
.unwrap();
}
#[inline]
pub fn enable_ppi_interrupt(&mut self, ppi_int: PpiInterrupt) {
// Unwrap okay, index is valid.
self.gicd
.modify_iser(0, |mut v| {
v |= 1 << (ppi_int as u32);
v
})
.unwrap();
}
#[inline]
pub fn enable_all_ppi_interrupts(&mut self) {
unsafe {
self.gicd.modify_iser_unchecked(0, |mut v| {
v |= ACTIVATE_ALL_PPIS_MASK_ISER;
v
})
};
}
#[inline]
pub fn enable_spi_interrupt(&mut self, spi_int: SpiInterrupt) {
let spi_int_raw = spi_int as u32;
match spi_int_raw {
32..=63 => {
let bit_pos = spi_int_raw - 32;
// Unwrap okay, valid index.
self.gicd.write_iser(1, 1 << bit_pos).unwrap();
}
64..=92 => {
let bit_pos = spi_int_raw - 64;
// Unwrap okay, valid index.
self.gicd.write_iser(2, 1 << bit_pos).unwrap();
}
_ => unreachable!(),
}
}
#[inline]
pub fn enable_all_spi_interrupts(&mut self) {
self.gicd.write_iser(1, 0xFFFF_FFFF).unwrap();
self.gicd.write_iser(2, 0xFFFF_FFFF).unwrap();
}
/// Enables all interrupts by calling [Self::enable_all_sgi_interrupts],
/// [Self::enable_all_ppi_interrupts] and [Self::enable_all_spi_interrupts].
pub fn enable_all_interrupts(&mut self) {
self.enable_all_sgi_interrupts();
self.enable_all_ppi_interrupts();
self.enable_all_spi_interrupts();
}
/// Enable the GIC assuming a possibly non-secure configuration.
///
/// This function will NOT configure and enable the various interrupt sources. You need to
/// set the interrupt sensitivities and targets before calling this function.
/// This function configured the control registers with the following settings:
///
/// - CPU interface: Secure and non-secure interrupts are enabled. SBPR, FIQen and AckCtrl
/// fields set to default value 0.
/// - Distributor interface: Both non-secure and secure interrupt distribution enabled.
///
/// It calls [Self::update_ctrl_regs] to update the control registers.
/// If you need custom settings, you can call [Self::update_ctrl_regs] with your required
/// settings.
///
/// This will not enable the interrupt exception for the Cortex-AR core. You might also have
/// to call [Self::enable_interrupts] for interrupts to work.
pub fn enable(&mut self) {
self.update_ctrl_regs(
Icr::builder()
.with_sbpr(false)
.with_fiq_en(false)
.with_ack_ctrl(false)
.with_enable_non_secure(true)
.with_enable_secure(true)
.build(),
Dcr::builder()
.with_enable_non_secure(true)
.with_enable_secure(true)
.build(),
);
}
/// Enable the regular interrupt exceprion for the Cortex-A core by calling the
/// [interrupt::enable] function. You also need to [Self::enable] the GIC for interrupts to
/// work.
///
/// # Safety
///
/// Do not call this in a critical section.
pub unsafe fn enable_interrupts(&self) {
unsafe {
interrupt::enable();
}
}
/// Enable the interrupts for the Cortex-A core by calling the [interrupt::enable] module.
pub fn disable_interrupts(&self) {
interrupt::disable();
}
/// Update the control registers which control the safety configuration and which also enable
/// the GIC.
pub fn update_ctrl_regs(&mut self, icr: Icr, dcr: Dcr) {
self.gicc.write_icr(icr);
self.gicd.write_dcr(dcr);
}
}
/// Helper structure which should only be used inside the interrupt handler once the GIC has
/// been configured with the [GicConfigurator].
pub struct GicInterruptHelper(MmioGicc<'static>);
impl GicInterruptHelper {
/// Create the interrupt helper with the fixed GICC MMIO instance.
pub const fn new() -> Self {
GicInterruptHelper(unsafe { Gicc::new_mmio_fixed() })
}
/// Acknowledges an interrupt by reading the IAR register and returning the interrupt context
/// information structure.
///
/// This should be called at the start of an interrupt handler.
pub fn acknowledge_interrupt(&mut self) -> InterruptInfo {
let iar = self.0.read_iar();
let int_id = iar.ack_int_id().as_u32();
let interrupt = match int_id {
0..=15 => Interrupt::Sgi(int_id as usize),
27..=31 => Interrupt::Ppi(PpiInterrupt::try_from(int_id as u8).unwrap()),
32..=92 => Interrupt::Spi(SpiInterrupt::try_from(int_id as u8).unwrap()),
SPURIOUS_INTERRUPT_ID => Interrupt::Spurious,
_ => Interrupt::Invalid(int_id as usize),
};
InterruptInfo {
interrupt,
cpu_id: iar.cpu_id().as_u8(),
raw_reg: iar,
}
}
/// Acknowledges the end of an interrupt by writing the EOIR register of the GICC.
///
/// This should be called at the end of an interrupt handler.
pub fn end_of_interrupt(&mut self, irq_info: InterruptInfo) {
self.0.write_eoir(irq_info.raw_reg())
}
}
impl Default for GicInterruptHelper {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,420 @@
//! Dynamic MIO pin module.
//!
//! This provides the type-erased MIO pin support.
use arbitrary_int::{u2, u3};
use zynq7000::gpio::{Gpio, MaskedOutput, MmioGpio};
pub use crate::gpio::PinState;
use crate::slcr::Slcr;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct MuxConf {
l3: u3,
l2: u2,
l1: bool,
l0: bool,
}
impl From<zynq7000::slcr::mio::Config> for MuxConf {
fn from(value: zynq7000::slcr::mio::Config) -> Self {
Self::new(
value.l0_sel(),
value.l1_sel(),
value.l2_sel(),
value.l3_sel(),
)
}
}
impl MuxConf {
#[inline]
pub const fn new(l0: bool, l1: bool, l2: u2, l3: u3) -> Self {
Self { l3, l2, l1, l0 }
}
pub const fn new_with_l3(l3: u3) -> Self {
Self::new(false, false, u2::new(0b00), l3)
}
pub const fn new_for_gpio() -> Self {
Self::new(false, false, u2::new(0), u3::new(0))
}
#[inline]
pub const fn l0_sel(&self) -> bool {
self.l0
}
#[inline]
pub const fn l1_sel(&self) -> bool {
self.l1
}
#[inline]
pub const fn l2_sel(&self) -> u2 {
self.l2
}
#[inline]
pub const fn l3_sel(&self) -> u3 {
self.l3
}
}
#[derive(Debug, Copy, Clone)]
pub enum DynPinModeMio {
Output,
InputFloating,
InputPullUp,
IoPeriph(MuxConf),
}
#[derive(Debug, thiserror::Error)]
#[error("invalid pin mode for MIO pin: {0:?}")]
pub struct InvalidPinModeMio(pub DynPinModeMio);
impl embedded_hal::digital::Error for InvalidPinModeMio {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}
pub trait MioPinProvider {
fn mode(&self) -> &DynPinModeMio;
fn offset(&self) -> usize;
#[inline]
fn is_input(&self) -> bool {
matches!(
self.mode(),
DynPinModeMio::InputFloating | DynPinModeMio::InputPullUp
)
}
#[inline]
fn is_output(&self) -> bool {
matches!(self.mode(), DynPinModeMio::Output)
}
#[inline]
fn is_io_periph(&self) -> bool {
matches!(self.mode(), DynPinModeMio::IoPeriph(_))
}
}
pub struct DynMioPin {
offset: usize,
dyn_mode: DynPinModeMio,
regs: MmioGpio<'static>,
}
impl MioPinProvider for DynMioPin {
#[inline]
fn mode(&self) -> &DynPinModeMio {
&self.dyn_mode
}
#[inline]
fn offset(&self) -> usize {
self.offset
}
}
#[derive(Debug, thiserror::Error)]
#[error("MIO pins 7 and 8 can only be output pins")]
pub struct PinIsOutputOnly;
impl DynMioPin {
#[inline]
pub(crate) const fn new(offset: usize, dyn_mode: DynPinModeMio) -> Self {
Self {
offset,
dyn_mode,
regs: unsafe { Gpio::new_mmio_fixed() },
}
}
/// Convert the pin into an output pin.
pub fn into_output(&mut self, init_level: PinState) {
self.dyn_mode = DynPinModeMio::Output;
// The Pullup config does not matter for Output. Tri-state bit must be 0 for the
// output driver to work.
self.reconfigure_slcr_mio_cfg(false, None, Some(MuxConf::new_for_gpio()));
match self.offset {
0..=31 => {
self.regs.bank_0().modify_dirm(|v| v | (1 << self.offset));
self.regs.bank_0().modify_out_en(|v| v | (1 << self.offset));
}
32..=53 => {
self.regs
.bank_1()
.modify_dirm(|v| v | 1 << (self.offset - 32));
self.regs
.bank_1()
.modify_out_en(|v| v | 1 << (self.offset - 32));
}
_ => panic!("invalid GPIO pin offset"),
}
// Unwrap okay, just set mode.
self.write_state(self.offset, init_level).unwrap();
}
/// Convert the pin into a floating input pin.
pub fn into_input_floating(&mut self) -> Result<(), PinIsOutputOnly> {
if self.offset == 7 || self.offset == 8 {
return Err(PinIsOutputOnly);
}
self.reconfigure_slcr_mio_cfg(true, Some(false), Some(MuxConf::new_for_gpio()));
self.configure_input_pin();
self.dyn_mode = DynPinModeMio::InputFloating;
Ok(())
}
/// Convert the pin into an input pin with a pull up.
pub fn into_input_pull_up(&mut self) -> Result<(), PinIsOutputOnly> {
if self.offset == 7 || self.offset == 8 {
return Err(PinIsOutputOnly);
}
self.reconfigure_slcr_mio_cfg(true, Some(true), Some(MuxConf::new_for_gpio()));
self.configure_input_pin();
self.dyn_mode = DynPinModeMio::InputPullUp;
Ok(())
}
/// Convert the pin into an IO peripheral pin.
pub fn into_io_periph_pin(&mut self, mux_conf: MuxConf, pullup: Option<bool>) {
self.reconfigure_slcr_mio_cfg(false, pullup, Some(mux_conf));
self.dyn_mode = DynPinModeMio::IoPeriph(mux_conf);
}
pub fn is_low(&self) -> Result<bool, InvalidPinModeMio> {
if !self.is_input() {
return Err(InvalidPinModeMio(self.dyn_mode));
}
Ok(self.is_low_unchecked())
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn is_low_unchecked(&self) -> bool {
match self.offset {
0..=31 => ((self.regs.read_in_0() >> self.offset) & 0b1) == 0,
32..=53 => ((self.regs.read_in_1() >> (self.offset - 32)) & 0b1) == 0,
_ => panic!("invalid GPIO pin offset"),
}
}
#[inline]
pub fn is_high(&self) -> Result<bool, InvalidPinModeMio> {
self.is_low().map(|v| !v)
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn is_high_unchecked(&self) -> bool {
!self.is_low_unchecked()
}
#[inline]
pub fn is_set_low(&self) -> Result<bool, InvalidPinModeMio> {
if !self.is_output() {
return Err(InvalidPinModeMio(self.dyn_mode));
}
Ok(self.is_set_low_unchecked())
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn is_set_low_unchecked(&self) -> bool {
match self.offset {
0..=31 => ((self.regs.read_out_0() >> self.offset) & 0b1) == 0,
32..=53 => ((self.regs.read_out_1() >> (self.offset - 32)) & 0b1) == 0,
_ => panic!("invalid GPIO pin offset"),
}
}
#[inline]
pub fn is_set_high(&self) -> Result<bool, InvalidPinModeMio> {
self.is_set_low().map(|v| !v)
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn is_set_high_unchecked(&self) -> bool {
!self.is_set_low_unchecked()
}
#[inline]
pub fn set_low(&mut self) -> Result<(), InvalidPinModeMio> {
self.write_state(self.offset, PinState::Low)
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn set_low_unchecked(&mut self) {
self.write_state_unchecked(self.offset, PinState::Low)
}
#[inline]
pub fn set_high(&mut self) -> Result<(), InvalidPinModeMio> {
self.write_state(self.offset, PinState::High)
}
/// This variant does not check whether the [DynPinModeMio] is correct.
#[inline]
pub fn set_high_unchecked(&mut self) {
self.write_state_unchecked(self.offset, PinState::High)
}
#[inline(always)]
fn offset_to_masked_out_ptr_and_ptr_offset(
&mut self,
offset: usize,
) -> (usize, *mut MaskedOutput) {
match offset {
0..=15 => (offset, self.regs.pointer_to_masked_out_0_lsw()),
16..=31 => (offset - 16, self.regs.pointer_to_masked_out_0_msw()),
32..=47 => (offset - 32, self.regs.pointer_to_masked_out_1_lsw()),
48..=53 => (offset - 48, self.regs.pointer_to_masked_out_1_msw()),
_ => unreachable!(),
}
}
#[inline]
fn write_state(&mut self, offset: usize, level: PinState) -> Result<(), InvalidPinModeMio> {
if !self.is_output() {
return Err(InvalidPinModeMio(self.dyn_mode));
}
self.write_state_unchecked(offset, level);
Ok(())
}
#[inline]
fn write_state_unchecked(&mut self, offset: usize, level: PinState) {
let (offset_in_reg, masked_out_ptr) = self.offset_to_masked_out_ptr_and_ptr_offset(offset);
unsafe {
core::ptr::write_volatile(
masked_out_ptr,
MaskedOutput::builder()
.with_mask(!(1 << offset_in_reg))
.with_output((level as u16) << offset_in_reg)
.build(),
);
}
}
#[inline]
fn is_low_mut(&mut self) -> Result<bool, InvalidPinModeMio> {
self.is_low()
}
#[inline]
fn is_high_mut(&mut self) -> Result<bool, InvalidPinModeMio> {
self.is_high()
}
#[inline]
fn is_set_low_mut(&mut self) -> Result<bool, InvalidPinModeMio> {
self.is_set_low()
}
#[inline]
fn is_set_high_mut(&mut self) -> Result<bool, InvalidPinModeMio> {
self.is_set_high()
}
fn reconfigure_slcr_mio_cfg(
&mut self,
tristate: bool,
pullup: Option<bool>,
mux_conf: Option<MuxConf>,
) {
// Safety: We only modify the MIO config of the pin.
let mut slcr_wrapper = unsafe { Slcr::steal() };
// We read first, because writing also required unlocking the SLCR.
// This allows the user to configure the SLCR themselves to avoid unnecessary
// re-configuration which might also be potentially unsafe at run-time.
let mio_cfg = slcr_wrapper.regs().read_mio_pins(self.offset).unwrap();
if (pullup.is_some() && mio_cfg.pullup() != pullup.unwrap())
|| (mux_conf.is_some() && MuxConf::from(mio_cfg) != mux_conf.unwrap())
|| tristate != mio_cfg.tri_enable()
{
slcr_wrapper.modify(|mut_slcr| {
mut_slcr
.modify_mio_pins(self.offset, |mut val| {
if let Some(pullup) = pullup {
val.set_pullup(pullup);
}
if let Some(mux_conf) = mux_conf {
val.set_l0_sel(mux_conf.l0_sel());
val.set_l1_sel(mux_conf.l1_sel());
val.set_l2_sel(mux_conf.l2_sel());
val.set_l3_sel(mux_conf.l3_sel());
}
val.set_tri_enable(tristate);
val
})
.unwrap();
});
}
}
fn configure_input_pin(&mut self) {
match self.offset {
0..=31 => {
self.regs.bank_0().modify_dirm(|mut v| {
v &= !(1 << self.offset);
v
});
self.regs.bank_0().modify_out_en(|mut v| {
v &= !(1 << self.offset);
v
});
}
32..=53 => {
self.regs.bank_1().modify_dirm(|mut v| {
v &= !(1 << (self.offset - 32));
v
});
self.regs.bank_1().modify_out_en(|mut v| {
v &= !(1 << (self.offset - 32));
v
});
}
_ => panic!("invalid GPIO pin offset"),
}
}
}
impl embedded_hal::digital::ErrorType for DynMioPin {
type Error = InvalidPinModeMio;
}
impl embedded_hal::digital::OutputPin for DynMioPin {
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low()?;
Ok(())
}
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high()?;
Ok(())
}
}
impl embedded_hal::digital::StatefulOutputPin for DynMioPin {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self.is_set_high_mut()
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self.is_set_low_mut()
}
}
impl embedded_hal::digital::InputPin for DynMioPin {
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
}
}

View File

@ -0,0 +1,290 @@
//! EMIO (Extended Multiplexed I/O) module.
use zynq7000::gpio::{Gpio, MaskedOutput, MmioGpio};
pub use crate::gpio::PinState;
#[derive(Debug, thiserror::Error)]
#[error("invalid pin mode for MIO pin: {0:?}")]
pub struct InvalidPinModeEmio(pub DynPinModeEmio);
impl embedded_hal::digital::Error for InvalidPinModeEmio {
fn kind(&self) -> embedded_hal::digital::ErrorKind {
embedded_hal::digital::ErrorKind::Other
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DynPinModeEmio {
Output,
Input,
}
pub struct EmioPin {
regs: MmioGpio<'static>,
mode: DynPinModeEmio,
offset: usize,
}
impl EmioPin {
/// Unsafely steal an EMIO input pin.
///
/// Unless you have no other option, it is recommended to use the [EmioPins::take] method
/// to retrieve the pins safely. Returns [None] if the offset is larger than 63.
///
/// # Safety
///
/// This allows potentially creating multiple input pin structures for the same GPIO
/// peripheral which can cause data races.
#[inline]
pub const unsafe fn steal(offset: usize) -> Option<Self> {
if offset >= 64 {
return None;
}
Some(Self {
regs: unsafe { Gpio::new_mmio_fixed() },
mode: DynPinModeEmio::Input,
offset: 0,
})
}
pub fn into_input(&mut self) {
self.mode = DynPinModeEmio::Input;
match self.offset {
0..=31 => {
self.regs
.bank_2()
.modify_dirm(|val| val & !(1 << self.offset));
}
32..=63 => {
self.regs
.bank_3()
.modify_dirm(|val| val & !(1 << self.offset));
}
_ => unreachable!(),
}
}
pub fn into_output(&mut self, init_level: PinState) {
self.mode = DynPinModeEmio::Output;
match self.offset {
0..=31 => {
self.regs
.bank_2()
.modify_dirm(|val| val | (1 << self.offset));
self.regs
.bank_2()
.modify_out_en(|val| val | (1 << self.offset));
}
32..=63 => {
self.regs
.bank_3()
.modify_dirm(|val| val | (1 << self.offset));
self.regs
.bank_2()
.modify_out_en(|val| val | (1 << self.offset));
}
_ => unreachable!(),
}
// Unwrap okay, just set mode.
self.write_state(self.offset, init_level).unwrap();
}
#[inline]
pub fn is_low(&self) -> Result<bool, InvalidPinModeEmio> {
if self.mode == DynPinModeEmio::Output {
return Err(InvalidPinModeEmio(self.mode));
}
Ok(self.is_low_unchecked())
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn is_low_unchecked(&self) -> bool {
match self.offset {
0..=31 => ((self.regs.read_in_0() >> self.offset) & 0b1) == 0,
32..=63 => ((self.regs.read_in_1() >> (self.offset - 32)) & 0b1) == 0,
_ => panic!("invalid GPIO pin offset"),
}
}
#[inline]
pub fn is_high(&self) -> Result<bool, InvalidPinModeEmio> {
self.is_low().map(|v| !v)
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn is_high_unchecked(&self) -> bool {
!self.is_low_unchecked()
}
#[inline(always)]
fn offset_to_masked_out_ptr_and_ptr_offset(
&mut self,
offset: usize,
) -> (usize, *mut MaskedOutput) {
match offset {
0..=15 => (offset, self.regs.pointer_to_masked_out_2_lsw()),
16..=31 => (offset - 16, self.regs.pointer_to_masked_out_2_msw()),
32..=47 => (offset - 32, self.regs.pointer_to_masked_out_3_lsw()),
48..=63 => (offset - 48, self.regs.pointer_to_masked_out_3_msw()),
_ => unreachable!(),
}
}
#[inline]
fn write_state(&mut self, offset: usize, level: PinState) -> Result<(), InvalidPinModeEmio> {
if self.mode == DynPinModeEmio::Input {
return Err(InvalidPinModeEmio(self.mode));
}
self.write_state_unchecked(offset, level);
Ok(())
}
#[inline]
fn write_state_unchecked(&mut self, offset: usize, level: PinState) {
let (offset_in_reg, masked_out_ptr) = self.offset_to_masked_out_ptr_and_ptr_offset(offset);
unsafe {
core::ptr::write_volatile(
masked_out_ptr,
MaskedOutput::builder()
.with_mask(!(1 << offset_in_reg))
.with_output((level as u16) << offset_in_reg)
.build(),
);
}
}
#[inline]
pub fn is_set_low(&mut self) -> Result<bool, InvalidPinModeEmio> {
if self.mode == DynPinModeEmio::Input {
return Err(InvalidPinModeEmio(self.mode));
}
Ok(self.is_set_low_unchecked())
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn is_set_low_unchecked(&mut self) -> bool {
match self.offset {
0..=31 => ((self.regs.read_out_2() >> self.offset) & 0b1) == 0,
32..=64 => ((self.regs.read_out_3() >> (self.offset - 32)) & 0b1) == 0,
_ => panic!("invalid GPIO pin offset"),
}
}
#[inline]
pub fn is_set_high(&mut self) -> Result<bool, InvalidPinModeEmio> {
self.is_set_low().map(|v| !v)
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn is_set_high_unchecked(&mut self) -> bool {
!self.is_set_low_unchecked()
}
#[inline]
pub fn set_high(&mut self) -> Result<(), InvalidPinModeEmio> {
self.write_state(self.offset, PinState::High)
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn set_high_unchecked(&mut self) {
self.write_state_unchecked(self.offset, PinState::High)
}
#[inline]
pub fn set_low(&mut self) -> Result<(), InvalidPinModeEmio> {
self.write_state(self.offset, PinState::Low)
}
/// This variant does not check whether the [DynPinModeEmio] is correct.
#[inline]
pub fn set_low_unchecked(&mut self) {
self.write_state_unchecked(self.offset, PinState::Low)
}
#[inline]
fn is_high_mut(&mut self) -> Result<bool, InvalidPinModeEmio> {
self.is_high()
}
#[inline]
fn is_low_mut(&mut self) -> Result<bool, InvalidPinModeEmio> {
self.is_low()
}
}
impl embedded_hal::digital::ErrorType for EmioPin {
type Error = InvalidPinModeEmio;
}
impl embedded_hal::digital::InputPin for EmioPin {
#[inline]
fn is_high(&mut self) -> Result<bool, Self::Error> {
self.is_high_mut()
}
#[inline]
fn is_low(&mut self) -> Result<bool, Self::Error> {
self.is_low_mut()
}
}
impl embedded_hal::digital::OutputPin for EmioPin {
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.set_high()
}
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.set_low()
}
}
impl embedded_hal::digital::StatefulOutputPin for EmioPin {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
self.is_set_high()
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
self.is_set_low()
}
}
pub struct EmioPins {
emios: [Option<EmioPin>; 64],
}
impl EmioPins {
/// Create a new EMIO pin structure.
///
/// This structure is supposed to be used as a singleton. It will configure all
/// EMIO pins as inputs. If you want to retrieve individual pins without this structure,
/// use [EmioPin::steal] instead.
pub fn new(mut mmio: MmioGpio) -> Self {
let mut emios = [const { None }; 64];
// Configure all EMIO pins as inputs.
mmio.bank_2().write_dirm(0);
mmio.bank_3().write_dirm(0);
(0..64).for_each(|i| {
let mmio = unsafe { Gpio::new_mmio_fixed() };
emios[i] = Some(EmioPin {
regs: mmio,
mode: DynPinModeEmio::Input,
offset: i,
});
});
Self { emios }
}
pub fn take(&mut self, offset: usize) -> Option<EmioPin> {
self.emios[offset].take()
}
pub fn give(&mut self, emio: EmioPin) {
self.emios[emio.offset].replace(emio);
}
}

View File

@ -0,0 +1,475 @@
//! Multiplexed I/O (MIO) module.
//!
//! This module provides a type-state API for all MIO pins. This also allows associating
//! the pins, their modes and their IDs to the peripherals they are able to serve.
//!
//! The pins can be type-erased for easier handling, which also incurs some more run-time checks.
//!
//! # Examples
//!
//! - [Blinky](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/main.rs)
//! - [Logger example](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/logger.rs)
//! which uses MIO pins for the UART.
use embedded_hal::digital::InputPin;
use zynq7000::gpio::MmioGpio;
use crate::sealed;
use super::{DynMioPin, DynPinModeMio, MioPinProvider, MuxConf, PinIsOutputOnly, PinState};
pub trait PinMode: sealed::Sealed {}
pub trait InputMode: sealed::Sealed {}
pub struct Output;
impl PinMode for Output {}
impl sealed::Sealed for Output {}
pub struct Input<S: InputMode>(core::marker::PhantomData<S>);
impl<S: InputMode> PinMode for Input<S> {}
impl<S: InputMode> sealed::Sealed for Input<S> {}
pub struct PullUp;
pub struct Floating;
impl InputMode for PullUp {}
impl sealed::Sealed for PullUp {}
impl InputMode for Floating {}
impl sealed::Sealed for Floating {}
pub struct IoPeriph;
impl sealed::Sealed for IoPeriph {}
impl PinMode for IoPeriph {}
pub type Reset = Input<PullUp>;
pub trait PinId {
const OFFSET: usize;
}
macro_rules! pin_id {
($Id:ident, $num:literal) => {
// Need paste macro to use ident in doc attribute
paste::paste! {
#[doc = "Pin ID representing pin " $Id]
#[derive(Debug)]
pub enum $Id {}
impl $crate::sealed::Sealed for $Id {}
impl PinId for $Id {
const OFFSET: usize = $num;
}
}
};
}
pin_id!(Mio0, 0);
pin_id!(Mio1, 1);
pin_id!(Mio2, 2);
pin_id!(Mio3, 3);
pin_id!(Mio4, 4);
pin_id!(Mio5, 5);
pin_id!(Mio6, 6);
pin_id!(Mio7, 7);
pin_id!(Mio8, 8);
pin_id!(Mio9, 9);
pin_id!(Mio10, 10);
pin_id!(Mio11, 11);
pin_id!(Mio12, 12);
pin_id!(Mio13, 13);
pin_id!(Mio14, 14);
pin_id!(Mio15, 15);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio16, 16);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio17, 17);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio18, 18);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio19, 19);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio20, 20);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio21, 21);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio22, 22);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio23, 23);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio24, 24);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio25, 25);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio26, 26);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio27, 27);
pin_id!(Mio28, 28);
pin_id!(Mio29, 29);
pin_id!(Mio30, 30);
pin_id!(Mio31, 31);
pin_id!(Mio32, 32);
pin_id!(Mio33, 33);
pin_id!(Mio34, 34);
pin_id!(Mio35, 35);
pin_id!(Mio36, 36);
pin_id!(Mio37, 37);
pin_id!(Mio38, 38);
pin_id!(Mio39, 39);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio40, 40);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio41, 41);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio42, 42);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio43, 43);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio44, 44);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio45, 45);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio46, 46);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio47, 47);
pin_id!(Mio48, 48);
pin_id!(Mio49, 49);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio50, 50);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pin_id!(Mio51, 51);
pin_id!(Mio52, 52);
pin_id!(Mio53, 53);
pub struct MioPin<I: PinId, S: PinMode> {
inner: DynMioPin,
phantom: core::marker::PhantomData<(I, S)>,
}
pub trait IoPeriphPin {
fn mux_conf(&self) -> MuxConf;
}
impl<I: PinId, M: PinMode> MioPinProvider for MioPin<I, M> {
delegate::delegate! {
to self.inner {
fn mode(&self) -> &DynPinModeMio;
fn offset(&self) -> usize;
}
}
}
impl<I: PinId, S: PinMode> MioPin<I, S> {
#[inline]
const unsafe fn new() -> Self {
Self {
inner: DynMioPin::new(I::OFFSET, DynPinModeMio::InputPullUp),
phantom: core::marker::PhantomData,
}
}
/// Steal a typed MIO pin.
///
/// Usually, you can just use the MIO pin members of the [MioPins] structure.
/// However, if you pass the pins into a consuming peripheral driver which performs
/// immediate type erasure, and you require the pins for/after a re-configuration
/// of the system, you can unsafely steal the pin. This function will NOT perform any
/// re-configuration.
///
/// # Safety
///
/// This allows to create multiple instances of the same pin, which can lead to
/// data races on concurrent access.
#[inline]
pub const unsafe fn steal() -> Self {
unsafe { Self::new() }
}
/// Convert the pin to the requested [`PinMode`]
#[inline]
pub fn into_mode<Mode: PinMode>(self) -> MioPin<I, Mode> {
// Safe because we drop the existing Pin
MioPin {
inner: self.inner,
phantom: core::marker::PhantomData,
}
}
pub fn into_floating_input(mut self) -> Result<MioPin<I, Input<Floating>>, PinIsOutputOnly> {
self.inner.into_input_floating()?;
Ok(self.into_mode())
}
pub fn into_pull_up_input(mut self) -> Result<MioPin<I, Input<PullUp>>, PinIsOutputOnly> {
self.inner.into_input_pull_up()?;
Ok(self.into_mode())
}
pub fn into_output(mut self, init_level: PinState) -> MioPin<I, Output> {
self.inner.into_output(init_level);
self.into_mode()
}
pub fn into_io_periph(
mut self,
mux_conf: MuxConf,
pullup: Option<bool>,
) -> MioPin<I, IoPeriph> {
self.inner.into_io_periph_pin(mux_conf, pullup);
self.into_mode()
}
/// Type-erase the pin.
#[inline]
pub const fn downgrade(self) -> DynMioPin {
self.inner
}
}
impl<I: PinId> MioPin<I, Output> {
#[inline]
pub fn set_high(&mut self) {
self.inner.set_high_unchecked();
}
#[inline]
pub fn set_low(&mut self) {
self.inner.set_low_unchecked();
}
#[inline]
pub fn is_set_high(&self) {
self.inner.is_set_high_unchecked();
}
#[inline]
pub fn is_set_low(&self) {
self.inner.is_set_low_unchecked();
}
}
impl<I: PinId> IoPeriphPin for MioPin<I, IoPeriph> {
#[inline]
fn mux_conf(&self) -> MuxConf {
if let DynPinModeMio::IoPeriph(mux_conf) = self.inner.mode() {
*mux_conf
} else {
// This should never happen due to type state guarantees.
panic!("unexpected pin state");
}
}
}
impl<I: PinId, S: InputPin + PinMode> MioPin<I, S> {
#[inline]
pub fn is_high(&self) -> bool {
self.inner.is_high_unchecked()
}
#[inline]
pub fn is_low(&self) -> bool {
self.inner.is_low_unchecked()
}
}
pub struct MioPins {
pub mio0: MioPin<Mio0, Reset>,
pub mio1: MioPin<Mio1, Reset>,
pub mio2: MioPin<Mio2, Reset>,
pub mio3: MioPin<Mio3, Reset>,
pub mio4: MioPin<Mio4, Reset>,
pub mio5: MioPin<Mio5, Reset>,
pub mio6: MioPin<Mio6, Reset>,
pub mio7: MioPin<Mio7, Output>,
pub mio8: MioPin<Mio8, Output>,
pub mio9: MioPin<Mio9, Reset>,
pub mio10: MioPin<Mio10, Reset>,
pub mio11: MioPin<Mio11, Reset>,
pub mio12: MioPin<Mio12, Reset>,
pub mio13: MioPin<Mio13, Reset>,
pub mio14: MioPin<Mio14, Reset>,
pub mio15: MioPin<Mio15, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio16: MioPin<Mio16, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio17: MioPin<Mio17, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio18: MioPin<Mio18, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio19: MioPin<Mio19, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio20: MioPin<Mio20, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio21: MioPin<Mio21, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio22: MioPin<Mio22, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio23: MioPin<Mio23, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio24: MioPin<Mio24, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio25: MioPin<Mio25, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio26: MioPin<Mio26, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio27: MioPin<Mio27, Reset>,
pub mio28: MioPin<Mio28, Reset>,
pub mio29: MioPin<Mio29, Reset>,
pub mio30: MioPin<Mio30, Reset>,
pub mio31: MioPin<Mio31, Reset>,
pub mio32: MioPin<Mio32, Reset>,
pub mio33: MioPin<Mio33, Reset>,
pub mio34: MioPin<Mio34, Reset>,
pub mio35: MioPin<Mio35, Reset>,
pub mio36: MioPin<Mio36, Reset>,
pub mio37: MioPin<Mio37, Reset>,
pub mio38: MioPin<Mio38, Reset>,
pub mio39: MioPin<Mio39, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio40: MioPin<Mio40, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio41: MioPin<Mio41, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio42: MioPin<Mio42, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio43: MioPin<Mio43, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio44: MioPin<Mio44, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio45: MioPin<Mio45, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio46: MioPin<Mio46, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio47: MioPin<Mio47, Reset>,
pub mio48: MioPin<Mio48, Reset>,
pub mio49: MioPin<Mio49, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio50: MioPin<Mio50, Reset>,
#[cfg(not(feature = "7z010-7z007s-clg225"))]
pub mio51: MioPin<Mio51, Reset>,
pub mio52: MioPin<Mio52, Reset>,
pub mio53: MioPin<Mio53, Reset>,
}
impl MioPins {
pub const fn new(_mmio: MmioGpio) -> Self {
Self {
mio0: unsafe { MioPin::new() },
mio1: unsafe { MioPin::new() },
mio2: unsafe { MioPin::new() },
mio3: unsafe { MioPin::new() },
mio4: unsafe { MioPin::new() },
mio5: unsafe { MioPin::new() },
mio6: unsafe { MioPin::new() },
mio7: unsafe { MioPin::new() },
mio8: unsafe { MioPin::new() },
mio9: unsafe { MioPin::new() },
mio10: unsafe { MioPin::new() },
mio11: unsafe { MioPin::new() },
mio12: unsafe { MioPin::new() },
mio13: unsafe { MioPin::new() },
mio14: unsafe { MioPin::new() },
mio15: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio16: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio17: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio18: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio19: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio20: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio21: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio22: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio23: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio24: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio25: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio26: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio27: unsafe { MioPin::new() },
mio28: unsafe { MioPin::new() },
mio29: unsafe { MioPin::new() },
mio30: unsafe { MioPin::new() },
mio31: unsafe { MioPin::new() },
mio32: unsafe { MioPin::new() },
mio33: unsafe { MioPin::new() },
mio34: unsafe { MioPin::new() },
mio35: unsafe { MioPin::new() },
mio36: unsafe { MioPin::new() },
mio37: unsafe { MioPin::new() },
mio38: unsafe { MioPin::new() },
mio39: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio40: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio41: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio42: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio43: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio44: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio45: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio46: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio47: unsafe { MioPin::new() },
mio48: unsafe { MioPin::new() },
mio49: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio50: unsafe { MioPin::new() },
#[cfg(not(feature = "7z010-7z007s-clg225"))]
mio51: unsafe { MioPin::new() },
mio52: unsafe { MioPin::new() },
mio53: unsafe { MioPin::new() },
}
}
}
impl<I: PinId, M: PinMode> embedded_hal::digital::ErrorType for MioPin<I, M> {
type Error = core::convert::Infallible;
}
impl<I: PinId> embedded_hal::digital::OutputPin for MioPin<I, Output> {
#[inline]
fn set_low(&mut self) -> Result<(), Self::Error> {
self.inner.set_low_unchecked();
Ok(())
}
#[inline]
fn set_high(&mut self) -> Result<(), Self::Error> {
self.inner.set_high_unchecked();
Ok(())
}
}
impl<I: PinId> embedded_hal::digital::StatefulOutputPin for MioPin<I, Output> {
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.inner.is_set_high_unchecked())
}
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.inner.is_set_low_unchecked())
}
}
impl<I: PinId, M: InputMode + PinMode> embedded_hal::digital::InputPin for MioPin<I, M> {
fn is_high(&mut self) -> Result<bool, Self::Error> {
Ok(self.inner.is_high_unchecked())
}
fn is_low(&mut self) -> Result<bool, Self::Error> {
Ok(self.inner.is_low_unchecked())
}
}

View File

@ -0,0 +1,42 @@
//! GPIO support module for the Zynq7000 SoC.
pub mod dyn_mio;
pub mod emio;
pub mod mio;
use crate::{enable_amba_peripheral_clock, slcr::Slcr};
pub use dyn_mio::*;
pub use emio::*;
pub use mio::*;
pub use embedded_hal::digital::PinState;
use zynq7000::{gpio::MmioGpio, slcr::reset::GpioClockReset};
pub struct GpioPins {
pub mio: MioPins,
pub emio: EmioPins,
}
impl GpioPins {
pub fn new(gpio: MmioGpio) -> Self {
enable_amba_peripheral_clock(crate::PeripheralSelect::Gpio);
Self {
mio: MioPins::new(unsafe { gpio.clone() }),
emio: EmioPins::new(gpio),
}
}
}
/// Reset the GPIO peripheral using the SLCR reset register for GPIO.
#[inline]
pub fn reset() {
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl()
.write_gpio(GpioClockReset::builder().with_gpio_cpu1x_rst(true).build());
// Keep it in reset for one cycle.. not sure if this is necessary.
cortex_ar::asm::nop();
regs.reset_ctrl()
.write_gpio(GpioClockReset::builder().with_gpio_cpu1x_rst(false).build());
});
}
}

169
zynq7000-hal/src/gtc.rs Normal file
View File

@ -0,0 +1,169 @@
//! Global timer counter driver module.
//!
//! # Examples
//!
//! - [GTC ticks example](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/examples/simple/src/bin/gtc-ticks.rs)
//! - [Embassy Timer Driver](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-embassy/src/lib.rs)
use zynq7000::gtc::MmioGtc;
use crate::{clocks::ArmClocks, time::Hertz};
/// High level GTC driver.
///
/// This structure also allows an optional clock member, which is required for the
/// [frequency_to_ticks] function and the [embedded_hal::delay::DelayNs] implementation
/// to work.
pub struct Gtc {
regs: MmioGtc<'static>,
cpu_3x2x_clock: Option<Hertz>,
}
unsafe impl Send for Gtc {}
pub const fn frequency_to_ticks(clock: Hertz, frequency: Hertz) -> u32 {
clock.raw().div_ceil(frequency.raw())
}
impl Gtc {
/// Create a peripheral driver from a MMIO GTC block.
#[inline]
pub const fn new(_regs: MmioGtc<'static>, clocks: &ArmClocks) -> Self {
unsafe { Self::steal_fixed(Some(clocks.cpu_3x2x_clk())) }
}
/// Steal the GTC from the PAC.
///
/// This function still expect the GTC clock, which is the CPU 3x2x clock frequency.
///
/// # Safety
///
/// This function allows creating an arbitrary amount of memory-mapped peripheral drivers.
/// See the [zynq7000::gtc::Gtc::new_mmio] docs for more safety information.
#[inline]
pub const unsafe fn steal_fixed(cpu_3x2x_clk: Option<Hertz>) -> Self {
Self {
regs: unsafe { zynq7000::gtc::Gtc::new_mmio_fixed() },
cpu_3x2x_clock: cpu_3x2x_clk,
}
}
#[inline]
pub fn set_cpu_3x2x_clock(&mut self, clock: Hertz) {
self.cpu_3x2x_clock = Some(clock);
}
// TODO: Change this API once pure-reads work.
/// Read the 64-bit timer.
#[inline]
pub fn read_timer(&self) -> u64 {
// Safety: We require interior mutability here because even reads are unsafe.
// But we want to avoid a RefCell which would incur a run-time cost solely to make this
// function non-mut, so we steal the GTC here. Ownership is guaranteed or mandated
// by constructor.
let upper = self.regs.read_count_upper();
loop {
let lower = self.regs.read_count_lower();
if self.regs.read_count_upper() == upper {
return ((upper as u64) << 32) | (lower as u64);
}
// Overflow, read upper again.
}
}
/// Set the comparator which can be used to trigger an interrupt in the future.
#[inline]
pub fn set_comparator(&mut self, comparator: u64) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_comparator_enable(false);
ctrl
});
self.regs.write_comparator_upper((comparator >> 32) as u32);
self.regs.write_comparator_lower(comparator as u32);
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_comparator_enable(true);
ctrl
});
}
pub fn frequency_to_ticks(&self, frequency: Hertz) -> u32 {
if self.cpu_3x2x_clock.is_none() {
return 0;
}
frequency_to_ticks(self.cpu_3x2x_clock.unwrap(), frequency)
}
/// Set the auto-increment value which will be used by the hardware to automatically
/// increment the comparator value on a comparator interrupt, if the auto-increment is enabled.
#[inline]
pub fn set_auto_increment_value(&mut self, value: u32) {
self.regs.write_auto_increment(value);
}
#[inline]
pub fn set_auto_increment_value_for_frequency(&mut self, frequency: Hertz) {
self.regs
.write_auto_increment(self.frequency_to_ticks(frequency));
}
#[inline]
pub fn enable(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_enable(true);
ctrl
});
}
#[inline]
pub fn enable_auto_increment(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_auto_increment(true);
ctrl
});
}
#[inline]
pub fn set_prescaler(&mut self, prescaler: u8) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_prescaler(prescaler);
ctrl
});
}
#[inline]
pub fn disable(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_enable(false);
ctrl
});
}
/// Enable the comparator interrupt.
#[inline]
pub fn enable_interrupt(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_irq_enable(true);
ctrl
});
}
/// Disable the comparator interrupt.
#[inline]
pub fn disable_interrupt(&mut self) {
self.regs.modify_ctrl(|mut ctrl| {
ctrl.set_irq_enable(false);
ctrl
});
}
}
/// GTC can be used for blocking delays.
impl embedded_hal::delay::DelayNs for Gtc {
fn delay_ns(&mut self, ns: u32) {
if self.cpu_3x2x_clock.is_none() {
return;
}
let end_of_delay = self.read_timer()
+ (((ns as u64) * self.cpu_3x2x_clock.unwrap().raw() as u64) / 1_000_000_000);
while self.read_timer() < end_of_delay {}
}
}

707
zynq7000-hal/src/i2c.rs Normal file
View File

@ -0,0 +1,707 @@
use arbitrary_int::{u2, u3, u6};
use embedded_hal::i2c::NoAcknowledgeSource;
use zynq7000::{
i2c::{Control, I2C_0_BASE_ADDR, I2C_1_BASE_ADDR, InterruptStatus, MmioI2c, TransferSize},
slcr::reset::DualClockReset,
};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::{
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
};
use crate::{
enable_amba_peripheral_clock,
gpio::{
IoPeriph, IoPeriphPin, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29, Mio30,
Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, Mio52, Mio53,
MioPin, MuxConf, PinMode,
},
slcr::Slcr,
time::Hertz,
};
pub const I2C_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b010));
pub const FIFO_DEPTH: usize = 16;
/// Maximum read size in one read operation.
pub const MAX_READ_SIZE: usize = 255;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cId {
I2c0 = 0,
I2c1 = 1,
}
pub trait PsI2c {
fn reg_block(&self) -> MmioI2c<'static>;
fn id(&self) -> Option<I2cId>;
}
impl PsI2c for MmioI2c<'static> {
#[inline]
fn reg_block(&self) -> MmioI2c<'static> {
unsafe { self.clone() }
}
#[inline]
fn id(&self) -> Option<I2cId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == I2C_0_BASE_ADDR {
return Some(I2cId::I2c0);
} else if base_addr == I2C_1_BASE_ADDR {
return Some(I2cId::I2c1);
}
None
}
}
pub trait SdaPin: IoPeriphPin {
const ID: I2cId;
}
pub trait SckPin: IoPeriphPin {
const ID: I2cId;
}
pub trait I2cPins {}
macro_rules! into_i2c {
($($Mio:ident),+) => {
$(
impl <M: PinMode> MioPin<$Mio, M> {
/// Convert the pin into I2C pins by configuring the pin routing via the
/// MIO multiplexer bits. Also enables pull-ups for the pins.
pub fn into_i2c(self) -> MioPin<$Mio, IoPeriph> {
// Enable pull-ups for the I2C pins.
self.into_io_periph(I2C_MUX_CONF, Some(true))
}
}
)+
};
}
macro_rules! i2c_pin_impls {
($Id: path, $SckMio:ident, $SdaMio:ident) => {
impl SckPin for MioPin<$SckMio, IoPeriph> {
const ID: I2cId = $Id;
}
impl SdaPin for MioPin<$SdaMio, IoPeriph> {
const ID: I2cId = $Id;
}
impl I2cPins for (MioPin<$SckMio, IoPeriph>, MioPin<$SdaMio, IoPeriph>) {}
};
}
into_i2c!(
Mio10, Mio11, Mio14, Mio15, Mio30, Mio31, Mio34, Mio35, Mio38, Mio39, Mio12, Mio13, Mio28,
Mio29, Mio32, Mio33, Mio36, Mio37, Mio48, Mio49, Mio52, Mio53
);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
into_i2c!(
Mio18, Mio19, Mio22, Mio23, Mio26, Mio27, Mio42, Mio43, Mio46, Mio47, Mio50, Mio51, Mio16,
Mio17, Mio20, Mio21, Mio24, Mio25, Mio40, Mio41, Mio44, Mio45
);
i2c_pin_impls!(I2cId::I2c0, Mio10, Mio11);
i2c_pin_impls!(I2cId::I2c0, Mio14, Mio15);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio18, Mio19);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio22, Mio23);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio26, Mio27);
i2c_pin_impls!(I2cId::I2c0, Mio30, Mio31);
i2c_pin_impls!(I2cId::I2c0, Mio34, Mio35);
i2c_pin_impls!(I2cId::I2c0, Mio38, Mio39);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio42, Mio43);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio46, Mio47);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c0, Mio50, Mio51);
i2c_pin_impls!(I2cId::I2c1, Mio12, Mio13);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c1, Mio16, Mio17);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c1, Mio20, Mio21);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c1, Mio24, Mio25);
i2c_pin_impls!(I2cId::I2c1, Mio28, Mio29);
i2c_pin_impls!(I2cId::I2c1, Mio32, Mio33);
i2c_pin_impls!(I2cId::I2c1, Mio36, Mio37);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c1, Mio40, Mio41);
#[cfg(not(feature = "7z010-7z007s-clg225"))]
i2c_pin_impls!(I2cId::I2c1, Mio44, Mio45);
i2c_pin_impls!(I2cId::I2c1, Mio48, Mio49);
i2c_pin_impls!(I2cId::I2c1, Mio52, Mio53);
#[derive(Debug, Clone, Copy)]
pub enum I2cSpeed {
Normal100kHz,
HighSpeed400KHz,
}
impl I2cSpeed {
pub fn frequency_full_number(&self) -> Hertz {
Hertz::from_raw(match self {
I2cSpeed::Normal100kHz => 100_000,
I2cSpeed::HighSpeed400KHz => 400_000,
})
}
/// From Xilinx embeddedsw
/// If frequency 400KHz is selected, 384.6KHz should be set.
/// If frequency 100KHz is selected, 90KHz should be set.
/// This is due to a hardware limitation.
pub fn frequency_for_calculation(&self) -> Hertz {
Hertz::from_raw(match self {
I2cSpeed::Normal100kHz => 90_000,
I2cSpeed::HighSpeed400KHz => 384_600,
})
}
}
#[derive(Debug, thiserror::Error)]
#[error("I2C speed not attainable")]
pub struct I2cSpeedNotAttainable;
#[derive(Debug, thiserror::Error)]
pub enum I2cTxError {
#[error("arbitration lost")]
ArbitrationLoss,
#[error("transfer not acknowledged: {0}")]
Nack(NoAcknowledgeSource),
#[error("TX overflow")]
TxOverflow,
#[error("timeout of transfer")]
Timeout,
}
#[derive(Debug, thiserror::Error)]
pub enum I2cRxError {
#[error("arbitration lost")]
ArbitrationLoss,
#[error("transfer not acknowledged")]
Nack(NoAcknowledgeSource),
#[error("RX underflow")]
RxUnderflow,
#[error("RX overflow")]
RxOverflow,
#[error("timeout of transfer")]
Timeout,
#[error("read data exceeds maximum allowed 255 bytes per transfer")]
ReadDataLenTooLarge,
}
#[derive(Debug, thiserror::Error)]
pub enum I2cError {
#[error("arbitration lost")]
ArbitrationLoss,
#[error("transfer not acknowledged: {0}")]
Nack(NoAcknowledgeSource),
#[error("TX overflow")]
TxOverflow,
#[error("RX underflow")]
RxUnderflow,
#[error("RX overflow")]
RxOverflow,
#[error("timeout of transfer")]
Timeout,
#[error("read data exceeds maximum allowed 255 bytes per transfer")]
ReadDataLenTooLarge,
}
impl From<I2cRxError> for I2cError {
fn from(err: I2cRxError) -> Self {
match err {
I2cRxError::ArbitrationLoss => I2cError::ArbitrationLoss,
I2cRxError::Nack(nack) => I2cError::Nack(nack),
I2cRxError::RxUnderflow => I2cError::RxUnderflow,
I2cRxError::RxOverflow => I2cError::RxOverflow,
I2cRxError::Timeout => I2cError::Timeout,
I2cRxError::ReadDataLenTooLarge => I2cError::ReadDataLenTooLarge,
}
}
}
impl From<I2cTxError> for I2cError {
fn from(err: I2cTxError) -> Self {
match err {
I2cTxError::ArbitrationLoss => I2cError::ArbitrationLoss,
I2cTxError::Nack(nack) => I2cError::Nack(nack),
I2cTxError::TxOverflow => I2cError::TxOverflow,
I2cTxError::Timeout => I2cError::Timeout,
}
}
}
#[inline]
pub fn calculate_i2c_speed(cpu_1x_clk: Hertz, clk_config: ClockConfig) -> Hertz {
cpu_1x_clk / (22 * (clk_config.div_a as u32 + 1) * (clk_config.div_b as u32 + 1))
}
pub fn calculate_divisors(
cpu_1x_clk: Hertz,
speed: I2cSpeed,
) -> Result<ClockConfig, I2cSpeedNotAttainable> {
let target_speed = speed.frequency_for_calculation();
if cpu_1x_clk > 22 * 64 * 4 * target_speed {
return Err(I2cSpeedNotAttainable);
}
let mut smallest_deviation = u32::MAX;
let mut best_div_a = 1;
let mut best_div_b = 1;
for divisor_a in 1..=4 {
for divisor_b in 1..=64 {
let i2c_clock = cpu_1x_clk / (22 * divisor_a * divisor_b);
let deviation = (target_speed.raw() as i32 - i2c_clock.raw() as i32).unsigned_abs();
if deviation < smallest_deviation {
smallest_deviation = deviation;
best_div_a = divisor_a;
best_div_b = divisor_b;
}
}
}
Ok(ClockConfig::new(best_div_a as u8 - 1, best_div_b as u8 - 1))
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ClockConfig {
div_a: u8,
div_b: u8,
}
impl ClockConfig {
pub fn new(div_a: u8, div_b: u8) -> Self {
Self { div_a, div_b }
}
pub fn div_a(&self) -> u8 {
self.div_a
}
pub fn div_b(&self) -> u8 {
self.div_b
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid I2C ID")]
pub struct InvalidPsI2cError;
#[derive(Debug, thiserror::Error)]
pub enum I2cConstructionError {
#[error("invalid I2C ID {0}")]
InvalidPsI2c(#[from] InvalidPsI2cError),
#[error("pin invalid for I2C ID")]
PinInvalidForI2cId,
#[error("invalid pin configuration for I2C")]
InvalidPinConf,
}
pub struct I2c {
regs: MmioI2c<'static>,
}
impl I2c {
pub fn new_with_mio<Sck: SckPin, Sda: SdaPin>(
i2c: impl PsI2c,
clk_cfg: ClockConfig,
i2c_pins: (Sck, Sda),
) -> Result<Self, I2cConstructionError> {
if i2c.id().is_none() {
return Err(InvalidPsI2cError.into());
}
if Sck::ID != Sda::ID {
return Err(I2cConstructionError::PinInvalidForI2cId);
}
if i2c_pins.0.mux_conf() != I2C_MUX_CONF || i2c_pins.1.mux_conf() != I2C_MUX_CONF {
return Err(I2cConstructionError::InvalidPinConf);
}
Ok(Self::new_generic(
i2c.id().unwrap(),
i2c.reg_block(),
clk_cfg,
))
}
pub fn new_with_emio(i2c: impl PsI2c, clk_cfg: ClockConfig) -> Result<Self, InvalidPsI2cError> {
if i2c.id().is_none() {
return Err(InvalidPsI2cError);
}
Ok(Self::new_generic(
i2c.id().unwrap(),
i2c.reg_block(),
clk_cfg,
))
}
pub fn new_generic(id: I2cId, mut regs: MmioI2c<'static>, clk_cfg: ClockConfig) -> Self {
let periph_sel = match id {
I2cId::I2c0 => crate::PeripheralSelect::I2c0,
I2cId::I2c1 => crate::PeripheralSelect::I2c1,
};
enable_amba_peripheral_clock(periph_sel);
//reset(id);
regs.write_cr(
Control::builder()
.with_div_a(u2::new(clk_cfg.div_a()))
.with_div_b(u6::new(clk_cfg.div_b()))
.with_clear_fifo(true)
.with_slv_mon(false)
.with_hold_bus(false)
.with_acken(false)
.with_addressing(true)
.with_mode(zynq7000::i2c::Mode::Master)
.with_dir(zynq7000::i2c::Direction::Transmitter)
.build(),
);
Self { regs }
}
/// Start the transfer by writing the I2C address.
#[inline]
fn start_transfer(&mut self, address: u8) {
self.regs
.write_addr(zynq7000::i2c::Addr::new_with_raw_value(address as u32));
}
#[inline]
pub fn set_hold_bit(&mut self) {
self.regs.modify_cr(|mut cr| {
cr.set_hold_bus(true);
cr
});
}
#[inline]
pub fn clear_hold_bit(&mut self) {
self.regs.modify_cr(|mut cr| {
cr.set_hold_bus(false);
cr
});
}
pub fn write_transfer_blocking(
&mut self,
addr: u8,
data: &[u8],
generate_stop: bool,
) -> Result<(), I2cTxError> {
self.regs.modify_cr(|mut cr| {
cr.set_acken(true);
cr.set_mode(zynq7000::i2c::Mode::Master);
cr.set_clear_fifo(true);
cr.set_dir(zynq7000::i2c::Direction::Transmitter);
if !generate_stop {
cr.set_hold_bus(true);
}
cr
});
let mut first_write_cycle = true;
let mut addr_set = false;
let mut written = 0;
// Clear the interrupt status register before using it to monitor the transfer.
self.regs.modify_isr(|isr| isr);
loop {
let bytes_to_write = core::cmp::min(
FIFO_DEPTH - self.regs.read_transfer_size().size() as usize,
data.len() - written,
);
(0..bytes_to_write).for_each(|_| {
self.regs
.write_data(zynq7000::i2c::Fifo::new_with_raw_value(
data[written] as u32,
));
written += 1;
});
if !addr_set {
self.start_transfer(addr);
addr_set = true;
}
let mut status = self.regs.read_sr();
// While the hardware is busy sending out data, we poll for errors.
while status.tx_busy() {
let isr = self.regs.read_isr();
self.check_and_handle_tx_errors(isr, first_write_cycle, bytes_to_write)?;
// Re-read for next check.
status = self.regs.read_sr();
}
first_write_cycle = false;
// Just need to poll to completion now.
if written == data.len() {
break;
}
}
// Poll to completion.
while !self.regs.read_isr().complete() {
let isr = self.regs.read_isr();
self.check_and_handle_tx_errors(isr, first_write_cycle, data.len())?;
}
if generate_stop {
self.clear_hold_bit();
}
Ok(())
}
fn check_and_handle_tx_errors(
&mut self,
isr: InterruptStatus,
first_write_cycle: bool,
first_chunk_len: usize,
) -> Result<(), I2cTxError> {
if isr.tx_overflow() {
self.clean_up_after_transfer_or_on_error();
return Err(I2cTxError::TxOverflow);
}
if isr.arbitration_lost() {
self.clean_up_after_transfer_or_on_error();
return Err(I2cTxError::ArbitrationLoss);
}
if isr.nack() {
self.clean_up_after_transfer_or_on_error();
// I have no tested this yet, but if no data was sent yet, this is probably
// an address NACK.
if first_write_cycle
&& self.regs.read_transfer_size().size() as usize + 1 == first_chunk_len
{
return Err(I2cTxError::Nack(NoAcknowledgeSource::Address));
} else {
return Err(I2cTxError::Nack(NoAcknowledgeSource::Data));
}
}
if isr.timeout() {
// Timeout / Stall condition.
self.clean_up_after_transfer_or_on_error();
return Err(I2cTxError::Timeout);
}
Ok(())
}
pub fn clean_up_after_transfer_or_on_error(&mut self) {
self.regs.modify_cr(|mut cr| {
cr.set_acken(false);
cr.set_clear_fifo(true);
cr
});
}
pub fn read_transfer_blocking(&mut self, addr: u8, data: &mut [u8]) -> Result<(), I2cRxError> {
self.regs.modify_cr(|mut cr| {
cr.set_acken(true);
cr.set_mode(zynq7000::i2c::Mode::Master);
cr.set_clear_fifo(true);
cr.set_dir(zynq7000::i2c::Direction::Receiver);
if data.len() > FIFO_DEPTH {
cr.set_hold_bus(true);
}
cr
});
let mut read = 0;
if data.len() > MAX_READ_SIZE {
return Err(I2cRxError::ReadDataLenTooLarge);
}
// Clear the interrupt status register before using it to monitor the transfer.
self.regs.modify_isr(|isr| isr);
self.regs
.write_transfer_size(TransferSize::new_with_raw_value(data.len() as u32));
self.start_transfer(addr);
loop {
let mut status = self.regs.read_sr();
loop {
let isr = self.regs.read_isr();
self.check_and_handle_rx_errors(read, isr)?;
if status.rx_valid() {
break;
}
// Re-read for next check.
status = self.regs.read_sr();
}
// Data to be read.
while self.regs.read_sr().rx_valid() {
data[read] = self.regs.read_data().data();
read += 1;
}
// The outstading read size is smaller than the FIFO. Clear the HOLD register as
// specified in TRM p.649 polled read step 6.
if self.regs.read_transfer_size().size() as usize <= FIFO_DEPTH {
self.clear_hold_bit();
}
// Read everything, just need to poll to completion now.
if read == data.len() {
break;
}
}
// Poll to completion.
while !self.regs.read_isr().complete() {
let isr = self.regs.read_isr();
self.check_and_handle_rx_errors(read, isr)?
}
self.clear_hold_bit();
self.clean_up_after_transfer_or_on_error();
Ok(())
}
fn check_and_handle_rx_errors(
&mut self,
read_count: usize,
isr: InterruptStatus,
) -> Result<(), I2cRxError> {
if isr.rx_overflow() {
self.clean_up_after_transfer_or_on_error();
return Err(I2cRxError::RxOverflow);
}
if isr.rx_underflow() {
self.clean_up_after_transfer_or_on_error();
return Err(I2cRxError::RxUnderflow);
}
if isr.nack() {
self.clean_up_after_transfer_or_on_error();
// I have no tested this yet, but if no data was sent yet, this is probably
// an address NACK.
if read_count == 0 {
return Err(I2cRxError::Nack(NoAcknowledgeSource::Address));
} else {
return Err(I2cRxError::Nack(NoAcknowledgeSource::Data));
}
}
if isr.timeout() {
// Timeout / Stall condition.
self.clean_up_after_transfer_or_on_error();
return Err(I2cRxError::Timeout);
}
Ok(())
}
}
impl embedded_hal::i2c::ErrorType for I2c {
type Error = I2cError;
}
impl embedded_hal::i2c::Error for I2cError {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
match self {
I2cError::ArbitrationLoss => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
I2cError::Nack(nack_kind) => embedded_hal::i2c::ErrorKind::NoAcknowledge(*nack_kind),
I2cError::RxOverflow => embedded_hal::i2c::ErrorKind::Overrun,
I2cError::TxOverflow => embedded_hal::i2c::ErrorKind::Other,
I2cError::RxUnderflow => embedded_hal::i2c::ErrorKind::Other,
I2cError::Timeout | I2cError::ReadDataLenTooLarge => {
embedded_hal::i2c::ErrorKind::Other
}
}
}
}
impl embedded_hal::i2c::I2c for I2c {
fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
for op in operations {
match op {
embedded_hal::i2c::Operation::Read(items) => {
self.read_transfer_blocking(address, items)?
}
embedded_hal::i2c::Operation::Write(items) => {
self.write_transfer_blocking(address, items, true)?
}
}
}
Ok(())
}
fn write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
// I have never tested this, so I am not sure whether the master still generates a stop
// condition somehow.. which might break the trait contract.
self.write_transfer_blocking(address, write, false)?;
Ok(self.read_transfer_blocking(address, read)?)
}
}
/// Reset the SPI peripheral using the SLCR reset register for SPI.
///
/// Please note that this function will interfere with an already configured
/// SPI instance.
#[inline]
pub fn reset(id: I2cId) {
let assert_reset = match id {
I2cId::I2c0 => DualClockReset::builder()
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
I2cId::I2c1 => DualClockReset::builder()
.with_periph1_cpu1x_rst(true)
.with_periph0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_i2c(assert_reset);
// Keep it in reset for some cycles.. The TMR just mentions some small delay,
// no idea what is meant with that.
for _ in 0..3 {
cortex_ar::asm::nop();
}
regs.reset_ctrl().write_i2c(DualClockReset::DEFAULT);
});
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use fugit::RateExtU32;
use std::println;
#[test]
fn example_test() {
let clk_cfg = calculate_divisors(111.MHz(), I2cSpeed::Normal100kHz).unwrap();
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 55);
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
assert!(speed.raw() < 100_000);
assert!(speed.raw() > 85_000);
}
#[test]
fn example_test_2() {
let clk_cfg = calculate_divisors(111.MHz(), I2cSpeed::HighSpeed400KHz).unwrap();
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 12);
let speed = calculate_i2c_speed(111.MHz(), clk_cfg);
assert!(speed.raw() < 400_000);
assert!(speed.raw() > 360_000);
}
#[test]
fn example_test_3() {
let clk_cfg = calculate_divisors(133.MHz(), I2cSpeed::Normal100kHz).unwrap();
assert_eq!(clk_cfg.div_a(), 1);
assert_eq!(clk_cfg.div_b(), 33);
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
assert!(speed.raw() < 100_000);
assert!(speed.raw() > 85_000);
}
#[test]
fn example_test_4() {
let clk_cfg = calculate_divisors(133.MHz(), I2cSpeed::HighSpeed400KHz).unwrap();
assert_eq!(clk_cfg.div_a(), 0);
assert_eq!(clk_cfg.div_b(), 15);
let speed = calculate_i2c_speed(133.MHz(), clk_cfg);
assert!(speed.raw() < 400_000);
assert!(speed.raw() > 360_000);
}
}

203
zynq7000-hal/src/lib.rs Normal file
View File

@ -0,0 +1,203 @@
//! # HAL for the AMD Zynq 7000 SoC family
//!
//! This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
//! hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000).
//!
//! It is the result of reading the datasheet for the device and encoding a type-safe layer over the
//! raw PAC. This crate also implements traits specified by the
//! [embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
//! various drivers in the embedded rust ecosystem.
#![no_std]
use slcr::Slcr;
use zynq7000::slcr::LevelShifterReg;
pub mod clocks;
pub mod gic;
pub mod gpio;
pub mod gtc;
pub mod i2c;
pub mod log;
pub mod prelude;
pub mod slcr;
pub mod spi;
pub mod time;
pub mod ttc;
pub mod uart;
/// This enumeration encodes the various boot sources.
#[derive(Debug, Copy, Clone)]
pub enum BootDevice {
JtagCascaded,
JtagIndependent,
Nor,
Nand,
Qspi,
SdCard,
}
#[derive(Debug, Copy, Clone)]
pub enum BootPllConfig {
Enabled,
Bypassed,
}
#[derive(Debug)]
pub struct BootMode {
boot_mode: Option<BootDevice>,
pll_config: BootPllConfig,
}
impl BootMode {
#[allow(clippy::new_without_default)]
/// Create a new boot mode information structure by reading the boot mode register from the
/// fixed SLCR block.
pub fn new() -> Self {
// Safety: Only read a read-only register here.
Self::new_with_raw_reg(
unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() }
.read_boot_mode()
.raw_value(),
)
}
fn new_with_raw_reg(raw_register: u32) -> Self {
let msb_three_bits = (raw_register >> 1) & 0b111;
let boot_mode = match msb_three_bits {
0b000 => {
if raw_register & 0b1 == 0 {
Some(BootDevice::JtagCascaded)
} else {
Some(BootDevice::JtagIndependent)
}
}
0b001 => Some(BootDevice::Nor),
0b010 => Some(BootDevice::Nand),
0b100 => Some(BootDevice::Qspi),
0b110 => Some(BootDevice::SdCard),
_ => None,
};
let pll_config = if (raw_register >> 4) & 0b1 == 0 {
BootPllConfig::Enabled
} else {
BootPllConfig::Bypassed
};
Self {
boot_mode,
pll_config,
}
}
pub fn boot_device(&self) -> Option<BootDevice> {
self.boot_mode
}
pub const fn pll_enable(&self) -> BootPllConfig {
self.pll_config
}
}
/// This configures the level shifters between the programmable logic (PL) and the processing
/// system (PS).
///
/// The Zynq-7000 TRM p.32 specifies more information about this register and how to use it.
pub fn configure_level_shifter(config: zynq7000::slcr::LevelShifterConfig) {
// Safety: We only manipulate the level shift registers.
unsafe {
Slcr::with(|slcr_unlocked| {
slcr_unlocked.write_lvl_shftr_en(LevelShifterReg::new_with_raw_value(config as u32));
});
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PeripheralSelect {
Smc = 24,
Lqspi = 23,
Gpio = 22,
Uart1 = 21,
Uart0 = 20,
I2c1 = 19,
I2c0 = 18,
Can1 = 17,
Can0 = 16,
Spi1 = 15,
Spi0 = 14,
Sdio1 = 11,
Sdio0 = 10,
Gem1 = 7,
Gem0 = 6,
Usb1 = 3,
Usb0 = 2,
Dma = 0,
}
/// Enable the AMBA peripheral clock, which is required to read the registers of a peripheral
/// block.
#[inline]
pub fn enable_amba_peripheral_clock(select: PeripheralSelect) {
unsafe {
Slcr::with(|regs| {
regs.clk_ctrl().modify_aper_clk_ctrl(|mut val| {
match select {
PeripheralSelect::Smc => val.set_smc_1x_clk_act(true),
PeripheralSelect::Lqspi => val.set_lqspi_1x_clk_act(true),
PeripheralSelect::Gpio => val.set_gpio_1x_clk_act(true),
PeripheralSelect::Uart1 => val.set_uart_1_1x_clk_act(true),
PeripheralSelect::Uart0 => val.set_uart_0_1x_clk_act(true),
PeripheralSelect::I2c1 => val.set_i2c_1_1x_clk_act(true),
PeripheralSelect::I2c0 => val.set_i2c_0_1x_clk_act(true),
PeripheralSelect::Can1 => val.set_can_1_1x_clk_act(true),
PeripheralSelect::Can0 => val.set_can_0_1x_clk_act(true),
PeripheralSelect::Spi1 => val.set_spi_1_1x_clk_act(true),
PeripheralSelect::Spi0 => val.set_spi_1_1x_clk_act(true),
PeripheralSelect::Sdio1 => val.set_sdio_1_1x_clk_act(true),
PeripheralSelect::Sdio0 => val.set_sdio_0_1x_clk_act(true),
PeripheralSelect::Gem1 => val.set_gem_1_1x_clk_act(true),
PeripheralSelect::Gem0 => val.set_gem_0_1x_clk_act(true),
PeripheralSelect::Usb1 => val.set_usb_1_cpu_1x_clk_act(true),
PeripheralSelect::Usb0 => val.set_usb_0_cpu_1x_clk_act(true),
PeripheralSelect::Dma => val.set_dma_cpu_2x_clk_act(true),
}
val
})
});
}
}
/// Disable the AMBA peripheral clock, which is required to read the registers of a peripheral
/// block.
#[inline]
pub fn disable_amba_peripheral_clock(select: PeripheralSelect) {
unsafe {
Slcr::with(|regs| {
regs.clk_ctrl().modify_aper_clk_ctrl(|mut val| {
match select {
PeripheralSelect::Smc => val.set_smc_1x_clk_act(false),
PeripheralSelect::Lqspi => val.set_lqspi_1x_clk_act(false),
PeripheralSelect::Gpio => val.set_gpio_1x_clk_act(false),
PeripheralSelect::Uart1 => val.set_uart_1_1x_clk_act(false),
PeripheralSelect::Uart0 => val.set_uart_0_1x_clk_act(false),
PeripheralSelect::I2c1 => val.set_i2c_1_1x_clk_act(false),
PeripheralSelect::I2c0 => val.set_i2c_0_1x_clk_act(false),
PeripheralSelect::Can1 => val.set_can_1_1x_clk_act(false),
PeripheralSelect::Can0 => val.set_can_0_1x_clk_act(false),
PeripheralSelect::Spi1 => val.set_spi_1_1x_clk_act(false),
PeripheralSelect::Spi0 => val.set_spi_1_1x_clk_act(false),
PeripheralSelect::Sdio1 => val.set_sdio_1_1x_clk_act(false),
PeripheralSelect::Sdio0 => val.set_sdio_0_1x_clk_act(false),
PeripheralSelect::Gem1 => val.set_gem_1_1x_clk_act(false),
PeripheralSelect::Gem0 => val.set_gem_0_1x_clk_act(false),
PeripheralSelect::Usb1 => val.set_usb_1_cpu_1x_clk_act(false),
PeripheralSelect::Usb0 => val.set_usb_0_cpu_1x_clk_act(false),
PeripheralSelect::Dma => val.set_dma_cpu_2x_clk_act(false),
}
val
})
});
}
}
pub(crate) mod sealed {
pub trait Sealed {}
}

230
zynq7000-hal/src/log.rs Normal file
View File

@ -0,0 +1,230 @@
//! # Simple logging providers.
/// Blocking UART loggers.
pub mod uart_blocking {
use core::cell::{Cell, RefCell, UnsafeCell};
use embedded_io::Write as _;
use cortex_ar::register::Cpsr;
use critical_section::Mutex;
use log::{LevelFilter, set_logger, set_max_level};
use crate::uart::Uart;
pub struct UartLoggerBlocking(Mutex<RefCell<Option<Uart>>>);
unsafe impl Send for UartLoggerBlocking {}
unsafe impl Sync for UartLoggerBlocking {}
static UART_LOGGER_BLOCKING: UartLoggerBlocking =
UartLoggerBlocking(Mutex::new(RefCell::new(None)));
/// Initialize the logger with a blocking UART instance.
///
/// This is a blocking logger which performs a write inside a critical section. This logger is
/// thread-safe, but interrupts will be disabled while the logger is writing to the UART.
///
/// For async applications, it is strongly recommended to use the asynchronous ring buffer
/// logger instead.
pub fn init_with_locks(uart: Uart, level: LevelFilter) {
// TODO: Impl debug for Uart
critical_section::with(|cs| {
let inner = UART_LOGGER_BLOCKING.0.borrow(cs);
inner.replace(Some(uart));
});
set_logger(&UART_LOGGER_BLOCKING).unwrap();
// Adjust as needed
set_max_level(level);
}
impl log::Log for UartLoggerBlocking {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
critical_section::with(|cs| {
let mut opt_logger = self.0.borrow(cs).borrow_mut();
if opt_logger.is_none() {
return;
}
let logger = opt_logger.as_mut().unwrap();
writeln!(logger, "{} - {}\r", record.level(), record.args()).unwrap();
})
}
fn flush(&self) {}
}
pub struct UartLoggerUnsafeSingleThread {
skip_in_isr: Cell<bool>,
uart: UnsafeCell<Option<Uart>>,
}
unsafe impl Send for UartLoggerUnsafeSingleThread {}
unsafe impl Sync for UartLoggerUnsafeSingleThread {}
static UART_LOGGER_UNSAFE_SINGLE_THREAD: UartLoggerUnsafeSingleThread =
UartLoggerUnsafeSingleThread {
skip_in_isr: Cell::new(false),
uart: UnsafeCell::new(None),
};
/// Initialize the logger with a blocking UART instance.
///
/// For async applications, it is strongly recommended to use the asynchronous ring buffer
/// logger instead.
///
/// # Safety
///
/// This is a blocking logger which performs a write WITHOUT a critical section. This logger is
/// NOT thread-safe. Users must ensure that this logger is not used inside a pre-emptive
/// multi-threading context and interrupt handlers.
pub unsafe fn create_unsafe_single_thread_logger(uart: Uart) -> UartLoggerUnsafeSingleThread {
UartLoggerUnsafeSingleThread {
skip_in_isr: Cell::new(false),
uart: UnsafeCell::new(Some(uart)),
}
}
/// Initialize the logger with a blocking UART instance which does not use locks.
///
/// # Safety
///
/// This is a blocking logger which performs a write WITHOUT a critical section. This logger is
/// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be
/// surpressed.
pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) {
let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() };
opt_uart.replace(uart);
UART_LOGGER_UNSAFE_SINGLE_THREAD
.skip_in_isr
.set(skip_in_isr);
set_logger(&UART_LOGGER_UNSAFE_SINGLE_THREAD).unwrap();
set_max_level(level); // Adjust as needed
}
impl log::Log for UartLoggerUnsafeSingleThread {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.skip_in_isr.get() {
match Cpsr::read().mode().unwrap() {
cortex_ar::register::cpsr::ProcessorMode::Fiq
| cortex_ar::register::cpsr::ProcessorMode::Irq => {
return;
}
_ => {}
}
}
let uart_mut = unsafe { &mut *self.uart.get() }.as_mut();
if uart_mut.is_none() {
return;
}
writeln!(
uart_mut.unwrap(),
"{} - {}\r",
record.level(),
record.args()
)
.unwrap();
}
fn flush(&self) {}
}
}
/// Logger module which logs into a ring buffer to allow asynchronous logging handling.
pub mod rb {
use core::cell::RefCell;
use core::fmt::Write as _;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use log::{LevelFilter, set_logger, set_max_level};
use ringbuf::{
StaticRb,
traits::{Consumer, Producer},
};
/// Logger implementation which logs frames via a ring buffer and sends the frame sizes
/// as messages.
///
/// The logger does not require allocation and reserved a generous amount of 4096 bytes for
/// both data buffer and ring buffer. This should be sufficient for most logging needs.
pub struct Logger {
frame_queue: embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32>,
data_buf: critical_section::Mutex<RefCell<heapless::String<4096>>>,
ring_buf: critical_section::Mutex<RefCell<Option<StaticRb<u8, 4096>>>>,
}
unsafe impl Send for Logger {}
unsafe impl Sync for Logger {}
static LOGGER_RB: Logger = Logger {
frame_queue: embassy_sync::channel::Channel::new(),
data_buf: critical_section::Mutex::new(RefCell::new(heapless::String::new())),
ring_buf: critical_section::Mutex::new(RefCell::new(None)),
};
impl log::Log for Logger {
fn enabled(&self, _metadata: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
critical_section::with(|cs| {
let ref_buf = self.data_buf.borrow(cs);
let mut buf = ref_buf.borrow_mut();
buf.clear();
let _ = writeln!(buf, "{} - {}\r", record.level(), record.args());
let rb_ref = self.ring_buf.borrow(cs);
let mut rb_opt = rb_ref.borrow_mut();
if rb_opt.is_none() {
panic!("log call on uninitialized logger");
}
rb_opt.as_mut().unwrap().push_slice(buf.as_bytes());
let _ = self.frame_queue.try_send(buf.len());
});
}
fn flush(&self) {
while !self.frame_queue().is_empty() {}
}
}
impl Logger {
pub fn frame_queue(
&self,
) -> &embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
&self.frame_queue
}
}
pub fn init(level: LevelFilter) {
critical_section::with(|cs| {
let rb = StaticRb::<u8, 4096>::default();
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
rb_ref.borrow_mut().replace(rb);
});
set_logger(&LOGGER_RB).unwrap();
set_max_level(level); // Adjust as needed
}
pub fn read_next_frame(frame_len: usize, buf: &mut [u8]) {
let read_len = core::cmp::min(frame_len, buf.len());
critical_section::with(|cs| {
let rb_ref = LOGGER_RB.ring_buf.borrow(cs);
let mut rb = rb_ref.borrow_mut();
rb.as_mut().unwrap().pop_slice(&mut buf[0..read_len]);
})
}
pub fn get_frame_queue()
-> &'static embassy_sync::channel::Channel<CriticalSectionRawMutex, usize, 32> {
LOGGER_RB.frame_queue()
}
}

View File

@ -0,0 +1,3 @@
//! Prelude
pub use fugit::ExtU32 as _;
pub use fugit::RateExtU32 as _;

67
zynq7000-hal/src/slcr.rs Normal file
View File

@ -0,0 +1,67 @@
//! # System Level Control Register (SLCR) module.
use zynq7000::slcr::MmioSlcr;
pub const LOCK_KEY: u32 = 0x767B;
pub const UNLOCK_KEY: u32 = 0xDF0D;
pub struct Slcr(zynq7000::slcr::MmioSlcr<'static>);
impl Slcr {
/// Modify the SLCR register.
///
/// # Safety
///
/// This method unsafely steals the SLCR MMIO block and then calls a user provided function
/// with the [SLCR MMIO][MmioSlcr] block as an input argument. It is the user's responsibility
/// that the SLCR is not used concurrently in a way which leads to data races.
pub unsafe fn with<F: FnMut(&mut MmioSlcr)>(mut f: F) {
let mut slcr = unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() };
slcr.write_unlock(UNLOCK_KEY);
f(&mut slcr);
slcr.write_lock(LOCK_KEY);
}
/// Create a new SLCR peripheral wrapper.
pub fn new(slcr: zynq7000::slcr::MmioSlcr<'static>) -> Self {
Self(slcr)
}
/// Unsafely create a new SLCR peripheral wrapper.
///
/// # Safety
///
/// This allows to create an arbitrary number of SLCR peripheral wrappers. It is the user's
/// responsibility that these wrappers are not used concurrently in a way which leads to
/// data races.
pub unsafe fn steal() -> Self {
Self::new(unsafe { zynq7000::slcr::Slcr::new_mmio_fixed() })
}
/// Returns a mutable reference to the SLCR MMIO block.
///
/// The MMIO block will not be unlocked. However, the registers can still be read.
pub fn regs(&mut self) -> &mut MmioSlcr<'static> {
&mut self.0
}
/// Modify the SLCR register.
///
/// This method unlocks the SLCR registers and then calls a user provided function
/// with the [SLCR MMIO][MmioSlcr] block as an input argument. This allows the user
/// to safely modify the SLCR registers. The SLCR will be locked afte the operation.
pub fn modify<F: FnMut(&mut MmioSlcr)>(&mut self, mut f: F) {
self.0.write_unlock(UNLOCK_KEY);
f(&mut self.0);
self.0.write_lock(LOCK_KEY);
}
/// Manually unlock the SLCR registers.
pub fn unlock(&mut self) {
self.0.write_unlock(UNLOCK_KEY);
}
/// Manually lock the SLCR registers.
pub fn lock(&mut self) {
self.0.write_lock(LOCK_KEY);
}
}

View File

@ -0,0 +1,584 @@
//! Asynchronous PS SPI driver.
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal_async::spi::SpiBus;
use raw_slice::{RawBufSlice, RawBufSliceMut};
use zynq7000::spi::InterruptStatus;
use super::{ChipSelect, FIFO_DEPTH, Spi, SpiId, SpiLowLevel};
static WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static TRANSFER_CONTEXTS: [Mutex<RefCell<TransferContext>>; 2] =
[const { Mutex::new(RefCell::new(TransferContext::new())) }; 2];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// This is a generic interrupt handler to handle asynchronous SPI operations for a given
/// SPI peripheral.
///
/// The user has to call this once in the interrupt handler responsible for the SPI interrupts on
/// the given SPI bank.
pub fn on_interrupt(peripheral: SpiId) {
let mut spi = unsafe { SpiLowLevel::steal(peripheral) };
let idx = peripheral as usize;
let imr = spi.read_imr();
// IRQ is not related.
if !imr.tx_trig() && !imr.tx_full() && !imr.tx_underflow() && !imr.rx_ovr() && !imr.rx_full() {
return;
}
// Prevent spurious interrupts from messing with out logic here.
spi.disable_interrupts();
let isr = spi.read_isr();
spi.clear_interrupts();
let mut context = critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
// No transfer active.
if context.transfer_type.is_none() {
return;
}
let transfer_type = context.transfer_type.unwrap();
match transfer_type {
TransferType::Read => on_interrupt_read(idx, &mut context, &mut spi, isr),
TransferType::Write => on_interrupt_write(idx, &mut context, &mut spi, isr),
TransferType::Transfer => on_interrupt_transfer(idx, &mut context, &mut spi, isr),
TransferType::TransferInPlace => {
on_interrupt_transfer_in_place(idx, &mut context, &mut spi, isr)
}
};
}
fn on_interrupt_read(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let transfer_len = read_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
context.rx_progress += 1;
});
// The FIFO still needs to be pumped.
while context.tx_progress < read_slice.len() && !isr.tx_full() {
spi.write_fifo_unchecked(0);
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn on_interrupt_write(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
let write_slice = unsafe { context.tx_slice.get().unwrap() };
let transfer_len = write_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
spi.read_fifo_unchecked();
context.rx_progress += 1;
});
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn on_interrupt_transfer(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
let read_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let read_len = read_slice.len();
let write_slice = unsafe { context.tx_slice.get().unwrap() };
let write_len = write_slice.len();
let transfer_len = core::cmp::max(read_len, write_len);
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
if context.rx_progress < read_len {
read_slice[context.rx_progress] = spi.read_fifo_unchecked();
} else {
spi.read_fifo_unchecked();
}
context.rx_progress += 1;
});
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
if context.tx_progress < write_len {
spi.write_fifo_unchecked(write_slice[context.tx_progress]);
} else {
// Dummy write.
spi.write_fifo_unchecked(0);
}
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn on_interrupt_transfer_in_place(
idx: usize,
context: &mut TransferContext,
spi: &mut SpiLowLevel,
mut isr: InterruptStatus,
) {
let transfer_slice = unsafe { context.rx_slice.get_mut().unwrap() };
let transfer_len = transfer_slice.len();
// Read data from RX FIFO first.
let read_len = calculate_read_len(spi, isr, transfer_len, context.rx_progress);
(0..read_len).for_each(|_| {
transfer_slice[context.rx_progress] = spi.read_fifo_unchecked();
context.rx_progress += 1;
});
// Data still needs to be sent
while context.tx_progress < transfer_len && !isr.tx_full() {
spi.write_fifo_unchecked(transfer_slice[context.tx_progress]);
context.tx_progress += 1;
isr = spi.read_isr();
}
isr_finish_handler(idx, spi, context, transfer_len)
}
fn calculate_read_len(
spi: &mut SpiLowLevel,
isr: InterruptStatus,
total_read_len: usize,
rx_progress: usize,
) -> usize {
if isr.rx_full() {
core::cmp::min(FIFO_DEPTH, total_read_len - rx_progress)
} else if isr.rx_not_empty() {
let trigger = spi.read_rx_not_empty_threshold();
core::cmp::min(total_read_len - rx_progress, trigger as usize)
} else {
0
}
}
/// Generic handler after RX FIFO and TX FIFO were handled. Checks and handles finished
/// and unfinished conditions.
fn isr_finish_handler(
idx: usize,
spi: &mut SpiLowLevel,
context: &mut TransferContext,
transfer_len: usize,
) {
// Transfer finish condition.
if context.rx_progress == context.tx_progress && context.rx_progress == transfer_len {
finish_transfer(idx, context, spi);
return;
}
unfinished_transfer(spi, transfer_len, context.rx_progress);
// If the transfer is done, the context structure was already written back.
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = *context;
});
}
fn finish_transfer(idx: usize, context: &mut TransferContext, spi: &mut SpiLowLevel) {
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = *context;
});
spi.set_rx_fifo_trigger(1).unwrap();
spi.set_tx_fifo_trigger(1).unwrap();
// Interrupts were already disabled and cleared.
DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
WAKERS[idx].wake();
}
fn unfinished_transfer(spi: &mut SpiLowLevel, transfer_len: usize, rx_progress: usize) {
let new_trig_level = core::cmp::min(FIFO_DEPTH, transfer_len - rx_progress);
spi.set_rx_fifo_trigger(new_trig_level as u32).unwrap();
// Re-enable interrupts with the new RX FIFO trigger level.
spi.enable_interrupts();
}
#[derive(Debug, Clone, Copy)]
pub enum TransferType {
Read,
Write,
Transfer,
TransferInPlace,
}
#[derive(Default, Debug, Copy, Clone)]
pub struct TransferContext {
transfer_type: Option<TransferType>,
tx_progress: usize,
rx_progress: usize,
tx_slice: RawBufSlice,
rx_slice: RawBufSliceMut,
}
#[allow(clippy::new_without_default)]
impl TransferContext {
pub const fn new() -> Self {
Self {
transfer_type: None,
tx_progress: 0,
rx_progress: 0,
tx_slice: RawBufSlice::new_nulled(),
rx_slice: RawBufSliceMut::new_nulled(),
}
}
}
pub struct SpiFuture {
id: super::SpiId,
spi: super::SpiLowLevel,
config: super::Config,
finished_regularly: core::cell::Cell<bool>,
}
impl SpiFuture {
fn new_for_read(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
if words.is_empty() {
panic!("words length unexpectedly 0");
}
let idx = spi_id as usize;
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
spi.inner.disable_interrupts();
let write_idx = core::cmp::min(super::FIFO_DEPTH, words.len());
// Send dummy bytes.
(0..write_idx).for_each(|_| {
spi.inner.write_fifo_unchecked(0);
});
Self::set_triggers(spi, write_idx, words.len());
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
spi.issue_manual_start_for_manual_cfg();
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Read);
unsafe {
context.rx_slice.set(words);
}
context.tx_slice.set_null();
context.tx_progress = write_idx;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_write(spi: &mut Spi, spi_id: SpiId, words: &[u8]) -> Self {
if words.is_empty() {
panic!("words length unexpectedly 0");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Write);
unsafe {
context.tx_slice.set(words);
}
context.rx_slice.set_null();
context.tx_progress = write_idx;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_transfer(spi: &mut Spi, spi_id: SpiId, read: &mut [u8], write: &[u8]) -> Self {
if read.is_empty() || write.is_empty() {
panic!("read or write buffer unexpectedly empty");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, write);
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::Transfer);
unsafe {
context.tx_slice.set(write);
context.rx_slice.set(read);
}
context.tx_progress = write_idx;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
finished_regularly: core::cell::Cell::new(false),
}
}
fn new_for_transfer_in_place(spi: &mut Spi, spi_id: SpiId, words: &mut [u8]) -> Self {
if words.is_empty() {
panic!("read and write buffer unexpectedly empty");
}
let (idx, write_idx) = Self::generic_init_transfer(spi, spi_id, words);
critical_section::with(|cs| {
let context_ref = TRANSFER_CONTEXTS[idx].borrow(cs);
let mut context = context_ref.borrow_mut();
context.transfer_type = Some(TransferType::TransferInPlace);
unsafe {
context.rx_slice.set(words);
}
context.tx_slice.set_null();
context.tx_progress = write_idx;
context.rx_progress = 0;
spi.inner.clear_interrupts();
spi.inner.enable_interrupts();
spi.inner.enable();
});
Self {
id: spi_id,
config: spi.config,
spi: unsafe { spi.inner.clone() },
finished_regularly: core::cell::Cell::new(false),
}
}
fn generic_init_transfer(spi: &mut Spi, spi_id: SpiId, write: &[u8]) -> (usize, usize) {
let idx = spi_id as usize;
DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
spi.inner.disable();
spi.inner.disable_interrupts();
let write_idx = core::cmp::min(super::FIFO_DEPTH, write.len());
(0..write_idx).for_each(|idx| {
spi.inner.write_fifo_unchecked(write[idx]);
});
Self::set_triggers(spi, write_idx, write.len());
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
spi.issue_manual_start_for_manual_cfg();
(idx, write_idx)
}
fn set_triggers(spi: &mut Spi, write_idx: usize, write_len: usize) {
// This should never fail because it is never larger than the FIFO depth.
spi.inner.set_rx_fifo_trigger(write_idx as u32).unwrap();
// We want to re-fill the TX FIFO before it is completely empty if the full transfer size
// is larger than the FIFO depth. I am not sure whether the default value of 1 ensures
// this because the TMR says that this interrupt is triggered when the FIFO has less than
// threshold entries.
if write_len > super::FIFO_DEPTH {
spi.inner.set_tx_fifo_trigger(2).unwrap();
}
}
}
impl Future for SpiFuture {
type Output = ();
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
WAKERS[self.id as usize].register(cx.waker());
if DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
critical_section::with(|cs| {
let mut ctx = TRANSFER_CONTEXTS[self.id as usize].borrow(cs).borrow_mut();
*ctx = TransferContext::default();
});
self.finished_regularly.set(true);
return core::task::Poll::Ready(());
}
core::task::Poll::Pending
}
}
impl Drop for SpiFuture {
fn drop(&mut self) {
if !self.finished_regularly.get() {
// It might be sufficient to disable and enable the SPI.. But this definitely
// ensures the SPI is fully reset.
self.spi.reset_and_reconfigure(self.config);
}
}
}
/// Asynchronous SPI driver.
///
/// This is the primary data structure used to perform non-blocking SPI operations.
/// It implements the [embedded_hal_async::spi::SpiBus] as well.
pub struct SpiAsync(pub Spi);
impl SpiAsync {
pub fn new(spi: Spi) -> Self {
Self(spi)
}
async fn read(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_read(&mut self.0, id, words);
spi_fut.await;
}
async fn write(&mut self, words: &[u8]) {
if words.is_empty() {
return;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_write(&mut self.0, id, words);
spi_fut.await;
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
if read.is_empty() || write.is_empty() {
return;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_transfer(&mut self.0, id, read, write);
spi_fut.await;
}
async fn transfer_in_place(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
let id = self.0.inner.id;
let spi_fut = SpiFuture::new_for_transfer_in_place(&mut self.0, id, words);
spi_fut.await;
}
}
impl embedded_hal_async::spi::ErrorType for SpiAsync {
type Error = Infallible;
}
impl embedded_hal_async::spi::SpiBus for SpiAsync {
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.read(words).await;
Ok(())
}
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.write(words).await;
Ok(())
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.transfer(read, write).await;
Ok(())
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.transfer_in_place(words).await;
Ok(())
}
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
/// This structure is a wrapper for [SpiAsync] which implements the
/// [embedded_hal_async::spi::SpiDevice] trait as well.
pub struct SpiWithHwCsAsync<Delay: embedded_hal_async::delay::DelayNs> {
pub spi: SpiAsync,
pub cs: ChipSelect,
pub delay: Delay,
}
impl<Delay: embedded_hal_async::delay::DelayNs> SpiWithHwCsAsync<Delay> {
pub fn new(spi: SpiAsync, cs: ChipSelect, delay: Delay) -> Self {
Self { spi, cs, delay }
}
pub fn release(self) -> SpiAsync {
self.spi
}
}
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::ErrorType
for SpiWithHwCsAsync<Delay>
{
type Error = Infallible;
}
impl<Delay: embedded_hal_async::delay::DelayNs> embedded_hal_async::spi::SpiDevice
for SpiWithHwCsAsync<Delay>
{
async fn transaction(
&mut self,
operations: &mut [embedded_hal::spi::Operation<'_, u8>],
) -> Result<(), Self::Error> {
self.spi.0.inner.select_hw_cs(self.cs);
for op in operations {
match op {
embedded_hal::spi::Operation::Read(items) => {
self.spi.read(items).await;
}
embedded_hal::spi::Operation::Write(items) => {
self.spi.write(items).await;
}
embedded_hal::spi::Operation::Transfer(read, write) => {
self.spi.transfer(read, write).await;
}
embedded_hal::spi::Operation::TransferInPlace(items) => {
self.spi.transfer_in_place(items).await;
}
embedded_hal::spi::Operation::DelayNs(delay) => {
self.delay.delay_ns(*delay).await;
}
}
}
self.spi.flush().await?;
self.spi.0.inner.no_hw_cs();
Ok(())
}
}

1222
zynq7000-hal/src/spi/mod.rs Normal file

File diff suppressed because it is too large Load Diff

26
zynq7000-hal/src/time.rs Normal file
View File

@ -0,0 +1,26 @@
//! Time units
// Frequency based
/// Hertz
pub type Hertz = fugit::HertzU32;
/// KiloHertz
pub type KiloHertz = fugit::KilohertzU32;
/// MegaHertz
pub type MegaHertz = fugit::MegahertzU32;
// Period based
/// Seconds
pub type Seconds = fugit::SecsDurationU32;
/// Milliseconds
pub type Milliseconds = fugit::MillisDurationU32;
/// Microseconds
pub type Microseconds = fugit::MicrosDurationU32;
/// Nanoseconds
pub type Nanoseconds = fugit::NanosDurationU32;

341
zynq7000-hal/src/ttc.rs Normal file
View File

@ -0,0 +1,341 @@
//! Triple-timer counter (TTC) high-level driver.
//!
//! This module also contains support for PWM and output waveform generation.
use core::convert::Infallible;
use arbitrary_int::{Number, u3, u4};
use zynq7000::ttc::{MmioTtc, TTC_0_BASE_ADDR, TTC_1_BASE_ADDR};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::{Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43};
use crate::{
clocks::ArmClocks,
gpio::{IoPeriph, IoPeriphPin, Mio28, Mio29, Mio30, Mio31, MioPin, MuxConf, PinMode},
time::Hertz,
};
/// Each TTC consists of three independent timers/counters.
#[derive(Debug, Copy, Clone)]
pub enum TtcId {
Ttc0 = 0,
Ttc1 = 1,
}
#[derive(Debug, Copy, Clone)]
pub enum ChannelId {
Ch0 = 0,
Ch1 = 1,
Ch2 = 2,
}
pub trait PsTtc {
fn reg_block(&self) -> MmioTtc<'static>;
fn id(&self) -> Option<TtcId>;
}
impl PsTtc for MmioTtc<'static> {
#[inline]
fn reg_block(&self) -> MmioTtc<'static> {
unsafe { self.clone() }
}
#[inline]
fn id(&self) -> Option<TtcId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == TTC_0_BASE_ADDR {
return Some(TtcId::Ttc0);
} else if base_addr == TTC_1_BASE_ADDR {
return Some(TtcId::Ttc1);
}
None
}
}
pub const TTC_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b110));
pub trait ClockInPin: IoPeriphPin {
const ID: TtcId;
}
pub trait WaveOutPin: IoPeriphPin {
const ID: TtcId;
}
macro_rules! into_ttc {
($($Mio:ident),+) => {
$(
impl <M: PinMode> MioPin<$Mio, M> {
/// Convert the pin into a TTC pin by configuring the pin routing via the
/// MIO multiplexer bits.
pub fn into_ttck(self) -> MioPin<$Mio, IoPeriph> {
self.into_io_periph(TTC_MUX_CONF, None)
}
}
)+
};
}
#[cfg(not(feature = "7z010-7z007s-clg225"))]
into_ttc!(Mio16, Mio17, Mio18, Mio19, Mio40, Mio41, Mio42, Mio43);
into_ttc!(Mio28, Mio29, Mio30, Mio31);
pub struct Ttc {
pub ch0: TtcChannel,
pub ch1: TtcChannel,
pub ch2: TtcChannel,
}
impl Ttc {
/// Create a new TTC instance. The passed TTC peripheral instance MUST point to a valid
/// processing system TTC peripheral.
///
/// Returns [None] if the passed peripheral block does not have a valid PS TTC address.
pub fn new(ps_ttc: impl PsTtc) -> Option<Self> {
ps_ttc.id()?;
let regs = ps_ttc.reg_block();
let ch0 = TtcChannel {
regs: unsafe { regs.clone() },
id: ChannelId::Ch0,
};
let ch1 = TtcChannel {
regs: unsafe { regs.clone() },
id: ChannelId::Ch1,
};
let ch2 = TtcChannel {
regs,
id: ChannelId::Ch2,
};
Some(Self { ch0, ch1, ch2 })
}
}
pub struct TtcChannel {
regs: MmioTtc<'static>,
id: ChannelId,
}
impl TtcChannel {
pub fn regs_mut(&mut self) -> &mut MmioTtc<'static> {
&mut self.regs
}
#[inline]
pub fn read_counter(&self) -> u16 {
self.regs
.read_current_counter(self.id as usize)
.unwrap()
.count()
}
pub fn id(&self) -> ChannelId {
self.id
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid TTC pin configuration")]
pub struct InvalidTtcPinConfigError(pub MuxConf);
#[derive(Debug, thiserror::Error)]
#[error("frequency is zero")]
pub struct FrequencyIsZeroError;
#[derive(Debug, thiserror::Error)]
pub enum TtcConstructionError {
#[error("invalid TTC pin configuration")]
InvalidTtcPinConfig(#[from] InvalidTtcPinConfigError),
#[error("frequency is zero")]
FrequencyIsZero(#[from] FrequencyIsZeroError),
}
pub fn calculate_prescaler_reg_and_interval_ticks(
mut ref_clk: Hertz,
freq: Hertz,
) -> (Option<u4>, u16) {
// TODO: Can this be optimized?
let mut prescaler_reg: Option<u4> = None;
let mut tick_val = ref_clk / freq;
while tick_val > u16::MAX as u32 {
ref_clk /= 2;
if let Some(prescaler_reg) = prescaler_reg {
// TODO: Better error handling for this case? Can this even happen?
if prescaler_reg.value() == u4::MAX.value() {
break;
} else {
prescaler_reg.checked_add(u4::new(1));
}
} else {
prescaler_reg = Some(u4::new(0));
}
tick_val = ref_clk / freq;
}
(prescaler_reg, tick_val as u16)
}
pub struct Pwm {
channel: TtcChannel,
ref_clk: Hertz,
}
impl Pwm {
/// Create a new PWM instance which uses the CPU 1x clock as the clock source and also uses
/// a MIO output pin for the waveform output.
pub fn new_with_cpu_clk_and_mio_waveout(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
wave_out: impl WaveOutPin,
) -> Result<Self, TtcConstructionError> {
if wave_out.mux_conf() != TTC_MUX_CONF {
return Err(InvalidTtcPinConfigError(wave_out.mux_conf()).into());
}
Ok(Self::new_with_cpu_clk(channel, arm_clocks, freq)?)
}
/// Create a new PWM instance which uses the CPU 1x clock as the clock source.
pub fn new_with_cpu_clk(
channel: TtcChannel,
arm_clocks: &ArmClocks,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
Self::new_generic(channel, arm_clocks.cpu_1x_clk(), freq)
}
/// Create a new PWM instance based on a reference clock source.
pub fn new_generic(
channel: TtcChannel,
ref_clk: Hertz,
freq: Hertz,
) -> Result<Self, FrequencyIsZeroError> {
if freq.raw() == 0 {
return Err(FrequencyIsZeroError);
}
let (prescaler_reg, tick_val) = calculate_prescaler_reg_and_interval_ticks(ref_clk, freq);
let id = channel.id() as usize;
let mut pwm = Self { channel, ref_clk };
pwm.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(pwm)
}
/// Set a new frequency for the PWM cycle.
///
/// This resets the duty cycle to 0%.
pub fn set_frequency(&mut self, freq: Hertz) -> Result<(), FrequencyIsZeroError> {
if freq.raw() == 0 {
return Err(FrequencyIsZeroError);
}
let id = self.channel.id() as usize;
let (prescaler_reg, tick_val) =
calculate_prescaler_reg_and_interval_ticks(self.ref_clk, freq);
self.set_up_and_configure_pwm(id, prescaler_reg, tick_val);
Ok(())
}
#[inline]
pub fn ttc_channel_mut(&mut self) -> &mut TtcChannel {
&mut self.channel
}
#[inline]
pub fn max_duty_cycle(&self) -> u16 {
self.channel
.regs
.read_interval_value(self.channel.id() as usize)
.unwrap()
.value()
}
#[inline]
pub fn set_duty_cycle(&mut self, duty: u16) {
let id = self.channel.id() as usize;
self.channel
.regs
.modify_cnt_ctrl(id, |mut val| {
val.set_disable(true);
val
})
.unwrap();
self.channel
.regs
.write_match_value_0(
self.channel.id() as usize,
zynq7000::ttc::RwValue::new_with_raw_value(duty as u32),
)
.unwrap();
self.channel
.regs
.modify_cnt_ctrl(id, |mut val| {
val.set_disable(false);
val.set_reset(true);
val
})
.unwrap();
}
fn set_up_and_configure_pwm(&mut self, id: usize, prescaler_reg: Option<u4>, tick_val: u16) {
// Disable the counter first.
self.channel
.regs
.write_cnt_ctrl(id, zynq7000::ttc::CounterControl::new_with_raw_value(1))
.unwrap();
// Clock configuration
self.channel
.regs
.write_clk_cntr(
id,
zynq7000::ttc::ClockControl::builder()
.with_ext_clk_edge(false)
.with_clk_src(zynq7000::ttc::ClockSource::Pclk)
.with_prescaler(prescaler_reg.unwrap_or(u4::new(0)))
.with_prescale_enable(prescaler_reg.is_some())
.build(),
)
.unwrap();
self.channel
.regs
.write_interval_value(
id,
zynq7000::ttc::RwValue::new_with_raw_value(tick_val as u32),
)
.unwrap();
// Corresponds to duty cycle 0.
self.channel
.regs
.write_match_value_0(id, zynq7000::ttc::RwValue::new_with_raw_value(0))
.unwrap();
self.channel
.regs
.write_cnt_ctrl(
id,
zynq7000::ttc::CounterControl::builder()
.with_wave_polarity(zynq7000::ttc::WavePolarity::LowToHighOnMatch1)
.with_wave_enable_n(zynq7000::ttc::WaveEnable::Enable)
.with_reset(true)
.with_match_enable(true)
.with_decrementing(false)
.with_mode(zynq7000::ttc::Mode::Interval)
.with_disable(false)
.build(),
)
.unwrap();
}
}
impl embedded_hal::pwm::ErrorType for Pwm {
type Error = Infallible;
}
impl embedded_hal::pwm::SetDutyCycle for Pwm {
#[inline]
fn max_duty_cycle(&self) -> u16 {
self.max_duty_cycle()
}
#[inline]
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
self.set_duty_cycle(duty);
Ok(())
}
}

View File

@ -0,0 +1,739 @@
//! # UART module.
//!
//! Support for the processing system UARTs.
use core::convert::Infallible;
use arbitrary_int::u3;
use libm::round;
use zynq7000::{
slcr::reset::DualRefAndClockReset,
uart::{
BaudRateDiv, Baudgen, ChMode, ClkSel, FifoTrigger, InterruptControl, MmioUart, Mode,
UART_0_BASE, UART_1_BASE,
},
};
use crate::{
enable_amba_peripheral_clock,
gpio::{
IoPeriph, IoPeriphPin, Mio8, Mio9, Mio10, Mio11, Mio12, Mio13, Mio14, Mio15, Mio28, Mio29,
Mio30, Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39, Mio48, Mio49, Mio52,
Mio53, MioPin, MuxConf, PinMode,
},
slcr::Slcr,
};
#[cfg(not(feature = "7z010-7z007s-clg225"))]
use crate::gpio::{
Mio16, Mio17, Mio18, Mio19, Mio20, Mio21, Mio22, Mio23, Mio24, Mio25, Mio26, Mio27, Mio40,
Mio41, Mio42, Mio43, Mio44, Mio45, Mio46, Mio47, Mio50, Mio51,
};
use super::{clocks::IoClocks, time::Hertz};
pub mod tx;
pub use tx::*;
pub mod tx_async;
pub use tx_async::*;
pub mod rx;
pub use rx::*;
pub const FIFO_DEPTH: usize = 64;
pub const DEFAULT_RX_TRIGGER_LEVEL: u8 = 32;
pub const UART_MUX_CONF: MuxConf = MuxConf::new_with_l3(u3::new(0b111));
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum UartId {
Uart0 = 0,
Uart1 = 1,
}
pub trait PsUart {
fn reg_block(&self) -> MmioUart<'static>;
fn uart_id(&self) -> Option<UartId>;
}
impl PsUart for MmioUart<'static> {
#[inline]
fn reg_block(&self) -> MmioUart<'static> {
unsafe { self.clone() }
}
fn uart_id(&self) -> Option<UartId> {
let base_addr = unsafe { self.ptr() } as usize;
if base_addr == UART_0_BASE {
return Some(UartId::Uart0);
} else if base_addr == UART_1_BASE {
return Some(UartId::Uart1);
}
None
}
}
impl UartId {
/// Unsafely steal a peripheral MMIO block for the given UART.
///
/// # Safety
///
/// Circumvents ownership and safety guarantees by the HAL.
pub const unsafe fn regs(&self) -> MmioUart<'static> {
match self {
UartId::Uart0 => unsafe { zynq7000::uart::Uart::new_mmio_fixed_0() },
UartId::Uart1 => unsafe { zynq7000::uart::Uart::new_mmio_fixed_1() },
}
}
}
pub trait RxPin {
const UART_IDX: UartId;
}
pub trait TxPin {
const UART_IDX: UartId;
}
pub trait UartPins {}
#[derive(Debug, thiserror::Error)]
#[error("divisor is zero")]
pub struct DivisorZero;
macro_rules! pin_pairs {
($UartPeriph:path, ($( [$(#[$meta:meta], )? $TxMio:ident, $RxMio:ident] ),+ $(,)? )) => {
$(
$( #[$meta] )?
impl TxPin for MioPin<$TxMio, IoPeriph> {
const UART_IDX: UartId = $UartPeriph;
}
$( #[$meta] )?
impl RxPin for MioPin<$RxMio, IoPeriph> {
const UART_IDX: UartId = $UartPeriph;
}
impl UartPins for (MioPin<$TxMio, IoPeriph>, MioPin<$RxMio, IoPeriph>) {}
)+
};
}
macro_rules! impl_into_uart {
(($($Mio:ident),+)) => {
$(
impl<M: PinMode> MioPin<$Mio, M> {
pub fn into_uart(self) -> MioPin<$Mio, IoPeriph> {
self.into_io_periph(UART_MUX_CONF, None)
}
}
)+
};
}
impl_into_uart!((
Mio10, Mio11, Mio15, Mio14, Mio31, Mio30, Mio35, Mio34, Mio39, Mio38, Mio8, Mio9, Mio12, Mio13,
Mio28, Mio29, Mio32, Mio33, Mio36, Mio37, Mio48, Mio49, Mio52, Mio53
));
#[cfg(not(feature = "7z010-7z007s-clg225"))]
impl_into_uart!((
Mio19, Mio18, Mio23, Mio22, Mio43, Mio42, Mio47, Mio46, Mio51, Mio50, Mio16, Mio17, Mio20,
Mio21, Mio24, Mio25, Mio40, Mio41, Mio44, Mio45
));
pin_pairs!(
UartId::Uart0,
(
[Mio11, Mio10],
[Mio15, Mio14],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio19, Mio18],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio23, Mio22],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio27, Mio26],
[Mio31, Mio30],
[Mio35, Mio34],
[Mio39, Mio38],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio43, Mio42],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio47, Mio46],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio51, Mio50],
)
);
pin_pairs!(
UartId::Uart1,
(
[Mio8, Mio9],
[Mio12, Mio13],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio16, Mio17],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio20, Mio21],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio24, Mio25],
[Mio28, Mio29],
[Mio32, Mio33],
[Mio36, Mio37],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio40, Mio41],
[#[cfg(not(feature ="7z010-7z007s-clg225"))], Mio44, Mio45],
[Mio48, Mio49],
[Mio52, Mio53],
)
);
/// Based on values provided by the vendor library.
pub const MAX_BAUD_RATE: u32 = 6240000;
/// Based on values provided by the vendor library.
pub const MIN_BAUD_RATE: u32 = 110;
#[derive(Debug, Default, Clone, Copy)]
pub enum Parity {
Even,
Odd,
#[default]
None,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum Stopbits {
#[default]
One,
OnePointFive,
Two,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum CharLen {
SixBits,
SevenBits,
#[default]
EightBits,
}
#[derive(Debug, Clone, Copy)]
pub struct ClkConfigRaw {
cd: u16,
bdiv: u8,
}
#[cfg(feature = "alloc")]
pub fn calculate_viable_configs(
mut uart_clk: Hertz,
clk_sel: ClkSel,
target_baud: u32,
) -> alloc::vec::Vec<(ClkConfigRaw, f64)> {
let mut viable_cfgs = alloc::vec::Vec::new();
if clk_sel == ClkSel::UartRefClkDiv8 {
uart_clk /= 8;
}
let mut current_clk_config = ClkConfigRaw::new(0, 0);
for bdiv in 4..u8::MAX {
let cd =
round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64;
if cd > u16::MAX as u64 {
continue;
}
current_clk_config.cd = cd as u16;
current_clk_config.bdiv = bdiv;
let baud = current_clk_config.actual_baud(uart_clk);
let error = ((baud - target_baud as f64).abs() / target_baud as f64) * 100.0;
if error < MAX_BAUDERROR_RATE as f64 {
viable_cfgs.push((current_clk_config, error));
}
}
viable_cfgs
}
/// Calculate the clock configuration for the smallest error to reach the desired target
/// baud rate.
///
/// You can also use [calculate_viable_configs] to get a list of all viable configurations.
pub fn calculate_raw_baud_cfg_smallest_error(
mut uart_clk: Hertz,
clk_sel: ClkSel,
target_baud: u32,
) -> Result<(ClkConfigRaw, f64), DivisorZero> {
if target_baud == 0 {
return Err(DivisorZero);
}
if clk_sel == ClkSel::UartRefClkDiv8 {
uart_clk /= 8;
}
let mut current_clk_config = ClkConfigRaw::default();
let mut best_clk_config = ClkConfigRaw::default();
let mut smallest_error: f64 = 100.0;
for bdiv in 4..u8::MAX {
let cd =
round(uart_clk.raw() as f64 / ((bdiv as u32 + 1) as f64 * target_baud as f64)) as u64;
if cd > u16::MAX as u64 {
continue;
}
current_clk_config.cd = cd as u16;
current_clk_config.bdiv = bdiv;
let baud = current_clk_config.actual_baud(uart_clk);
let error = ((baud - target_baud as f64).abs() / target_baud as f64) * 100.0;
if error < smallest_error {
best_clk_config = current_clk_config;
smallest_error = error;
}
}
Ok((best_clk_config, smallest_error))
}
impl ClkConfigRaw {
#[inline]
pub const fn new(cd: u16, bdiv: u8) -> Result<Self, DivisorZero> {
if cd == 0 {
return Err(DivisorZero);
}
Ok(ClkConfigRaw { cd, bdiv })
}
/// Auto-calculates the best clock configuration settings for the target baudrate.
///
/// This function assumes [ClkSel::UartRefClk] as the clock source. It returns a tuple
/// where the first entry is the clock configuration while the second entry is the associated
/// baud error from 0.0 to 1.0. It is recommended to keep this error below 2-3 %.
pub fn new_autocalc_with_error(
io_clks: &IoClocks,
target_baud: u32,
) -> Result<(Self, f64), DivisorZero> {
Self::new_autocalc_generic(io_clks, ClkSel::UartRefClk, target_baud)
}
pub fn new_autocalc_generic(
io_clks: &IoClocks,
clk_sel: ClkSel,
target_baud: u32,
) -> Result<(Self, f64), DivisorZero> {
Self::new_autocalc_with_raw_clk(io_clks.uart_clk(), clk_sel, target_baud)
}
pub fn new_autocalc_with_raw_clk(
uart_clk: Hertz,
clk_sel: ClkSel,
target_baud: u32,
) -> Result<(Self, f64), DivisorZero> {
calculate_raw_baud_cfg_smallest_error(uart_clk, clk_sel, target_baud)
}
#[inline]
pub const fn cd(&self) -> u16 {
self.cd
}
#[inline]
pub const fn bdiv(&self) -> u8 {
self.bdiv
}
#[inline]
pub fn rounded_baud(&self, sel_clk: Hertz) -> u32 {
round(self.actual_baud(sel_clk)) as u32
}
#[inline]
pub fn actual_baud(&self, sel_clk: Hertz) -> f64 {
sel_clk.raw() as f64 / (self.cd as f64 * (self.bdiv + 1) as f64)
}
}
impl Default for ClkConfigRaw {
#[inline]
fn default() -> Self {
ClkConfigRaw::new(1, 0).unwrap()
}
}
#[derive(Debug)]
pub struct UartConfig {
clk_config: ClkConfigRaw,
chmode: ChMode,
parity: Parity,
stopbits: Stopbits,
chrl: CharLen,
clk_sel: ClkSel,
}
impl UartConfig {
pub fn new_with_clk_config(clk_config: ClkConfigRaw) -> Self {
Self::new(
clk_config,
ChMode::default(),
Parity::default(),
Stopbits::default(),
CharLen::default(),
ClkSel::default(),
)
}
#[inline]
pub const fn new(
clk_config: ClkConfigRaw,
chmode: ChMode,
parity: Parity,
stopbits: Stopbits,
chrl: CharLen,
clk_sel: ClkSel,
) -> Self {
UartConfig {
clk_config,
chmode,
parity,
stopbits,
chrl,
clk_sel,
}
}
#[inline]
pub const fn raw_clk_config(&self) -> ClkConfigRaw {
self.clk_config
}
#[inline]
pub const fn chmode(&self) -> ChMode {
self.chmode
}
#[inline]
pub const fn parity(&self) -> Parity {
self.parity
}
#[inline]
pub const fn stopbits(&self) -> Stopbits {
self.stopbits
}
#[inline]
pub const fn chrl(&self) -> CharLen {
self.chrl
}
#[inline]
pub const fn clksel(&self) -> ClkSel {
self.clk_sel
}
}
// TODO: Impl Debug
pub struct Uart {
rx: Rx,
tx: Tx,
cfg: UartConfig,
}
#[derive(Debug, thiserror::Error)]
#[error("invalid UART ID")]
pub struct InvalidPsUart;
#[derive(Debug, thiserror::Error)]
pub enum UartConstructionError {
#[error("invalid UART ID")]
InvalidPsUart(#[from] InvalidPsUart),
#[error("missmatch between pins index and passed index")]
IdxMissmatch,
#[error("invalid pin mux conf for UART")]
InvalidMuxConf(MuxConf),
}
impl Uart {
/// This is the constructor to use the PS UART with EMIO pins to route the UART into the PL
/// or expose them via the PL package pins.
///
/// A valid PL design which routes the UART pins through into the PL must be used for this to
/// work.
pub fn new_with_emio(uart: impl PsUart, cfg: UartConfig) -> Result<Uart, InvalidPsUart> {
if uart.uart_id().is_none() {
return Err(InvalidPsUart);
}
Ok(Self::new_generic_unchecked(
uart.reg_block(),
uart.uart_id().unwrap(),
cfg,
))
}
/// This is the constructor to use the PS UART with MIO pins.
pub fn new_with_mio<TxPinI: TxPin + IoPeriphPin, RxPinI: RxPin + IoPeriphPin>(
uart: impl PsUart,
cfg: UartConfig,
pins: (TxPinI, RxPinI),
) -> Result<Self, UartConstructionError>
where
(TxPinI, RxPinI): UartPins,
{
let id = uart.uart_id();
if id.is_none() {
return Err(InvalidPsUart.into());
}
if id.unwrap() != TxPinI::UART_IDX || id.unwrap() != RxPinI::UART_IDX {
return Err(UartConstructionError::IdxMissmatch);
}
if pins.0.mux_conf() != UART_MUX_CONF {
return Err(UartConstructionError::InvalidMuxConf(pins.0.mux_conf()));
}
if pins.1.mux_conf() != UART_MUX_CONF {
return Err(UartConstructionError::InvalidMuxConf(pins.1.mux_conf()));
}
Ok(Self::new_generic_unchecked(
uart.reg_block(),
id.unwrap(),
cfg,
))
}
/// This is the generic constructor used by all other constructors.
///
/// It does not do any pin checks and resource control. It is recommended to use the other
/// constructors instead.
pub fn new_generic_unchecked(
mut reg_block: MmioUart<'static>,
uart_id: UartId,
cfg: UartConfig,
) -> Uart {
let periph_sel = match uart_id {
UartId::Uart0 => crate::PeripheralSelect::Uart0,
UartId::Uart1 => crate::PeripheralSelect::Uart1,
};
enable_amba_peripheral_clock(periph_sel);
reset(uart_id);
reg_block.modify_cr(|mut v| {
v.set_tx_dis(true);
v.set_rx_dis(true);
v
});
// Disable all interrupts.
reg_block.write_idr(InterruptControl::new_with_raw_value(0xFFFF_FFFF));
let mode = Mode::builder()
.with_chmode(cfg.chmode)
.with_nbstop(match cfg.stopbits {
Stopbits::One => zynq7000::uart::Stopbits::One,
Stopbits::OnePointFive => zynq7000::uart::Stopbits::OnePointFive,
Stopbits::Two => zynq7000::uart::Stopbits::Two,
})
.with_par(match cfg.parity {
Parity::Even => zynq7000::uart::Parity::Even,
Parity::Odd => zynq7000::uart::Parity::Odd,
Parity::None => zynq7000::uart::Parity::NoParity,
})
.with_chrl(match cfg.chrl {
CharLen::SixBits => zynq7000::uart::Chrl::SixBits,
CharLen::SevenBits => zynq7000::uart::Chrl::SevenBits,
CharLen::EightBits => zynq7000::uart::Chrl::EightBits,
})
.with_clksel(cfg.clk_sel)
.build();
reg_block.write_mr(mode);
reg_block.write_baudgen(
Baudgen::builder()
.with_cd(cfg.raw_clk_config().cd())
.build(),
);
reg_block.write_baud_rate_div(
BaudRateDiv::builder()
.with_bdiv(cfg.raw_clk_config().bdiv())
.build(),
);
// Soft reset for both TX and RX.
reg_block.modify_cr(|mut v| {
v.set_tx_rst(true);
v.set_rx_rst(true);
v
});
// Write default value.
reg_block.write_rx_fifo_trigger(FifoTrigger::new_with_raw_value(
DEFAULT_RX_TRIGGER_LEVEL as u32,
));
// Enable TX and RX.
reg_block.modify_cr(|mut v| {
v.set_tx_dis(false);
v.set_rx_dis(false);
v.set_tx_en(true);
v.set_rx_en(true);
v
});
Uart {
rx: Rx {
regs: unsafe { reg_block.clone() },
},
tx: Tx {
regs: reg_block,
idx: uart_id,
},
cfg,
}
}
#[inline]
pub fn set_mode(&mut self, mode: ChMode) {
self.regs().modify_mr(|mut mr| {
mr.set_chmode(mode);
mr
});
}
#[inline]
pub const fn regs(&mut self) -> &mut MmioUart<'static> {
&mut self.rx.regs
}
#[inline]
pub const fn cfg(&self) -> &UartConfig {
&self.cfg
}
#[inline]
pub const fn split(self) -> (Tx, Rx) {
(self.tx, self.rx)
}
}
impl embedded_hal_nb::serial::ErrorType for Uart {
type Error = Infallible;
}
impl embedded_hal_nb::serial::Write for Uart {
#[inline]
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
self.tx.write(word)
}
fn flush(&mut self) -> nb::Result<(), Self::Error> {
self.tx.flush()
}
}
impl embedded_hal_nb::serial::Read for Uart {
/// Read one byte from the FIFO.
///
/// This operation is infallible because pulling an available byte from the FIFO
/// always succeeds. If you want to be informed about RX errors, you should look at the
/// non-blocking API using interrupts, which also tracks the RX error bits.
#[inline]
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.rx.read()
}
}
impl embedded_io::ErrorType for Uart {
type Error = Infallible;
}
impl embedded_io::Write for Uart {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.tx.write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.tx.flush()
}
}
impl embedded_io::Read for Uart {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.rx.read(buf)
}
}
/// Reset the UART peripheral using the SLCR reset register for UART.
///
/// Please note that this function will interfere with an already configured
/// UART instance.
#[inline]
pub fn reset(id: UartId) {
let assert_reset = match id {
UartId::Uart0 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(false)
.with_periph0_ref_rst(true)
.with_periph1_cpu1x_rst(false)
.with_periph0_cpu1x_rst(true)
.build(),
UartId::Uart1 => DualRefAndClockReset::builder()
.with_periph1_ref_rst(true)
.with_periph0_ref_rst(false)
.with_periph1_cpu1x_rst(true)
.with_periph0_cpu1x_rst(false)
.build(),
};
unsafe {
Slcr::with(|regs| {
regs.reset_ctrl().write_uart(assert_reset);
// Keep it in reset for one cycle.. not sure if this is necessary.
cortex_ar::asm::nop();
regs.reset_ctrl().write_uart(DualRefAndClockReset::DEFAULT);
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::abs_diff_eq;
use fugit::HertzU32;
use zynq7000::uart::ClkSel;
const REF_UART_CLK: HertzU32 = HertzU32::from_raw(50_000_000);
const REF_UART_CLK_DIV_8: HertzU32 = HertzU32::from_raw(6_250_000);
#[test]
fn test_error_calc_0() {
// Baud 600
let cfg_0 = ClkConfigRaw::new(10417, 7).unwrap();
let actual_baud_0 = cfg_0.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud_0, 599.980, epsilon = 0.01));
}
#[test]
fn test_error_calc_1() {
// Baud 9600
let cfg = ClkConfigRaw::new(81, 7).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK_DIV_8);
assert!(abs_diff_eq!(actual_baud, 9645.061, epsilon = 0.01));
}
#[test]
fn test_error_calc_2() {
// Baud 9600
let cfg = ClkConfigRaw::new(651, 7).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 9600.614, epsilon = 0.01));
}
#[test]
fn test_error_calc_3() {
// Baud 28800
let cfg = ClkConfigRaw::new(347, 4).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 28818.44, epsilon = 0.01));
}
#[test]
fn test_error_calc_4() {
// Baud 921600
let cfg = ClkConfigRaw::new(9, 5).unwrap();
let actual_baud = cfg.actual_baud(REF_UART_CLK);
assert!(abs_diff_eq!(actual_baud, 925925.92, epsilon = 0.01));
}
#[test]
fn test_best_calc_0() {
let result = ClkConfigRaw::new_autocalc_with_raw_clk(REF_UART_CLK, ClkSel::UartRefClk, 600);
assert!(result.is_ok());
let (cfg, _error) = result.unwrap();
assert_eq!(cfg.cd(), 499);
assert_eq!(cfg.bdiv(), 166);
}
#[test]
#[cfg(feature = "alloc")]
fn test_viable_config_calculation() {
let cfgs = calculate_viable_configs(REF_UART_CLK, ClkSel::UartRefClk, 115200);
assert!(
cfgs.iter()
.find(|(cfg, _error)| { cfg.cd() == 62 && cfg.bdiv() == 6 })
.is_some()
);
}
}

249
zynq7000-hal/src/uart/rx.rs Normal file
View File

@ -0,0 +1,249 @@
use core::convert::Infallible;
use arbitrary_int::Number;
use zynq7000::uart::{InterruptControl, InterruptStatus, MmioUart};
use super::FIFO_DEPTH;
pub struct Rx {
pub(crate) regs: MmioUart<'static>,
}
// TODO: Remove once this is impelemnted for MmioUart
unsafe impl Send for Rx {}
#[derive(Debug, Default, Clone, Copy)]
pub struct RxErrors {
framing: bool,
overrun: bool,
parity: bool,
}
impl RxErrors {
#[inline]
pub const fn framing(&self) -> bool {
self.framing
}
#[inline]
pub const fn overrun(&self) -> bool {
self.overrun
}
#[inline]
pub const fn parity(&self) -> bool {
self.parity
}
}
#[derive(Debug, Default)]
pub struct RxInterruptResult {
read_bytes: usize,
errors: Option<RxErrors>,
}
impl RxInterruptResult {
pub fn read_bytes(&self) -> usize {
self.read_bytes
}
pub fn errors(&self) -> Option<RxErrors> {
self.errors
}
}
impl Rx {
#[inline]
pub fn read_fifo(&mut self) -> nb::Result<u8, Infallible> {
if self.regs.read_sr().rx_empty() {
return Err(nb::Error::WouldBlock);
}
Ok(self.regs.read_fifo().fifo())
}
#[inline(always)]
pub fn read_fifo_unchecked(&mut self) -> u8 {
self.regs.read_fifo().fifo()
}
/// Write the receiver timeout value.
///
/// A value of 0 will disable the receiver timeout.
/// Otherwise, the 10-bit counter used by the receiver timeout mechanism of the UART will
/// load this value for the upper 8 bits on a reload. The counter is clocked by the UART
/// bit clock, so this value times 4 is the number of UART clock ticks until a timeout occurs.
#[inline]
pub fn set_rx_timeout_value(&mut self, rto: u8) {
self.regs.write_rx_tout(rto as u32);
}
#[inline]
pub fn soft_reset(&mut self) {
self.regs.modify_cr(|mut cr| {
cr.set_rx_rst(true);
cr
});
while self.regs.read_cr().rx_rst() {}
}
/// Helper function to start the interrupt driven reception of data.
///
/// This function will perform a soft-reset, clear RX related interrupts and then enable
/// all relevant interrupts for the RX side of the UART. These steps are recommended to have
/// a glitch-free start of the interrupt driven reception.
///
/// This should be called once at system start-up. After that, you only need to call
/// [Self::on_interrupt] in the interrupt handler for the UART peripheral.
pub fn start_interrupt_driven_reception(&mut self) {
self.soft_reset();
self.clear_interrupts();
self.enable_interrupts();
}
/// Enables all interrupts relevant for the RX side of the UART.
///
/// It is recommended to also clear all interrupts immediately after enabling them.
#[inline]
pub fn enable_interrupts(&mut self) {
self.regs.write_ier(
InterruptControl::builder()
.with_tx_over(false)
.with_tx_near_full(false)
.with_tx_trig(false)
.with_rx_dms(false)
.with_rx_timeout(true)
.with_rx_parity(true)
.with_rx_framing(true)
.with_rx_over(true)
.with_tx_full(false)
.with_tx_empty(false)
.with_rx_full(true)
.with_rx_empty(false)
.with_rx_trg(true)
.build(),
);
}
pub fn on_interrupt(
&mut self,
buf: &mut [u8; FIFO_DEPTH],
reset_rx_timeout: bool,
) -> RxInterruptResult {
let mut result = RxInterruptResult::default();
let imr = self.regs.read_imr();
if !imr.rx_full()
&& !imr.rx_trg()
&& !imr.rx_parity()
&& !imr.rx_framing()
&& !imr.rx_over()
&& !imr.rx_timeout()
{
return result;
}
let isr = self.regs.read_isr();
if isr.rx_full() {
// Read all bytes in the full RX fifo.
for byte in buf.iter_mut() {
*byte = self.read_fifo_unchecked();
}
result.read_bytes = FIFO_DEPTH;
} else if isr.rx_trg() {
// It is guaranteed that we can read the FIFO level amount of data
let fifo_trigger = self.regs.read_rx_fifo_trigger().trig().as_usize();
(0..fifo_trigger).for_each(|i| {
buf[i] = self.read_fifo_unchecked();
});
result.read_bytes = fifo_trigger;
}
// Read everything else that is available, as long as there is space left.
while result.read_bytes < buf.len() {
if let Ok(byte) = self.read_fifo() {
buf[result.read_bytes] = byte;
result.read_bytes += 1;
} else {
break;
}
}
// Handle error events.
if isr.rx_parity() || isr.rx_framing() || isr.rx_over() {
result.errors = Some(RxErrors {
framing: isr.rx_framing(),
overrun: isr.rx_over(),
parity: isr.rx_parity(),
});
}
// Handle timeout event.
if isr.rx_timeout() && reset_rx_timeout {
self.regs.modify_cr(|mut cr| {
cr.set_rstto(true);
cr
});
}
self.clear_interrupts();
result
}
// This clears all RX related interrupts.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs.write_isr(
InterruptStatus::builder()
.with_tx_over(false)
.with_tx_near_full(false)
.with_tx_trig(false)
.with_rx_dms(true)
.with_rx_timeout(true)
.with_rx_parity(true)
.with_rx_framing(true)
.with_rx_over(true)
.with_tx_full(false)
.with_tx_empty(false)
.with_rx_full(true)
.with_rx_empty(true)
.with_rx_trg(true)
.build(),
);
}
}
impl embedded_hal_nb::serial::ErrorType for Rx {
type Error = Infallible;
}
impl embedded_hal_nb::serial::Read for Rx {
/// Read one byte from the FIFO.
///
/// This operation is infallible because pulling an available byte from the FIFO
/// always succeeds. If you want to be informed about RX errors, you should look at the
/// non-blocking API using interrupts, which also tracks the RX error bits.
#[inline]
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.read_fifo()
}
}
impl embedded_io::ErrorType for Rx {
type Error = Infallible;
}
impl embedded_io::Read for Rx {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
if buf.is_empty() {
return Ok(0);
}
let mut read = 0;
loop {
if !self.regs.read_sr().rx_empty() {
break;
}
}
for byte in buf.iter_mut() {
match <Self as embedded_hal_nb::serial::Read<u8>>::read(self) {
Ok(w) => {
*byte = w;
read += 1;
}
Err(nb::Error::WouldBlock) => break,
}
}
Ok(read)
}
}

201
zynq7000-hal/src/uart/tx.rs Normal file
View File

@ -0,0 +1,201 @@
use core::convert::Infallible;
use zynq7000::uart::{Fifo, InterruptControl, InterruptStatus, MmioUart};
use super::UartId;
pub struct Tx {
pub(crate) regs: MmioUart<'static>,
pub(crate) idx: UartId,
}
impl Tx {
/// Steal the TX side of the UART for a given UART index.
///
/// # Safety
///
/// Circumvents safety guarantees provided by the compiler.
#[inline]
pub const unsafe fn steal(idx: UartId) -> Self {
Tx {
regs: unsafe { idx.regs() },
idx,
}
}
#[inline]
pub const fn uart_idx(&self) -> UartId {
self.idx
}
#[inline]
pub const fn regs(&mut self) -> &mut MmioUart<'static> {
&mut self.regs
}
#[inline]
pub fn write_fifo(&mut self, word: u8) -> nb::Result<(), Infallible> {
if self.regs.read_sr().tx_full() {
return Err(nb::Error::WouldBlock);
}
self.write_fifo_unchecked(word);
Ok(())
}
/// Enables TX side of the UART.
#[inline]
pub fn enable(&mut self, with_reset: bool) {
if with_reset {
self.soft_reset();
}
self.regs.modify_cr(|mut val| {
val.set_tx_en(true);
val.set_tx_dis(false);
val
});
}
/// Disables TX side of the UART.
#[inline]
pub fn disable(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_tx_en(false);
val.set_tx_dis(true);
val
});
}
#[inline]
pub fn soft_reset(&mut self) {
self.regs.modify_cr(|mut val| {
val.set_tx_rst(true);
val
});
loop {
if !self.regs.read_cr().tx_rst() {
break;
}
}
}
#[inline]
pub fn write_fifo_unchecked(&mut self, word: u8) {
self.regs.write_fifo(Fifo::new_with_raw_value(word as u32));
}
/// Enables interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn enable_interrupts(&mut self) {
self.regs.write_ier(
InterruptControl::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
.with_rx_framing(false)
.with_rx_over(false)
.with_tx_full(true)
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.build(),
);
}
/// Disable interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn disable_interrupts(&mut self) {
self.regs.write_idr(
InterruptControl::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
.with_rx_framing(false)
.with_rx_over(false)
.with_tx_full(true)
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.build(),
);
}
/// Clears interrupts relevant for the TX side of the UART except the TX trigger interrupt.
#[inline]
pub fn clear_interrupts(&mut self) {
self.regs.write_isr(
InterruptStatus::builder()
.with_tx_over(true)
.with_tx_near_full(true)
.with_tx_trig(false)
.with_rx_dms(false)
.with_rx_timeout(false)
.with_rx_parity(false)
.with_rx_framing(false)
.with_rx_over(false)
.with_tx_full(true)
.with_tx_empty(true)
.with_rx_full(false)
.with_rx_empty(false)
.with_rx_trg(false)
.build(),
);
}
}
impl embedded_hal_nb::serial::ErrorType for Tx {
type Error = Infallible;
}
impl embedded_hal_nb::serial::Write for Tx {
#[inline]
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
self.write_fifo(word)
}
fn flush(&mut self) -> nb::Result<(), Self::Error> {
loop {
if self.regs.read_sr().tx_empty() {
return Ok(());
}
}
}
}
impl embedded_io::ErrorType for Tx {
type Error = Infallible;
}
impl embedded_io::Write for Tx {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
if buf.is_empty() {
return Ok(0);
}
let mut written = 0;
loop {
if !self.regs.read_sr().tx_full() {
break;
}
}
for byte in buf.iter() {
match self.write_fifo(*byte) {
Ok(_) => written += 1,
Err(nb::Error::WouldBlock) => return Ok(written),
}
}
Ok(written)
}
fn flush(&mut self) -> Result<(), Self::Error> {
<Self as embedded_hal_nb::serial::Write<u8>>::flush(self).ok();
Ok(())
}
}

View File

@ -0,0 +1,205 @@
use core::{cell::RefCell, convert::Infallible, sync::atomic::AtomicBool};
use critical_section::Mutex;
use embassy_sync::waitqueue::AtomicWaker;
use raw_slice::RawBufSlice;
use crate::uart::{FIFO_DEPTH, Tx, UartId};
#[derive(Debug)]
pub enum TransferType {
Read,
Write,
Transfer,
TransferInPlace,
}
static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
// Completion flag. Kept outside of the context structure as an atomic to avoid
// critical section.
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
/// UART peripheral.
///
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
/// the given UART bank.
pub fn on_interrupt_tx(peripheral: UartId) {
let mut tx_with_irq = unsafe { Tx::steal(peripheral) };
let idx = peripheral as usize;
let imr = tx_with_irq.regs().read_imr();
// IRQ is not related to TX.
if !imr.tx_over() && !imr.tx_near_full() && !imr.tx_full() && !imr.tx_empty() && !imr.tx_full()
{
return;
}
let isr = tx_with_irq.regs().read_isr();
let unexpected_overrun = isr.tx_over();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow()
});
// No transfer active.
if context.slice.is_null() {
return;
}
let slice_len = context.slice.len().unwrap();
context.tx_overrun = unexpected_overrun;
if (context.progress >= slice_len && isr.tx_empty()) || slice_len == 0 {
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Transfer is done.
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
tx_with_irq.disable_interrupts();
tx_with_irq.clear_interrupts();
UART_TX_WAKERS[idx].wake();
return;
}
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { context.slice.get() }.expect("slice is invalid");
while context.progress < slice_len {
if tx_with_irq.regs().read_sr().tx_full() {
break;
}
// Safety: TX structure is owned by the future which does not write into the the data
// register, so we can assume we are the only one writing to the data register.
tx_with_irq.write_fifo_unchecked(slice[context.progress]);
context.progress += 1;
}
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
*context_ref.borrow_mut() = context;
});
// Clear interrupts.
tx_with_irq.clear_interrupts();
}
#[derive(Debug, Copy, Clone)]
pub struct TxContext {
progress: usize,
tx_overrun: bool,
slice: RawBufSlice,
}
#[allow(clippy::new_without_default)]
impl TxContext {
pub const fn new() -> Self {
Self {
progress: 0,
tx_overrun: false,
slice: RawBufSlice::new_nulled(),
}
}
}
pub struct TxFuture {
id: UartId,
}
impl TxFuture {
/// # Safety
///
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
/// that the slice outlives the data structure.
pub unsafe fn new(tx_with_irq: &mut Tx, data: &[u8]) -> Self {
let idx = tx_with_irq.uart_idx() as usize;
TX_DONE[idx].store(false, core::sync::atomic::Ordering::Relaxed);
tx_with_irq.disable_interrupts();
tx_with_irq.disable();
let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
let mut context = context_ref.borrow_mut();
unsafe {
context.slice.set(data);
}
context.progress = init_fill_count; // We fill the FIFO.
});
tx_with_irq.enable(true);
for data in data.iter().take(init_fill_count) {
tx_with_irq.write_fifo_unchecked(*data);
}
tx_with_irq.enable_interrupts();
Self {
id: tx_with_irq.uart_idx(),
}
}
}
impl Future for TxFuture {
type Output = usize;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
UART_TX_WAKERS[self.id as usize].register(cx.waker());
if TX_DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
let progress = critical_section::with(|cs| {
let mut ctx = TX_CONTEXTS[self.id as usize].borrow(cs).borrow_mut();
ctx.slice.set_null();
ctx.progress
});
return core::task::Poll::Ready(progress);
}
core::task::Poll::Pending
}
}
impl Drop for TxFuture {
fn drop(&mut self) {
let mut tx = unsafe { Tx::steal(self.id) };
tx.disable_interrupts();
}
}
pub struct TxAsync {
tx: Tx,
}
impl TxAsync {
pub fn new(tx: Tx) -> Self {
Self { tx }
}
/// Write a buffer asynchronously.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
pub async fn write(&mut self, buf: &[u8]) -> usize {
if buf.is_empty() {
return 0;
}
let fut = unsafe { TxFuture::new(&mut self.tx, buf) };
fut.await
}
pub fn release(self) -> Tx {
self.tx
}
}
impl embedded_io::ErrorType for TxAsync {
type Error = Infallible;
}
impl embedded_io_async::Write for TxAsync {
/// Write a buffer asynchronously.
///
/// This implementation is not side effect free, and a started future might have already
/// written part of the passed buffer.
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
Ok(self.write(buf).await)
}
}

7
zynq7000-rt/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "zynq-rt"
version = "0.1.0"

26
zynq7000-rt/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "zynq7000-rt"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "Run-time support for the Zynq7000 family of SoCs for running bare-metal applications"
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"
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
cortex-a-rt = { path = "/home/rmueller/Rust/cortex-ar/cortex-a-rt", optional = true, features = ["vfp-dp"] }
cortex-ar = { path = "/home/rmueller/Rust/cortex-ar/cortex-ar" }
[features]
default = ["rt"]
tools = []
rt = ["dep:cortex-a-rt"]
[[bin]]
name = "table-gen"
path = "src/bin/table-gen.rs"
# Prevents default build
required-features = ["tools"]

27
zynq7000-rt/README.md Normal file
View File

@ -0,0 +1,27 @@
Zynq7000 Rust Run-Time Support
========
Startup code and minimal runtime for the AMD Zynq7000 SoC to write bare metal Rust code.
This run-time crate is strongly based on the
[startup code provided by AMD](https://github.com/Xilinx/embeddedsw/blob/master/lib/bsp/standalone/src/arm/cortexa9/gcc/boot.S).
One major difference is that the MMU table is specified as Rust code. There are also modification
to the stack setup code, because a different linker script is used.
This crate pulls in the [cortex-a-rt](https://github.com/us-irs/cortex-ar/tree/cortex-a-addition/cortex-a-rt)
crate to provide ARM vectors and the linker script.
## Features
- `rt` is a default feature which activates the run-time.
## Re-Generating the MMU table
The MMU table is a static flat map of 4096 entries for each 1 MB in the memory map.
It was generated using the `table-gen` binary tool.
You can re-run the tool using
```sh
cargo +stable --target <hostTarget> run --bin table-gen --no-default-features --features tools
```

2
zynq7000-rt/regen-table.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
cargo +stable run --target $(rustc -vV | grep host | cut -d ' ' -f2) --bin table-gen --no-default-features --features tools

View File

@ -0,0 +1,285 @@
use std::fs::File;
use std::io::Write;
use std::process::Command;
use zynq7000_rt::mmu::ONE_MB;
pub use zynq7000_rt::mmu::segments::*;
fn main() {
let file_path = "src/mmu_table.rs";
let file = File::create(file_path).expect("Failed to create file");
let mut offset = 0;
let attr_ddr = stringify!(section_attrs::DDR);
let attr_unassigned = stringify!(section_attrs::UNASSIGNED_RESERVED);
let attr_fpga_slaves = stringify!(section_attrs::FPGA_SLAVES);
let attr_shared_dev = stringify!(section_attrs::SHAREABLE_DEVICE);
let attr_sram = stringify!(section_attrs::SRAM);
let attr_qspi = stringify!(section_attrs::QSPI_XIP);
let attr_ocm_high = stringify!(section_attrs::OCM_MAPPED_HIGH);
assert_eq!(
1 + DDR_FULL_ACCESSIBLE
+ FPGA_SLAVE
+ FPGA_SLAVE
+ UNASSIGNED_0
+ IO_PERIPHS
+ UNASSIGNED_1
+ NAND
+ NOR
+ SRAM
+ SEGMENTS_UNASSIGNED_2
+ AMBA_APB
+ UNASSIGNED_3
+ QSPI_XIP
+ UNASSIGNED_4
+ OCM_MAPPED_HIGH,
4096
);
let mut buf_writer = std::io::BufWriter::new(file);
writeln!(
buf_writer,
"//! This file was auto-generated by table-gen.rs"
)
.unwrap();
writeln!(buf_writer, "use cortex_ar::mmu::L1Section;").unwrap();
writeln!(buf_writer, "use crate::mmu::{{section_attrs, L1Table}};").unwrap();
writeln!(buf_writer, "").unwrap();
writeln!(buf_writer, "/// MMU Level 1 Page table.").unwrap();
writeln!(buf_writer, "///").unwrap();
writeln!(
buf_writer,
"/// 4096 entries, each covering 1MB of the address space."
)
.unwrap();
writeln!(
buf_writer,
"pub const MMU_L1_PAGE_TABLE: L1Table = L1Table(["
)
.unwrap();
writeln!(
buf_writer,
"// First DDR segment, OCM memory (0x0000_0000 - 0x0010_0000)"
)
.unwrap();
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_ddr
)
.unwrap();
offset += ONE_MB;
writeln!(buf_writer, "// DDR memory (0x00100000 - 0x4000_0000)").unwrap();
for _ in 0..DDR_FULL_ACCESSIBLE {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_ddr
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// FPGA slave 0 (0x4000_0000 - 0x8000_0000)").unwrap();
for _ in 0..FPGA_SLAVE {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_fpga_slaves
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// FPGA slave 1 (0x8000_0000 - 0xC000_0000)").unwrap();
for _ in 0..FPGA_SLAVE {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_fpga_slaves
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Unassigned/Reserved (0xC000_0000 - 0xE000_0000)"
)
.unwrap();
for _ in 0..UNASSIGNED_0 {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_unassigned
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Segments IO peripherals (0xE000_0000 - 0xE030_0000)"
)
.unwrap();
for _ in 0..IO_PERIPHS {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_shared_dev
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Unassigned/Reserved (0xE030_0000 - 0xE100_0000)"
)
.unwrap();
for _ in 0..UNASSIGNED_1 {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_unassigned
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// NAND (0xE100_0000 - 0xE200_0000)").unwrap();
for _ in 0..NAND {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_shared_dev
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// NOR (0xE200_0000 - 0xE400_0000)").unwrap();
for _ in 0..NOR {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_shared_dev
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// SRAM (0xE400_0000 - 0xE600_0000)").unwrap();
for _ in 0..SRAM {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_sram
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Unassigned/Reserved (0xE600_0000 - 0xF800_0000)"
)
.unwrap();
for _ in 0..SEGMENTS_UNASSIGNED_2 {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_unassigned
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// AMBA APB peripherals (0xF800_0000 - 0xF900_0000)"
)
.unwrap();
for _ in 0..AMBA_APB {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_shared_dev
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Unassigned/Reserved (0xF900_0000 - 0xFC00_0000)"
)
.unwrap();
for _ in 0..UNASSIGNED_3 {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_unassigned
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// QSPI XIP (0xFC00_0000 - 0xFE00_0000)").unwrap();
for _ in 0..QSPI_XIP {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_qspi
)
.unwrap();
offset += ONE_MB;
}
writeln!(
buf_writer,
"// Unassiged/Reserved (0xFE00_0000 - 0xFFF0_0000)"
)
.unwrap();
for _ in 0..UNASSIGNED_4 {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_unassigned
)
.unwrap();
offset += ONE_MB;
}
writeln!(buf_writer, "// OCM High (0xFFF0_0000 - 0xFFFF_FFFF)").unwrap();
let mut offset_u64 = offset as u64;
for _ in 0..OCM_MAPPED_HIGH {
writeln!(
buf_writer,
"L1Section::new({}, {}).raw_value(),",
offset, attr_ocm_high
)
.unwrap();
offset_u64 += ONE_MB as u64;
}
// Check that the full 4 GB were covered (not too much, or less)
assert_eq!(offset_u64, 0x1_0000_0000 as u64);
writeln!(buf_writer, "]);").unwrap();
// Finish the file.
drop(buf_writer);
println!("Generated mmu_table.rs");
// Run rustfmt on the generated file
let output = Command::new("rustfmt")
.arg(file_path)
.output()
.expect("Failed to run rustfmt");
if !output.status.success() {
eprintln!("rustfmt failed: {:?}", output);
}
}

11
zynq7000-rt/src/lib.rs Normal file
View File

@ -0,0 +1,11 @@
//! Rust bare metal run-time support for the AMD Zynq 7000 SoCs
//!
//! This includes basic low-level startup code similar to the bare-metal boot routines
//! [provided by Xilinx](https://github.com/Xilinx/embeddedsw/tree/master/lib/bsp/standalone/src/arm/cortexa9/gcc).
#![no_std]
pub mod mmu;
#[cfg(feature = "rt")]
mod mmu_table;
#[cfg(feature = "rt")]
pub mod rt;

191
zynq7000-rt/src/mmu.rs Normal file
View File

@ -0,0 +1,191 @@
//! The overview of translation table memory attributes is described below.
//!
//!| | Memory Range | Definition in Translation Table |
//!|-----------------------|-------------------------|-----------------------------------|
//!| DDR | 0x00000000 - 0x3FFFFFFF | Normal write-back Cacheable |
//!| PL | 0x40000000 - 0xBFFFFFFF | Strongly Ordered |
//!| Reserved | 0xC0000000 - 0xDFFFFFFF | Unassigned |
//!| Memory mapped devices | 0xE0000000 - 0xE02FFFFF | Device Memory |
//!| Reserved | 0xE0300000 - 0xE0FFFFFF | Unassigned |
//!| NAND, NOR | 0xE1000000 - 0xE3FFFFFF | Device memory |
//!| SRAM | 0xE4000000 - 0xE5FFFFFF | Normal write-back Cacheable |
//!| Reserved | 0xE6000000 - 0xF7FFFFFF | Unassigned |
//!| AMBA APB Peripherals | 0xF8000000 - 0xF8FFFFFF | Device Memory |
//!| Reserved | 0xF9000000 - 0xFBFFFFFF | Unassigned |
//!| Linear QSPI - XIP | 0xFC000000 - 0xFDFFFFFF | Normal write-through cacheable |
//!| Reserved | 0xFE000000 - 0xFFEFFFFF | Unassigned |
//!| OCM | 0xFFF00000 - 0xFFFFFFFF | Normal inner write-back cacheable |
//!
//! For region 0x00000000 - 0x3FFFFFFF, a system where DDR is less than 1 GB,
//! region after DDR and before PL is marked as undefined/reserved in translation
//! table. In 0xF8000000 - 0xF8FFFFFF, 0xF8000C00 - 0xF8000FFF, 0xF8010000 -
//! 0xF88FFFFF and 0xF8F03000 to 0xF8FFFFFF are reserved but due to granual size
//! of 1 MB, it is not possible to define separate regions for them. For region
//! 0xFFF00000 - 0xFFFFFFFF, 0xFFF00000 to 0xFFFB0000 is reserved but due to 1MB
//! granual size, it is not possible to define separate region for it.
pub const MAX_DDR_SIZE: usize = 0x4000_0000;
pub const ONE_MB: usize = 0x10_0000;
pub mod offsets {
pub const OFFSET_DDR: usize = 0;
pub const OFFSET_DDR_ALL_ACCESSIBLE: usize = 0x10_0000;
pub const OFFSET_FPGA_SLAVE_0: usize = 0x4000_0000;
pub const OFFSET_FPGA_SLAVE_1_START: usize = 0x8000_0000;
pub const OFFSET_FPGA_SLAVE_1_END: usize = 0xC000_0000;
pub const OFFSET_IO_PERIPHERALS_START: usize = 0xE000_0000;
pub const OFFSET_IO_PERIPHERALS_END: usize = 0xE030_0000;
pub const OFFSET_NAND_MEMORY: usize = 0xE100_0000;
pub const OFFSET_NOR_MEMORY: usize = 0xE200_0000;
pub const OFFSET_SRAM_MEMORY: usize = 0xE400_0000;
pub const OFFSET_SMC_MEMORIES_END: usize = 0xE600_0000;
/// 0xf8000c00 to 0xf8000fff, 0xf8010000 to 0xf88fffff and
/// 0xf8f03000 to 0xf8ffffff are reserved but due to granual size of
/// 1MB, it is not possible to define separate regions for them.
pub const OFFSET_AMBA_APB_START: usize = 0xF800_0000;
pub const OFFSET_AMBA_APB_END: usize = 0xF900_0000;
pub const OFFSET_QSPI_XIP_START: usize = 0xFC00_0000;
pub const OFFSET_QSPI_XIP_END: usize = 0xFE00_0000;
/// 0xfff00000 to 0xfffb0000 is reserved but due to granual size of
/// 1MB, it is not possible to define separate region for it
pub const OFFSET_OCM_MAPPED_HIGH_START: usize = 0xFFF0_0000;
pub const OFFSET_OCM_MAPPED_HIGH_END: u64 = 0x1_0000_0000;
}
pub mod segments {
pub use super::offsets::*;
use super::{MAX_DDR_SIZE, ONE_MB};
/// First 1 MB of DDR has special treatment, access is dependant on SCU/OCM state.
/// Refer to Zynq TRM UG585 p.106 for more details.
pub const DDR_FULL_ACCESSIBLE: usize = (MAX_DDR_SIZE - ONE_MB) / ONE_MB;
pub const FPGA_SLAVE: usize = (OFFSET_FPGA_SLAVE_1_START - OFFSET_FPGA_SLAVE_0) / ONE_MB;
pub const UNASSIGNED_0: usize =
(OFFSET_IO_PERIPHERALS_START - OFFSET_FPGA_SLAVE_1_END) / ONE_MB;
pub const IO_PERIPHS: usize =
(OFFSET_IO_PERIPHERALS_END - OFFSET_IO_PERIPHERALS_START) / ONE_MB;
pub const UNASSIGNED_1: usize = (OFFSET_NAND_MEMORY - OFFSET_IO_PERIPHERALS_END) / ONE_MB;
pub const NAND: usize = (OFFSET_NOR_MEMORY - OFFSET_NAND_MEMORY) / ONE_MB;
pub const NOR: usize = (OFFSET_SRAM_MEMORY - OFFSET_NOR_MEMORY) / ONE_MB;
pub const SRAM: usize = (OFFSET_SMC_MEMORIES_END - OFFSET_SRAM_MEMORY) / ONE_MB;
pub const SEGMENTS_UNASSIGNED_2: usize =
(OFFSET_AMBA_APB_START - OFFSET_SMC_MEMORIES_END) / ONE_MB;
pub const AMBA_APB: usize = (OFFSET_AMBA_APB_END - OFFSET_AMBA_APB_START) / ONE_MB;
pub const UNASSIGNED_3: usize = (OFFSET_QSPI_XIP_START - OFFSET_AMBA_APB_END) / ONE_MB;
pub const QSPI_XIP: usize = (OFFSET_QSPI_XIP_END - OFFSET_QSPI_XIP_START) / ONE_MB;
pub const UNASSIGNED_4: usize = (OFFSET_OCM_MAPPED_HIGH_START - OFFSET_QSPI_XIP_END) / ONE_MB;
pub const OCM_MAPPED_HIGH: usize = ((OFFSET_OCM_MAPPED_HIGH_END
- OFFSET_OCM_MAPPED_HIGH_START as u64)
/ ONE_MB as u64) as usize;
}
pub mod section_attrs {
use cortex_ar::mmu::{
AccessPermissions, CacheableMemoryAttribute, MemoryRegionAttributes, SectionAttributes,
};
pub const DDR: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: true,
access: AccessPermissions::FullAccess,
// Manager domain
domain: 0b1111,
execute_never: false,
memory_attrs: MemoryRegionAttributes::CacheableMemory {
inner: CacheableMemoryAttribute::WriteBackWriteAlloc,
outer: CacheableMemoryAttribute::WriteBackWriteAlloc,
}
.as_raw(),
};
pub const FPGA_SLAVES: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::FullAccess,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::StronglyOrdered.as_raw(),
};
pub const SHAREABLE_DEVICE: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::FullAccess,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::ShareableDevice.as_raw(),
};
pub const SRAM: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::FullAccess,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteBackNoWriteAlloc.as_raw(),
};
pub const QSPI_XIP: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::FullAccess,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::OuterAndInnerWriteThroughNoWriteAlloc.as_raw(),
};
pub const OCM_MAPPED_HIGH: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::FullAccess,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::CacheableMemory {
inner: CacheableMemoryAttribute::WriteThroughNoWriteAlloc,
outer: CacheableMemoryAttribute::NonCacheable,
}
.as_raw(),
};
pub const UNASSIGNED_RESERVED: SectionAttributes = SectionAttributes {
non_global: false,
p_bit: false,
shareable: false,
access: AccessPermissions::PermissionFault,
domain: 0b0000,
execute_never: false,
memory_attrs: MemoryRegionAttributes::StronglyOrdered.as_raw(),
};
}
pub const NUM_L1_PAGE_TABLE_ENTRIES: usize = 4096;
#[repr(C, align(16384))]
#[cfg(feature = "rt")]
pub struct L1Table(pub(crate) [u32; NUM_L1_PAGE_TABLE_ENTRIES]);
/// Load the MMU translation table base address into the MMU.
///
/// # Safety
///
/// This function is unsafe because it directly writes to the MMU related registers. It has to be
/// called once in the boot code before enabling the MMU, and it should be called while the MMU is
/// disabled.
#[unsafe(no_mangle)]
#[cfg(feature = "rt")]
unsafe extern "C" fn load_mmu_table() {
let table_base = &crate::mmu_table::MMU_L1_PAGE_TABLE.0 as *const _ as u32;
unsafe {
core::arch::asm!(
"orr {0}, {0}, #0x5B", // Outer-cacheable, WB
"mcr p15, 0, {0}, c2, c0, 0", // Load table pointer
inout(reg) table_base => _,
options(nostack, preserves_flags)
);
}
}

4121
zynq7000-rt/src/mmu_table.rs Normal file

File diff suppressed because it is too large Load Diff

396
zynq7000-rt/src/rt.rs Normal file
View File

@ -0,0 +1,396 @@
//! Start-up code for Zynq 7000
//!
//! The bootup routine was kepts as similar to the one
//! [provided by Xilinx](https://github.com/Xilinx/embeddedsw/blob/master/lib/bsp/standalone/src/arm/cortexa9/gcc/boot.S)
//! as possible. The boot routine includes stack, MMU, cache and .bss/.data section initialization.
use cortex_a_rt as _;
use cortex_ar::register::{Cpsr, cpsr::ProcessorMode};
// Start-up code for Armv7-A
//
// We set up our stacks and `kmain` in system mode.
core::arch::global_asm!(
r#"
.set PSS_L2CC_BASE_ADDR, 0xF8F02000
.set PSS_SLCR_BASE_ADDR, 0xF8000000
.set RESERVED, 0x0fffff00
.set LRemap, 0xFE00000F /* set the base address of the peripheral block as not shared */
.set L2CCWay, (PSS_L2CC_BASE_ADDR + 0x077C) /*(PSS_L2CC_BASE_ADDR + PSS_L2CC_CACHE_INVLD_WAY_OFFSET)*/
.set L2CCSync, (PSS_L2CC_BASE_ADDR + 0x0730) /*(PSS_L2CC_BASE_ADDR + PSS_L2CC_CACHE_SYNC_OFFSET)*/
.set L2CCCrtl, (PSS_L2CC_BASE_ADDR + 0x0100) /*(PSS_L2CC_BASE_ADDR + PSS_L2CC_CNTRL_OFFSET)*/
.set L2CCAuxCrtl, (PSS_L2CC_BASE_ADDR + 0x0104) /*(PSS_L2CC_BASE_ADDR + XPSS_L2CC_AUX_CNTRL_OFFSET)*/
.set L2CCTAGLatReg, (PSS_L2CC_BASE_ADDR + 0x0108) /*(PSS_L2CC_BASE_ADDR + XPSS_L2CC_TAG_RAM_CNTRL_OFFSET)*/
.set L2CCDataLatReg, (PSS_L2CC_BASE_ADDR + 0x010C) /*(PSS_L2CC_BASE_ADDR + XPSS_L2CC_DATA_RAM_CNTRL_OFFSET)*/
.set L2CCIntClear, (PSS_L2CC_BASE_ADDR + 0x0220) /*(PSS_L2CC_BASE_ADDR + XPSS_L2CC_IAR_OFFSET)*/
.set L2CCIntRaw, (PSS_L2CC_BASE_ADDR + 0x021C) /*(PSS_L2CC_BASE_ADDR + XPSS_L2CC_ISR_OFFSET)*/
.set SLCRlockReg, (PSS_SLCR_BASE_ADDR + 0x04) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_LOCK_OFFSET)*/
.set SLCRUnlockReg, (PSS_SLCR_BASE_ADDR + 0x08) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_UNLOCK_OFFSET)*/
.set SLCRL2cRamReg, (PSS_SLCR_BASE_ADDR + 0xA1C) /*(PSS_SLCR_BASE_ADDR + XPSS_SLCR_L2C_RAM_OFFSET)*/
.set SLCRCPURSTReg, (0xF8000000 + 0x244) /*(XPS_SYS_CTRL_BASEADDR + A9_CPU_RST_CTRL_OFFSET)*/
.set EFUSEStatus, (0xF800D000 + 0x10) /*(XPS_EFUSE_BASEADDR + EFUSE_STATUS_OFFSET)*/
.set CRValMmuCac, 0b01000000000101 /* Enable IDC, and MMU */
.set CRValHiVectorAddr, 0b10000000000000 /* Set the Vector address to high, 0xFFFF0000 */
.set L2CCAuxControl, 0x72360000 /* Enable all prefetching, Cache replacement policy, Parity enable,
Event monitor bus enable and Way Size (64 KB) */
.set L2CCControl, 0x01 /* Enable L2CC */
.set L2CCTAGLatency, 0x0111 /* latency for TAG RAM */
.set L2CCDataLatency, 0x0121 /* latency for DATA RAM */
.set SLCRlockKey, 0x767B /* SLCR lock key */
.set SLCRUnlockKey, 0xDF0D /* SLCR unlock key */
.set SLCRL2cRamConfig, 0x00020202 /* SLCR L2C ram configuration */
.set FPEXC_EN, 0x40000000 /* FPU enable bit, (1 << 30) */
.section .text.startup
.align 0
.global _start
.type _start, %function
_start:
// only allow cpu0 through
// Read MPIDR
mrc p15,0,r1,c0,c0,5
// Extract CPU ID bits. For single-core systems, this should always be 0
and r1, r1, #0x3
cmp r1, #0
beq check_efuse
b initialize
// Zynq specific code. It is recommended to reset CPU1 according to page 160 of the datasheet
check_efuse:
ldr r0,=EFUSEStatus
ldr r1,[r0] /* Read eFuse setting */
ands r1,r1,#0x80 /* Check whether device is having single core */
beq initialize
/* single core device, reset cpu1 */
ldr r0,=SLCRUnlockReg /* Load SLCR base address base + unlock register */
ldr r1,=SLCRUnlockKey /* set unlock key */
str r1, [r0] /* Unlock SLCR */
ldr r0,=SLCRCPURSTReg
ldr r1,[r0] /* Read CPU Software Reset Control register */
orr r1,r1,#0x22
str r1,[r0] /* Reset CPU1 */
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
ldr r1,=SLCRlockKey /* set lock key */
str r1, [r0] /* lock SLCR */
initialize:
mrc p15, 0, r0, c0, c0, 0 /* Get the revision */
and r5, r0, #0x00f00000
and r6, r0, #0x0000000f
orr r6, r6, r5, lsr #20-4
/* set VBAR to the _vector_table address in linker script */
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0
/* Invalidate scu */
ldr r7, =0xf8f0000c
ldr r6, =0xffff
str r6, [r7]
/* Invalidate caches and TLBs */
mov r0,#0 /* r0 = 0 */
mcr p15, 0, r0, c8, c7, 0 /* invalidate TLBs */
mcr p15, 0, r0, c7, c5, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c5, 6 /* Invalidate branch predictor array */
bl invalidate_dcache /* invalidate dcache */
/* Disable MMU, if enabled */
mrc p15, 0, r0, c1, c0, 0 /* read CP15 register 1 */
bic r0, r0, #0x1 /* clear bit 0 */
mcr p15, 0, r0, c1, c0, 0 /* write value back */
// Set up stacks first.
ldr r3, =_stack_top
// get the current PSR
mrs r0, cpsr
// mask for mode bits
mvn r1, #0x1f
and r2, r1, r0
// IRQ mode
orr r2, r2, {irq_mode}
msr cpsr, r2
// IRQ stack pointer
mov sp, r3
ldr r1, =_irq_stack_size
sub r3, r3, r1
mrs r0, cpsr
and r2, r1, r0
// Supervisor mode
orr r2, r2, {svc_mode}
msr cpsr, r2
// Supervisor stack pointer
mov sp, r3
ldr r1, =_svc_stack_size
sub r3, r3, r1
mrs r0, cpsr
and r2, r1, r0
// Abort mode
orr r2, r2, {abt_mode}
msr cpsr, r2
// Abort stack pointer
mov sp, r3
ldr r1, =_abt_stack_size
sub r3, r3, r1
mrs r0, cpsr
and r2, r1, r0
// FIQ mode
orr r2, r2, {fiq_mode}
msr cpsr, r2
// FIQ stack pointer
mov sp, r3
ldr r1, =_fiq_stack_size
sub r3, r3, r1
mrs r0, cpsr
and r2, r1, r0
// Undefined mode
orr r2, r2, {und_mode}
msr cpsr, r2
// Undefined stack pointer
mov sp, r3
ldr r1, =_und_stack_size
sub r3, r3, r1
mrs r0, cpsr
and r2, r1, r0
// System mode
orr r2, r2, {sys_mode}
msr cpsr, r2
// System stack pointer (main stack)
mov sp, r3
// set scu enable bit in scu
ldr r7, =0xf8f00000
ldr r0, [r7]
orr r0, r0, #0x1
str r0, [r7]
/* enable MMU and cache */
bl load_mmu_table
mvn r0,#0 /* Load MMU domains -- all ones=manager */
mcr p15,0,r0,c3,c0,0
/* Enable mmu, icahce and dcache */
ldr r0,=CRValMmuCac
mcr p15,0,r0,c1,c0,0 /* Enable cache and MMU */
dsb /* dsb allow the MMU to start up */
isb /* isb flush prefetch buffer */
/* Write to ACTLR */
mrc p15, 0, r0, c1, c0, 1 /* Read ACTLR*/
orr r0, r0, #(0x01 << 6) /* set SMP bit */
orr r0, r0, #(0x01 ) /* Cache/TLB maintenance broadcast */
mcr p15, 0, r0, c1, c0, 1 /* Write ACTLR*/
/* Invalidate L2 Cache and enable L2 Cache*/
/* For AMP, assume running on CPU1. Don't initialize L2 Cache (up to Linux) */
ldr r0,=L2CCCrtl /* Load L2CC base address base + control register */
mov r1, #0 /* force the disable bit */
str r1, [r0] /* disable the L2 Caches */
ldr r0,=L2CCAuxCrtl /* Load L2CC base address base + Aux control register */
ldr r1,[r0] /* read the register */
ldr r2,=L2CCAuxControl /* set the default bits */
orr r1,r1,r2
str r1, [r0] /* store the Aux Control Register */
ldr r0,=L2CCTAGLatReg /* Load L2CC base address base + TAG Latency address */
ldr r1,=L2CCTAGLatency /* set the latencies for the TAG*/
str r1, [r0] /* store the TAG Latency register Register */
ldr r0,=L2CCDataLatReg /* Load L2CC base address base + Data Latency address */
ldr r1,=L2CCDataLatency /* set the latencies for the Data*/
str r1, [r0] /* store the Data Latency register Register */
ldr r0,=L2CCWay /* Load L2CC base address base + way register*/
ldr r2, =0xFFFF
str r2, [r0] /* force invalidate */
ldr r0,=L2CCSync /* need to poll 0x730, PSS_L2CC_CACHE_SYNC_OFFSET */
/* Load L2CC base address base + sync register*/
/* poll for completion */
Sync:
ldr r1, [r0]
cmp r1, #0
bne Sync
ldr r0,=L2CCIntRaw /* clear pending interrupts */
ldr r1,[r0]
ldr r0,=L2CCIntClear
str r1,[r0]
ldr r0,=SLCRUnlockReg /* Load SLCR base address base + unlock register */
ldr r1,=SLCRUnlockKey /* set unlock key */
str r1, [r0] /* Unlock SLCR */
ldr r0,=SLCRL2cRamReg /* Load SLCR base address base + l2c Ram Control register */
ldr r1,=SLCRL2cRamConfig /* set the configuration value */
str r1, [r0] /* store the L2c Ram Control Register */
ldr r0,=SLCRlockReg /* Load SLCR base address base + lock register */
ldr r1,=SLCRlockKey /* set lock key */
str r1, [r0] /* lock SLCR */
ldr r0,=L2CCCrtl /* Load L2CC base address base + control register */
ldr r1,[r0] /* read the register */
mov r2, #L2CCControl /* set the enable bit */
orr r1,r1,r2
str r1, [r0] /* enable the L2 Caches */
mov r0, r0
mrc p15, 0, r1, c1, c0, 2 /* read cp access control register (CACR) into r1 */
orr r1, r1, #(0xf << 20) /* enable full access for p10 & p11 */
mcr p15, 0, r1, c1, c0, 2 /* write back into CACR */
/* enable vfp */
fmrx r1, FPEXC /* read the exception register */
orr r1,r1, #FPEXC_EN /* set VFP enable bit, leave the others in orig state */
fmxr FPEXC, r1 /* write back the exception register */
mrc p15,0,r0,c1,c0,0 /* flow prediction enable */
orr r0, r0, #(0x01 << 11) /* #0x8000 */
mcr p15,0,r0,c1,c0,0
mrc p15,0,r0,c1,c0,1 /* read Auxiliary Control Register */
orr r0, r0, #(0x1 << 2) /* enable Dside prefetch */
orr r0, r0, #(0x1 << 1) /* enable L2 Prefetch hint */
mcr p15,0,r0,c1,c0,1 /* write Auxiliary Control Register */
mrs r0, cpsr /* get the current PSR */
bic r0, r0, #0x100 /* enable asynchronous abort exception */
msr cpsr_xsf, r0
/* Zero BSS and initialize data before calling any function which might require them. */
// Initialise .bss
ldr r0, =__sbss
ldr r1, =__ebss
mov r2, 0
0:
cmp r1, r0
beq 1f
stm r0!, {{r2}}
b 0b
1:
// Initialise .data
ldr r0, =__sdata
ldr r1, =__edata
ldr r2, =__sidata
0:
cmp r1, r0
beq 1f
ldm r2!, {{r3}}
stm r0!, {{r3}}
b 0b
1:
// Jump to application
// Load CPU ID 0, which will be used as a function argument to the boot_core function.
mov r0, #0x0
bl boot_core
// In case the application returns, loop forever
b .
.size _start, . - _start
.type _invalidate_dcache, %function
invalidate_dcache:
mrc p15, 1, r0, c0, c0, 1 /* read CLIDR */
ands r3, r0, #0x7000000
mov r3, r3, lsr #23 /* cache level value (naturally aligned) */
beq finished
mov r10, #0 /* start with level 0 */
loop1:
add r2, r10, r10, lsr #1 /* work out 3xcachelevel */
mov r1, r0, lsr r2 /* bottom 3 bits are the Cache type for this level */
and r1, r1, #7 /* get those 3 bits alone */
cmp r1, #2
blt skip /* no cache or only instruction cache at this level */
mcr p15, 2, r10, c0, c0, 0 /* write the Cache Size selection register */
isb /* isb to sync the change to the CacheSizeID reg */
mrc p15, 1, r1, c0, c0, 0 /* reads current Cache Size ID register */
and r2, r1, #7 /* extract the line length field */
add r2, r2, #4 /* add 4 for the line length offset (log2 16 bytes) */
ldr r4, =0x3ff
ands r4, r4, r1, lsr #3 /* r4 is the max number on the way size (right aligned) */
clz r5, r4 /* r5 is the bit position of the way size increment */
ldr r7, =0x7fff
ands r7, r7, r1, lsr #13 /* r7 is the max number of the index size (right aligned) */
loop2:
mov r9, r4 /* r9 working copy of the max way size (right aligned) */
loop3:
orr r11, r10, r9, lsl r5 /* factor in the way number and cache number into r11 */
orr r11, r11, r7, lsl r2 /* factor in the index number */
mcr p15, 0, r11, c7, c6, 2 /* invalidate by set/way */
subs r9, r9, #1 /* decrement the way number */
bge loop3
subs r7, r7, #1 /* decrement the index */
bge loop2
skip:
add r10, r10, #2 /* increment the cache number */
cmp r3, r10
bgt loop1
finished:
mov r10, #0 /* switch back to cache level 0 */
mcr p15, 2, r10, c0, c0, 0 /* select current cache level in cssr */
dsb
isb
bx lr
.size invalidate_dcache, . - invalidate_dcache
"#,
fiq_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Fiq)
.with_i(true)
.with_f(true)
.raw_value()
},
irq_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Irq)
.with_i(true)
.with_f(true)
.raw_value()
},
svc_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Svc)
.with_i(true)
.with_f(true)
.raw_value()
},
und_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Und)
.with_i(true)
.with_f(true)
.raw_value()
},
abt_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Abt)
.with_i(true)
.with_f(true)
.raw_value()
},
sys_mode = const {
Cpsr::new_with_raw_value(0)
.with_mode(ProcessorMode::Sys)
.with_i(true)
.with_f(true)
.raw_value()
},
);

27
zynq7000/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "zynq7000"
version = "0.1.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2024"
description = "PAC for the Zynq7000 family of SoCs"
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"
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
categories = ["embedded", "no-std", "hardware-support"]
[dependencies]
static_assertions = "1.1"
derive-mmio = { path = "../../derive-mmio", default-features = false }
bitbybit = "1.3"
arbitrary-int = "1.3"
rustversion = "1"
thiserror = { version = "2", default-features = false }
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
[dev-dependencies]
approx = "0.5"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]

201
zynq7000/LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
zynq7000/LICENSE-MIT Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Robin A. Mueller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

8
zynq7000/README.md Normal file
View File

@ -0,0 +1,8 @@
# PAC for the AMD Zynq 7000 SoC family
This repository contains the Peripheral Access Crate (PAC) for the AMD Zynq7000 SoC family.
If you are interested in higher-level abstractions, it is recommended you visit
the `zynq7000-hal` HAL crate which build on top of this PAC.
Check out the documentation for more details.

3
zynq7000/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
}

202
zynq7000/src/gic.rs Normal file
View File

@ -0,0 +1,202 @@
//! # GIC (Generic Interrupt Controller) register module.
pub use crate::mpcore::{GICC_BASE_ADDR, GICD_BASE_ADDR};
use arbitrary_int::{u3, u5, u10};
use static_assertions::const_assert_eq;
/// Distributor Control Register
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Dcr {
#[bit(1, rw)]
enable_non_secure: bool,
#[bit(0, rw)]
enable_secure: bool,
}
/// Read only bit. This register only returns fixed constants.
#[bitbybit::bitfield(u32)]
pub struct TypeRegister {
#[bits(11..=15, r)]
lspi: u5,
#[bit(10, r)]
security_extension: bool,
#[bits(5..=7, r)]
cpu_number: u3,
#[bits(0..=4, r)]
it_lines_number: u5,
}
impl TypeRegister {
pub const SECURITY_EXTNS_BIT: bool = true;
/// 31 LSPIs.
pub const NUM_LSPI: usize = 0x1f;
/// Encoding: 0b001 means that the Cortex-A9 MPCore has 2 processors.
pub const CPU_NUMBER_BITS: u8 = 0b001;
/// The distributor provides 96 interrupts.
pub const IT_LINES_NUMBER: u8 = 0x2;
pub const NUM_OF_CPUS: usize = 2;
pub const NUM_OF_INTERRUPTS: usize = 96;
}
pub type Typer = TypeRegister;
/// GIC Distributor registers.
#[derive(derive_mmio::Mmio)]
#[repr(C, align(8))]
pub struct Gicd {
/// Distributor Control Register
pub dcr: Dcr,
/// Interrupt Controller Type Register
#[mmio(PureRead)]
pub ictr: Typer,
/// Distributor Implementer Identification Register
#[mmio(PureRead)]
pub iidr: u32,
_reserved_0: [u32; 0x1D],
/// Interrupt security registers
pub isr: [u32; 3],
_reserved_1: [u32; 0x1D],
/// Interrupt Set-Enable Registers
pub iser: [u32; 0x3],
_reserved_3: [u32; 0x1D],
/// Interrupt Clear-Enable Registers
pub icer: [u32; 0x3],
_reserved_4: [u32; 0x1D],
/// Interrupt Set-Pending Registers
pub ispr: [u32; 0x3],
_reserved_5: [u32; 0x1D],
/// Interrupt Clear-Pending Registers
pub icpr: [u32; 0x3],
_reserved_6: [u32; 0x1D],
/// Active Bit Registers
pub abr: [u32; 0x3],
_reserved_10: [u32; 0x3D],
/// Interrupt Priority Registers
pub ipr: [u32; 0x18],
_reserved_11: [u32; 0xE8],
/// Interrupt Processor Targes Registers
pub iptr_sgi: [u32; 0x4],
// TODO: Mark those read-only as soon as that works for arrays.
pub iptr_ppi: [u32; 0x4],
pub iptr_spi: [u32; 0x10],
// Those are split in the ARM documentation for some reason..
_reserved_12: [u32; 0xE8],
/// Interrupt Configuration Registers
/// Interupt sensitivity register for software generated interrupts (SGI)
pub icfr_0_sgi: u32,
/// Interupt sensitivity register for private peripheral interrupts (PPI)
pub icfr_1_ppi: u32,
pub icfr_2_spi: u32,
pub icfr_3_spi: u32,
pub icfr_4_spi: u32,
pub icfr_5_spi: u32,
_reserved_13: [u32; 0x3A],
pub ppi_status: u32,
pub spi_status_0: u32,
pub spi_status_1: u32,
_reserved_14: [u32; 0x7D],
/// Software Generated Interrupt Register.
pub sgir: u32,
_reserved_15: [u32; 0x33],
pub pidr_4: u32,
pub pidr_5: u32,
pub pidr_6: u32,
pub pidr_7: u32,
pub pidr_0: u32,
pub pidr_1: u32,
pub pidr_2: u32,
pub pidr_3: u32,
pub cidr: [u32; 4],
}
const_assert_eq!(core::mem::size_of::<Gicd>(), 0x1000);
impl Gicd {
/// Create a new Global Interrupt Controller Distributor MMIO instance at the fixed address of
/// the processing system.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioGicd<'static> {
unsafe { Self::new_mmio_at(GICD_BASE_ADDR) }
}
}
/// CPU interface control register.
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Icr {
#[bit(4, rw)]
sbpr: bool,
#[bit(3, rw)]
fiq_en: bool,
#[bit(2, rw)]
ack_ctrl: bool,
#[bit(1, rw)]
enable_non_secure: bool,
#[bit(0, rw)]
enable_secure: bool,
}
/// Priority Mask Register
#[bitbybit::bitfield(u32)]
pub struct PriorityRegister {
#[bits(0..=7, rw)]
priority: u8,
}
/// Interrupt acknowledge register.
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct InterruptSignalRegister {
#[bits(10..=12, rw)]
cpu_id: u3,
#[bits(0..=9, rw)]
ack_int_id: u10,
}
/// GIC CPU interface registers.
#[derive(derive_mmio::Mmio)]
#[repr(C, align(8))]
pub struct Gicc {
/// CPU Interface Control Register.
pub icr: Icr,
/// Interrupt Priority Mask Register.
pub pmr: PriorityRegister,
/// Binary Point Register.
pub bpr: u32,
/// Interrupt Acknowledge Register.
pub iar: InterruptSignalRegister,
/// End of Interrupt Register.
pub eoir: InterruptSignalRegister,
/// Running Priority Register.
pub rpr: PriorityRegister,
/// Highest Pending Interrupt Register.
pub hpir: InterruptSignalRegister,
/// Aliased Binary Point Register
pub abpr: u32,
_reserved_0: [u32; 0x37],
/// CPU Interface Identification Register.
#[mmio(PureRead)]
pub iidr: u32,
}
const_assert_eq!(core::mem::size_of::<Gicc>(), 0x100);
impl Gicc {
/// Create a new Global Interrupt Controller CPU MMIO instance at the fixed address of the
/// processing system.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioGicc<'static> {
unsafe { Self::new_mmio_at(GICC_BASE_ADDR) }
}
}

121
zynq7000/src/gpio.rs Normal file
View File

@ -0,0 +1,121 @@
//! # GPIO register module.
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct MaskedOutput {
#[bits(16..=31, w)]
pub mask: u16,
#[bits(0..=15, rw)]
pub output: u16,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct BankCtrl {
/// Direction mode
dirm: u32,
/// Output enable
out_en: u32,
/// Interrupt mask status
#[mmio(PureRead)]
int_mask: u32,
/// Interrupt enable/unmask
#[mmio(Write)]
int_en: u32,
/// Interrupt disable/mask
#[mmio(Write)]
int_dis: u32,
/// Interrupt status
#[mmio(PureRead, Write)]
int_sts: u32,
/// Interrupt type
int_type: u32,
/// Interrupt polarity
int_pol: u32,
/// Interrupt any edge sensitivity
int_any: u32,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Gpio {
/// Maskable output data (GPIO bank 0, MIO, lower 16 bits)
masked_out_0_lsw: MaskedOutput,
/// Maskable output data (GPIO bank 0, MIO, upper 16 bits)
masked_out_0_msw: MaskedOutput,
/// Maskable output data (GPIO bank 1, MIO, lower 16 bits)
masked_out_1_lsw: MaskedOutput,
/// Maskable output data (GPIO bank 1, MIO, upper 16 bits)
masked_out_1_msw: MaskedOutput,
/// Maskable output data (GPIO bank 2, EMIO, lower 16 bits)
masked_out_2_lsw: MaskedOutput,
/// Maskable output data (GPIO bank 2, EMIO, upper 16 bits)
masked_out_2_msw: MaskedOutput,
/// Maskable output data (GPIO bank 3, EMIO, lower 16 bits)
masked_out_3_lsw: MaskedOutput,
/// Maskable output data (GPIO bank 3, EMIO, upper 16 bits)
masked_out_3_msw: MaskedOutput,
_reserved_0: [u32; 8],
/// Output data (GPIO bank 0, MIO)
out_0: u32,
/// Output data (GPIO bank 1, MIO)
out_1: u32,
/// Output data (GPIO bank 2, EMIO)
out_2: u32,
/// Output data (GPIO bank 3, EMIO)
out_3: u32,
_reserved_1: [u32; 4],
/// Input data (GPIO bank 0, MIO)
#[mmio(PureRead)]
in_0: u32,
/// Input data (GPIO bank 1, MIO)
#[mmio(PureRead)]
in_1: u32,
/// Input data (GPIO bank 2, EMIO)
#[mmio(PureRead)]
in_2: u32,
/// Input data (GPIO bank 3, EMIO)
#[mmio(PureRead)]
in_3: u32,
_reserved_2: [u32; 101],
#[mmio(inner)]
bank_0: BankCtrl,
_reserved_3: [u32; 7],
#[mmio(inner)]
bank_1: BankCtrl,
_reserved_4: [u32; 7],
#[mmio(inner)]
bank_2: BankCtrl,
_reserved_5: [u32; 7],
#[mmio(inner)]
bank_3: BankCtrl,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Gpio>(), 0x2E8);
impl Gpio {
/// Create a new XGPIOPS GPIO MMIO instance.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed() -> MmioGpio<'static> {
MmioGpio {
ptr: 0xE000A000 as *mut Gpio,
phantom: core::marker::PhantomData,
}
}
}

60
zynq7000/src/gtc.rs Normal file
View File

@ -0,0 +1,60 @@
//! # Global timer counter module.
pub const GTC_BASE_ADDR: usize = super::mpcore::MPCORE_BASE_ADDR + 0x0000_0200;
#[bitbybit::bitfield(u32)]
pub struct GtcCtrl {
#[bits(8..=15, rw)]
prescaler: u8,
#[bit(3, rw)]
auto_increment: bool,
#[bit(2, rw)]
irq_enable: bool,
#[bit(1, rw)]
comparator_enable: bool,
#[bit(0, rw)]
enable: bool,
}
#[bitbybit::bitfield(u32)]
pub struct InterruptStatus {
#[bit(0, rw)]
event_flag: bool,
}
/// Global timer counter.
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Gtc {
/// Count register 0, lower 32 bits
count_lower: u32,
/// Count register 1, upper 32 bits
count_upper: u32,
/// Control register
ctrl: GtcCtrl,
/// Interrupt status register
#[mmio(PureRead, Write)]
isr: InterruptStatus,
/// Comparator 0, lower 32 bits
comparator_lower: u32,
/// Comparator 1, upper 32 bits
comparator_upper: u32,
/// Auto-increment register
auto_increment: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Gtc>(), 0x1C);
impl Gtc {
/// Create a new GTC MMIO instance at the fixed base address.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioGtc<'static> {
unsafe { Gtc::new_mmio_at(GTC_BASE_ADDR) }
}
}

203
zynq7000/src/i2c.rs Normal file
View File

@ -0,0 +1,203 @@
//! SPI register module.
use arbitrary_int::{u2, u6, u10};
pub const I2C_0_BASE_ADDR: usize = 0xE000_4000;
pub const I2C_1_BASE_ADDR: usize = 0xE000_5000;
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum Direction {
Receiver = 0b1,
Transmitter = 0b0,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum Mode {
Slave = 0b0,
Master = 0b1,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct Control {
/// Divides the input PCLK frequency by this value + 1
#[bits(14..=15, rw)]
div_a: u2,
/// Divides the output from divisor A by this value + 1
#[bits(8..=13, rw)]
div_b: u6,
#[bit(6, rw)]
clear_fifo: bool,
#[bit(5, rw)]
slv_mon: bool,
/// 0: Allow transfer to terminate as soon as all data has been transmitted or received.
/// 1: When no more data is avilable for transmit or no more data can be received, hold
/// the SCK line low until services by the host.
#[bit(4, rw)]
hold_bus: bool,
/// Should be set to 1. 0: Disabled, NACK transmitted. 1: Enabled, ACK transmitted.
#[bit(3, rw)]
acken: bool,
/// Only used in master mode. 0: Reserved. 1: Normal 7-bit address.
#[bit(2, rw)]
addressing: bool,
#[bit(1, rw)]
mode: Mode,
#[bit(0, rw)]
dir: Direction,
}
#[bitbybit::bitfield(u32)]
pub struct Status {
#[bit(8, r)]
bus_active: bool,
/// FIFO is full and new byte was received. The new byte is not acknowledged and the contents
/// of the FIFO remain unchanged.
#[bit(6, r)]
rx_overflow: bool,
/// 1: There is still a byte of data to be transmitted by the interface.
#[bit(6, r)]
tx_busy: bool,
/// Receiver data valid, ca be read from the interface.
#[bit(5, r)]
rx_valid: bool,
#[bit(3, r)]
rx_rw: bool,
}
#[bitbybit::bitfield(u32)]
pub struct Addr {
#[bits(0..=9, rw)]
addr: u10,
}
#[bitbybit::bitfield(u32)]
pub struct Fifo {
#[bits(0..=7, rw)]
data: u8,
}
#[bitbybit::bitfield(u32)]
pub struct InterruptStatus {
#[bit(9, rw)]
arbitration_lost: bool,
#[bit(7, rw)]
rx_underflow: bool,
#[bit(6, rw)]
tx_overflow: bool,
#[bit(5, rw)]
rx_overflow: bool,
#[bit(4, rw)]
slave_ready: bool,
#[bit(3, rw)]
timeout: bool,
#[bit(2, rw)]
nack: bool,
#[bit(1, rw)]
data: bool,
#[bit(0, rw)]
complete: bool,
}
#[bitbybit::bitfield(u32)]
pub struct InterruptMask {
#[bit(9, r)]
arbitration_lost: bool,
#[bit(7, r)]
rx_underflow: bool,
#[bit(6, r)]
tx_overflow: bool,
#[bit(5, r)]
rx_overflow: bool,
#[bit(4, r)]
slave_ready: bool,
#[bit(3, r)]
timeout: bool,
#[bit(2, r)]
nack: bool,
#[bit(1, r)]
data: bool,
#[bit(0, r)]
complete: bool,
}
#[bitbybit::bitfield(u32)]
pub struct InterruptControl {
#[bit(9, w)]
arbitration_lost: bool,
#[bit(7, w)]
rx_underflow: bool,
#[bit(6, w)]
tx_overflow: bool,
#[bit(5, w)]
rx_overflow: bool,
#[bit(4, w)]
slave_ready: bool,
#[bit(3, w)]
timeout: bool,
#[bit(2, w)]
nack: bool,
#[bit(1, w)]
data: bool,
#[bit(0, w)]
complete: bool,
}
#[bitbybit::bitfield(u32)]
pub struct Timeout {
/// Reset value: 0x1F.
#[bits(0..=7, rw)]
timeout: u8,
}
#[bitbybit::bitfield(u32, default = 0x0)]
pub struct TransferSize {
#[bits(0..=7, rw)]
size: u8,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct I2c {
cr: Control,
#[mmio(PureRead)]
sr: Status,
addr: Addr,
#[mmio(Read, Write)]
data: Fifo,
#[mmio(PureRead, Write, Modify)]
isr: InterruptStatus,
transfer_size: TransferSize,
slave_pause: u32,
timeout: Timeout,
#[mmio(PureRead)]
imr: InterruptMask,
#[mmio(Write)]
ier: InterruptControl,
#[mmio(Write)]
idr: InterruptControl,
}
impl I2c {
/// Create a new I2C MMIO instance for I2C0 at address [I2C_0_BASE_ADDR].
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_0() -> MmioI2c<'static> {
unsafe { Self::new_mmio_at(I2C_0_BASE_ADDR) }
}
/// Create a new I2C MMIO instance for I2C1 at address [I2C_1_BASE_ADDR].
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
pub const unsafe fn new_mmio_fixed_1() -> MmioI2c<'static> {
unsafe { Self::new_mmio_at(I2C_1_BASE_ADDR) }
}
}

85
zynq7000/src/lib.rs Normal file
View File

@ -0,0 +1,85 @@
//! # Rust peripheral acess crate to the AMD Zynq 7000 SoCs
//!
//! This crate provides a low-level register access API building on the
//! [`derive-mmio` crate](https://crates.io/crates/derive-mmio). However, its structure
//! is similar to the crates auto-generated by [`svd2rust`](https://docs.rs/svd2rust/latest/svd2rust/#peripheral-api).
//!
//! This crate is purposely kept low-level to allow building higher level abstractions like HALs
//! on top of it.
#![no_std]
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(test)]
extern crate std;
pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000;
pub mod gic;
pub mod gpio;
pub mod gtc;
pub mod i2c;
pub mod mpcore;
pub mod slcr;
pub mod spi;
pub mod ttc;
pub mod uart;
static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false);
/// This is a collection of all the processing peripherals.
///
/// It is a singleton which exposes all peripherals supported by this crate.
/// The [`svd2rust` documentation](https://docs.rs/svd2rust/latest/svd2rust/#peripheral-api)
/// provides some more information about this.
pub struct PsPeripherals {
pub gicc: gic::MmioGicc<'static>,
pub gicd: gic::MmioGicd<'static>,
pub uart_0: uart::MmioUart<'static>,
pub uart_1: uart::MmioUart<'static>,
pub spi_0: spi::MmioSpi<'static>,
pub spi_1: spi::MmioSpi<'static>,
pub i2c_0: i2c::MmioI2c<'static>,
pub i2c_1: i2c::MmioI2c<'static>,
pub gtc: gtc::MmioGtc<'static>,
pub gpio: gpio::MmioGpio<'static>,
pub slcr: slcr::MmioSlcr<'static>,
pub ttc_0: ttc::MmioTtc<'static>,
pub ttc_1: ttc::MmioTtc<'static>,
}
impl PsPeripherals {
/// Returns all supported processing system peripherals *once*.
pub fn take() -> Option<Self> {
let taken = PERIPHERALS_TAKEN.swap(true, Ordering::Relaxed);
if taken {
return None;
}
Some(unsafe { Self::steal() })
}
/// Unchecked version of [Self::take].
///
/// # Safety
///
/// Each of the returned peripherals must be used at most once.
pub unsafe fn steal() -> Self {
unsafe {
Self {
gicc: gic::Gicc::new_mmio_fixed(),
gicd: gic::Gicd::new_mmio_fixed(),
uart_0: uart::Uart::new_mmio_fixed_0(),
uart_1: uart::Uart::new_mmio_fixed_1(),
gtc: gtc::Gtc::new_mmio_fixed(),
gpio: gpio::Gpio::new_mmio_fixed(),
slcr: slcr::Slcr::new_mmio_fixed(),
spi_0: spi::Spi::new_mmio_fixed_0(),
spi_1: spi::Spi::new_mmio_fixed_1(),
i2c_0: i2c::I2c::new_mmio_fixed_0(),
i2c_1: i2c::I2c::new_mmio_fixed_1(),
ttc_0: ttc::Ttc::new_mmio_fixed_0(),
ttc_1: ttc::Ttc::new_mmio_fixed_1(),
}
}
}
}

97
zynq7000/src/mpcore.rs Normal file
View File

@ -0,0 +1,97 @@
//! Application Processing Unit Registers (mpcore)
//!
//! Based on p.1483 of the Zynq-7000 TRM.
use static_assertions::const_assert_eq;
use crate::{
gic::{Gicc, Gicd, MmioGicc, MmioGicd},
gtc::{Gtc, MmioGtc},
};
pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000;
pub const SCU_BASE_ADDR: usize = MPCORE_BASE_ADDR;
pub const GICC_BASE_ADDR: usize = MPCORE_BASE_ADDR + 0x100;
pub const GICD_BASE_ADDR: usize = MPCORE_BASE_ADDR + 0x1000;
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Scu {
ctrl: u32,
config: u32,
cpu_power_status: u32,
invalidate_all_regs_in_secure_state: u32,
_reserved_0: [u32; 0xC],
filtering_start_addr: u32,
filtering_end_addr: u32,
_reserved_1: [u32; 0x2],
access_ctrl: u32,
non_secure_access_ctrl: u32,
}
impl Scu {
/// Create a new Snoop Control Unit interface at the fixed base address.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioScu<'static> {
unsafe { Self::new_mmio_at(SCU_BASE_ADDR) }
}
}
const_assert_eq!(core::mem::size_of::<Scu>(), 0x58);
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Mpcore {
#[mmio(inner)]
scu: Scu,
_reserved_0: [u32; 0x2A],
#[mmio(inner)]
gicc: Gicc,
#[mmio(inner)]
gt: Gtc,
_reserved_1: [u32; 0xF9],
private_timer_load: u32,
private_timer_counter: u32,
private_timer_ctrl: u32,
private_interrupt_status: u32,
_reserved_2: [u32; 0x4],
watchdog_load: u32,
watchdog_counter: u32,
watchdog_ctrl: u32,
watchdog_interrupt_status: u32,
watchdog_reset_status: u32,
watchdog_disable: u32,
_reserved_3: [u32; 0x272],
#[mmio(inner)]
gicd: Gicd,
}
const_assert_eq!(core::mem::size_of::<Mpcore>(), 0x2000);
impl Mpcore {
/// Create a MP core peripheral interface at the fixed base address.
///
/// # Safety
///
/// This API can be used to potentially create a driver to the same peripheral structure
/// from multiple threads. The user must ensure that concurrent accesses are safe and do not
/// interfere with each other.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioMpcore<'static> {
unsafe { Self::new_mmio_at(MPCORE_BASE_ADDR) }
}
}

364
zynq7000/src/slcr/clocks.rs Normal file
View File

@ -0,0 +1,364 @@
//! SLCR clock control registers.
//!
//! Writing any of these registers required unlocking the SLCR first.
use super::{CLOCK_CONTROL_OFFSET, SLCR_BASE_ADDR};
use arbitrary_int::{u4, u6, u7, u10};
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum BypassForce {
EnabledOrSetByBootMode = 0b0,
Bypassed = 0b1,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum BypassQual {
BypassForceBit = 0b0,
BootModeFourthBit = 0b1,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct PllCtrl {
/// Feedback divisor for the PLL.
///
/// NOTE: Before changing this value, the PLL must first be bypassed and then put into
/// reset mode.
#[bits(12..=18, rw)]
fdiv: u7,
/// Select source for the ARM PLL bypass control
#[bit(4, rw)]
bypass_force: BypassForce,
/// Select source for the ARM PLL bypass control
#[bit(3, rw)]
bypass_qual: BypassQual,
// Power-down control
#[bit(1, rw)]
pwrdwn: bool,
/// Reset control
#[bit(0, rw)]
reset: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct PllCfg {
#[bits(12..=21, rw)]
lock_count: u10,
/// Charge Pump control
#[bits(8..=11, rw)]
pll_cp: u4,
/// Loop resistor control
#[bits(4..=7, rw)]
pll_res: u4,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct PllStatus {
#[bit(5)]
io_pll_stable: bool,
#[bit(4)]
ddr_pll_stable: bool,
#[bit(3)]
arm_pll_stable: bool,
#[bit(2)]
io_pll_lock: bool,
#[bit(1)]
drr_pll_lock: bool,
#[bit(0)]
arm_pll_lock: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct FpgaClkControl {
// Reset value 0x1
#[bits(20..=25, rw)]
divisor_1: u6,
// Reset value 0x18
#[bits(8..=13, rw)]
divisor_0: u6,
#[bits(4..=5, rw)]
srcsel: SrcSelIo,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct FpgaClkBlock {
clk_ctrl: FpgaClkControl,
thr_ctrl: u32,
thr_cnt: u32,
thr_status: u32,
}
static_assertions::const_assert_eq!(core::mem::size_of::<FpgaClkBlock>(), 0x10);
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
pub enum SrcSelArm {
ArmPll = 0b00,
ArmPllAlt = 0b01,
DdrPll = 0b10,
IoPll = 0b11,
}
#[bitbybit::bitfield(u32)]
pub struct ArmClkCtrl {
#[bit(28, rw)]
cpu_peri_clk_act: bool,
#[bit(27, rw)]
cpu_1x_clk_act: bool,
#[bit(26, rw)]
cpu_2x_clk_act: bool,
#[bit(25, rw)]
cpu_3or2x_clk_act: bool,
#[bit(24, rw)]
cpu_6or4x_clk_act: bool,
/// Reset value: 0x4. There is a requirement for the quality of the high speed clock that
/// it has to be divided by an even number. This field must be equal to or greater than 2.
#[bits(8..=13, rw)]
divisor: u6,
/// Reset value: 0x0
#[bits(4..=5, rw)]
srcsel: SrcSelArm,
}
#[bitbybit::bitfield(u32)]
pub struct DdrClkCtrl {
/// Divisor for DDR 2x clock. Reset value: 0x6
#[bits(26..=31, rw)]
div_2x_clk: u6,
/// Divisor for DDR 3x clock. Only even divisors are allowed! Reset value: 0x4
#[bits(20..=25, rw)]
div_3x_clk: u6,
/// Reset value: 0x1
#[bit(1, rw)]
ddr_2x_clk_act: bool,
/// Reset value: 0x1
#[bit(0, rw)]
ddr_3x_clk_act: bool,
}
#[bitbybit::bitfield(u32)]
pub struct DciClkCtrl {
/// Second cascade divider. Reset value: 0x1E
#[bits(20..=25, rw)]
divisor_1: u6,
/// Reset value: 0x32
#[bits(8..=13, rw)]
divisor_0: u6,
/// Reset value: 0x1
#[bit(0, rw)]
clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct ClockRatioSelectReg {
/// Reset value: 0x1 (6:2:1 clock)
#[bit(0, rw)]
sel: ClockRatioSelect,
}
#[bitbybit::bitenum(u1, exhaustive = true)]
#[derive(Debug)]
pub enum ClockRatioSelect {
/// 4:2:1 clock ratio, which is an abbreviation for 4:2:2:1.
FourToTwoToOne = 0b0,
/// 6:2:1 clock ratio, which is an abbreviation for 6:3:2:1.
SixToTwoToOne = 0b1,
}
#[bitbybit::bitenum(u2, exhaustive = true)]
#[derive(Debug)]
pub enum SrcSelIo {
IoPll = 0b00,
IoPllAlt = 0b01,
ArmPll = 0b10,
DdrPll = 0b11,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct GigEthClkCtrl {
#[bits(20..=25, rw)]
divisor_1: u6,
#[bits(8..=13, rw)]
divisor_0: u6,
#[bit(6, rw)]
use_emio_tx_clk: bool,
#[bits(4..=5, rw)]
srcsel: SrcSelIo,
#[bit(0, rw)]
clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct CanClkCtrl {
#[bits(20..=25, rw)]
divisor_1: u6,
#[bits(8..=13, rw)]
divisor_0: u6,
#[bits(4..=5, rw)]
srcsel: SrcSelIo,
#[bit(1, rw)]
clk_1_act: bool,
#[bit(0, rw)]
clk_0_act: bool,
}
#[bitbybit::bitfield(u32)]
pub struct SingleCommonPeriphIoClkCtrl {
#[bits(8..=13, rw)]
divisor: u6,
#[bits(4..=5, rw)]
srcsel: SrcSelIo,
#[bit(0, rw)]
clk_act: bool,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct DualCommonPeriphIoClkCtrl {
#[bits(8..=13, rw)]
divisor: u6,
#[bits(4..=5, rw)]
srcsel: SrcSelIo,
#[bit(1, rw)]
clk_1_act: bool,
#[bit(0, rw)]
clk_0_act: bool,
}
#[bitbybit::bitenum(u3, exhaustive = true)]
#[derive(Debug)]
pub enum SrcSelTpiu {
IoPll = 0b000,
IoPllAlt = 0b001,
ArmPll = 0b010,
DdrPll = 0b011,
EmioTraceClk = 0b100,
EmioTraceClkAlt0 = 0b101,
EmioTraceClkAlt1 = 0b110,
EmioTraceClkAlt2 = 0b111,
}
#[bitbybit::bitfield(u32)]
pub struct TracePortClkCtrl {
#[bits(8..=13, rw)]
divisor: u6,
#[bits(4..=6, rw)]
srcsel: SrcSelTpiu,
#[bit(1, rw)]
clk_1x_clk_act: bool,
#[bit(0, rw)]
clk_act: bool,
}
/// AMBA peripheral clock control.
///
/// These clocks must be enabled if you want to read from the peripheral register space.
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct AperClkCtrl {
#[bit(24, rw)]
smc_1x_clk_act: bool,
#[bit(23, rw)]
lqspi_1x_clk_act: bool,
#[bit(22, rw)]
gpio_1x_clk_act: bool,
#[bit(21, rw)]
uart_1_1x_clk_act: bool,
#[bit(20, rw)]
uart_0_1x_clk_act: bool,
#[bit(19, rw)]
i2c_1_1x_clk_act: bool,
#[bit(18, rw)]
i2c_0_1x_clk_act: bool,
#[bit(17, rw)]
can_1_1x_clk_act: bool,
#[bit(16, rw)]
can_0_1x_clk_act: bool,
#[bit(15, rw)]
spi_1_1x_clk_act: bool,
#[bit(14, rw)]
spi_0_1x_clk_act: bool,
#[bit(11, rw)]
sdio_1_1x_clk_act: bool,
#[bit(10, rw)]
sdio_0_1x_clk_act: bool,
#[bit(7, rw)]
gem_1_1x_clk_act: bool,
#[bit(6, rw)]
gem_0_1x_clk_act: bool,
#[bit(3, rw)]
usb_1_cpu_1x_clk_act: bool,
#[bit(2, rw)]
usb_0_cpu_1x_clk_act: bool,
#[bit(0, rw)]
dma_cpu_2x_clk_act: bool,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct ClockControl {
arm_pll: PllCtrl,
ddr_pll: PllCtrl,
io_pll: PllCtrl,
pll_status: PllStatus,
arm_pll_cfg: PllCfg,
ddr_pll_cfg: PllCfg,
io_pll_cfg: PllCfg,
_gap0: u32,
arm_clk_ctrl: ArmClkCtrl,
ddr_clk_ctrl: DdrClkCtrl,
dci_clk_ctrl: DciClkCtrl,
/// AMBA peripheral clock control
aper_clk_ctrl: AperClkCtrl,
usb_0_clk_ctrl: u32,
usb_1_clk_ctrl: u32,
gem_0_rclk_ctrl: u32,
gem_1_rclk_ctrl: u32,
gem_0_clk_ctrl: GigEthClkCtrl,
gem_1_clk_ctrl: GigEthClkCtrl,
smc_clk_ctrl: SingleCommonPeriphIoClkCtrl,
lqspi_clk_ctrl: SingleCommonPeriphIoClkCtrl,
sdio_clk_ctrl: DualCommonPeriphIoClkCtrl,
uart_clk_ctrl: DualCommonPeriphIoClkCtrl,
spi_clk_ctrl: DualCommonPeriphIoClkCtrl,
can_clk_ctrl: CanClkCtrl,
can_mioclk_ctrl: u32,
/// Debug or Trace Port clock control.
dbg_clk_ctrl: TracePortClkCtrl,
pcap_clk_ctrl: SingleCommonPeriphIoClkCtrl,
topsw_clk_ctrl: u32,
#[mmio(inner)]
fpga_0_clk_ctrl: FpgaClkBlock,
#[mmio(inner)]
fpga_1_clk_ctrl: FpgaClkBlock,
#[mmio(inner)]
fpga_2_clk_ctrl: FpgaClkBlock,
#[mmio(inner)]
fpga_3_clk_ctrl: FpgaClkBlock,
_gap1: [u32; 5],
clk_621_true: ClockRatioSelectReg,
}
impl ClockControl {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioClockControl<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + CLOCK_CONTROL_OFFSET) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<ClockControl>(), 0xC8);

41
zynq7000/src/slcr/mio.rs Normal file
View File

@ -0,0 +1,41 @@
//! # SLCR MIO (Multiplexed I/O) configuration registers
//!
//! Writing any of these registers required unlocking the SLCR first.
use arbitrary_int::{u2, u3};
#[bitbybit::bitenum(u1, exhaustive = true)]
pub enum Speed {
SlowCmosEdge = 0b0,
FastCmosEdge = 0b1,
}
#[bitbybit::bitenum(u3)]
pub enum IoType {
LvCmos18 = 0b001,
LvCmos25 = 0b010,
LvCmos33 = 0b011,
Hstl = 0b100,
}
#[bitbybit::bitfield(u32)]
#[derive(Debug)]
pub struct Config {
#[bit(13, rw)]
disable_hstl_rcvr: bool,
#[bit(12, rw)]
pullup: bool,
#[bits(9..=11, rw)]
io_type: Option<IoType>,
#[bit(8, rw)]
speed: Speed,
#[bits(5..=7, rw)]
l3_sel: u3,
#[bits(3..=4, rw)]
l2_sel: u2,
#[bit(2, rw)]
l1_sel: bool,
#[bit(1, rw)]
l0_sel: bool,
#[bit(0, rw)]
tri_enable: bool,
}

206
zynq7000/src/slcr/mod.rs Normal file
View File

@ -0,0 +1,206 @@
//! System Level Control Registers (slcr)
//!
//! Writing any of these registers required unlocking the SLCR first.
use arbitrary_int::u4;
pub use clocks::{ClockControl, MmioClockControl};
pub use reset::{MmioResetControl, ResetControl};
const SLCR_BASE_ADDR: usize = 0xF8000000;
const CLOCK_CONTROL_OFFSET: usize = 0x100;
const RESET_BLOCK_OFFSET: usize = 0x200;
const GPIOB_OFFSET: usize = 0xB00;
const DDRIOB_OFFSET: usize = 0xB40;
pub mod clocks;
pub mod mio;
pub mod reset;
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct DdrIoB {
ddriob_addr0: u32,
ddriob_addr1: u32,
ddriob_data0: u32,
ddriob_data1: u32,
ddriob_diff0: u32,
ddriob_diff1: u32,
ddriob_clock: u32,
ddriob_drive_slew_addr: u32,
ddriob_drive_slew_data: u32,
ddriob_drive_slew_diff: u32,
ddriob_drive_slew_clock: u32,
ddriob_ddr_ctrl: u32,
ddriob_dci_ctrl: u32,
ddriob_dci_status: u32,
}
impl DdrIoB {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioDdrIoB<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + DDRIOB_OFFSET) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<DdrIoB>(), 0x38);
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct GpiobCtrl {
ctrl: u32,
cfg_cmos18: u32,
cfg_cmos25: u32,
cfg_cmos33: u32,
_gap17: u32,
cfg_hstl: u32,
drvr_bias_ctrl: u32,
}
impl GpiobCtrl {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioGpiobCtrl<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + GPIOB_OFFSET) }
}
}
#[bitbybit::bitfield(u32)]
pub struct BootModeRegister {
#[bit(4, r)]
pll_bypass: bool,
#[bits(0..=3, r)]
boot_mode: u4,
}
#[bitbybit::bitenum(u4)]
#[derive(Debug, PartialEq, Eq)]
pub enum LevelShifterConfig {
DisableAll = 0x00,
EnablePsToPl = 0xA,
EnableAll = 0xF,
}
#[bitbybit::bitfield(u32)]
pub struct LevelShifterReg {
#[bits(0..=3, rw)]
user_lvl_shftr_en: Option<LevelShifterConfig>,
}
/// System Level Control Registers
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct Slcr {
/// Secure configuration lock.
scl: u32,
/// SLCR write protection lock
lock: u32,
/// SLCR write protection unlock
unlock: u32,
/// SLCR write protection status
lock_status: u32,
_gap0: [u32; 0x3C],
#[mmio(inner)]
clk_ctrl: ClockControl,
_gap1: [u32; 0x0E],
#[mmio(inner)]
reset_ctrl: ResetControl,
_gap2: [u32; 0x02],
reboot_status: u32,
boot_mode: BootModeRegister,
_gap3: [u32; 0x28],
apu_ctrl: u32,
wdt_clk_set: u32,
_gap4: [u32; 0x4E],
tz_dma_ns: u32,
tz_dma_irq_ns: u32,
tz_dma_periph_ns: u32,
_gap5: [u32; 0x39],
pss_idcode: u32,
_gap6: [u32; 0x33],
ddr_urgent: u32,
_gap7: [u32; 0x02],
ddr_cal_start: u32,
_gap8: u32,
ddr_ref_start: u32,
ddr_cmd_status: u32,
ddr_urgent_sel: u32,
ddr_dfi_status: u32,
_gap9: [u32; 0x37],
mio_pins: [mio::Config; 0x36],
_gap10: [u32; 0x0B],
mio_loopback: u32,
_gap11: u32,
mio_mst_tri_0: u32,
mio_mst_tri_1: u32,
_gap12: [u32; 7],
sd_0_wp_cd_sel: u32,
sd_1_wp_cd_sel: u32,
_gap13: [u32; 0x32],
lvl_shftr_en: LevelShifterReg,
_gap14: [u32; 0x03],
ocm_cfg: u32,
_gap15: [u32; 0x42],
reserved: u32,
_gap16: [u32; 0x38],
_gap18: [u32; 0x09],
#[mmio(inner)]
gpiob: GpiobCtrl,
#[mmio(inner)]
ddriob: DdrIoB,
}
static_assertions::const_assert_eq!(core::mem::size_of::<Slcr>(), 0xB78);
impl Slcr {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioSlcr<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR) }
}
}

View File

@ -0,0 +1,79 @@
use super::{RESET_BLOCK_OFFSET, SLCR_BASE_ADDR};
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct DualClockReset {
/// Peripheral 1 AMBA software reset.
#[bit(1, rw)]
periph1_cpu1x_rst: bool,
/// Peripheral 0 AMBA software reset.
#[bit(0, rw)]
periph0_cpu1x_rst: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct DualRefAndClockReset {
/// Periperal 1 Reference software reset.
#[bit(3, rw)]
periph1_ref_rst: bool,
/// Peripheral 0 Reference software reset.
#[bit(2, rw)]
periph0_ref_rst: bool,
/// Peripheral 1 AMBA software reset.
#[bit(1, rw)]
periph1_cpu1x_rst: bool,
/// Peripheral 0 AMBA software reset.
#[bit(0, rw)]
periph0_cpu1x_rst: bool,
}
#[bitbybit::bitfield(u32, default = 0x0)]
#[derive(Debug)]
pub struct GpioClockReset {
#[bit(0, rw)]
gpio_cpu1x_rst: bool,
}
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct ResetControl {
/// PS Software reset control
pss: u32,
ddr: u32,
/// Central interconnect reset control
topsw: u32,
dmac: u32,
usb: u32,
gem: u32,
sdio: DualRefAndClockReset,
spi: DualRefAndClockReset,
can: DualClockReset,
i2c: DualClockReset,
uart: DualRefAndClockReset,
gpio: GpioClockReset,
lqspi: u32,
smc: u32,
ocm: u32,
_gap0: u32,
fpga: u32,
a9_cpu: u32,
_gap1: u32,
rs_awdt: u32,
}
impl ResetControl {
/// Create a new handle to this peripheral.
///
/// Writing to this register requires unlocking the SLCR registers first.
///
/// # Safety
///
/// If you create multiple instances of this handle at the same time, you are responsible for
/// ensuring that there are no read-modify-write races on any of the registers.
pub unsafe fn new_mmio_fixed() -> MmioResetControl<'static> {
unsafe { Self::new_mmio_at(SLCR_BASE_ADDR + RESET_BLOCK_OFFSET) }
}
}
static_assertions::const_assert_eq!(core::mem::size_of::<ResetControl>(), 0x50);

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