diff --git a/README.md b/README.md
index a23a61d..a208d0a 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,10 @@ 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 project contains the following crates:
diff --git a/ferris-zedboard.jpeg b/ferris-zedboard.jpeg
new file mode 100644
index 0000000..3922eea
Binary files /dev/null and b/ferris-zedboard.jpeg differ
diff --git a/firmware/examples/zedboard/Cargo.toml b/firmware/examples/zedboard/Cargo.toml
index 650c039..539db25 100644
--- a/firmware/examples/zedboard/Cargo.toml
+++ b/firmware/examples/zedboard/Cargo.toml
@@ -27,9 +27,14 @@ critical-section = "1"
static_cell = "2"
embedded-alloc = "0.7"
embedded-hal = "1"
+embedded-hal-bus = "0.3"
embedded-hal-async = "1"
+dummy-pin = "1"
fugit = "0.3"
+embedded-graphics = "0.8"
log = "0.4"
+ssd1306 = { version = "0.10" }
+tinybmp = "0.7"
rand = { version = "0.10", default-features = false }
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
diff --git a/firmware/examples/zedboard/assets/ferris-flat-happy-small.bmp b/firmware/examples/zedboard/assets/ferris-flat-happy-small.bmp
new file mode 100644
index 0000000..845c347
Binary files /dev/null and b/firmware/examples/zedboard/assets/ferris-flat-happy-small.bmp differ
diff --git a/firmware/examples/zedboard/assets/ferris-flat-happy-small.svg b/firmware/examples/zedboard/assets/ferris-flat-happy-small.svg
new file mode 100644
index 0000000..80bef48
--- /dev/null
+++ b/firmware/examples/zedboard/assets/ferris-flat-happy-small.svg
@@ -0,0 +1,16 @@
+
+
diff --git a/firmware/examples/zedboard/assets/ferris-flat-happy.svg b/firmware/examples/zedboard/assets/ferris-flat-happy.svg
new file mode 100644
index 0000000..8ede0b2
--- /dev/null
+++ b/firmware/examples/zedboard/assets/ferris-flat-happy.svg
@@ -0,0 +1,16 @@
+
+
diff --git a/firmware/examples/zedboard/assets/ferris-flat-happy.xcf b/firmware/examples/zedboard/assets/ferris-flat-happy.xcf
new file mode 100644
index 0000000..47446e5
Binary files /dev/null and b/firmware/examples/zedboard/assets/ferris-flat-happy.xcf differ
diff --git a/firmware/examples/zedboard/assets/rust-logo-single-path.bmp b/firmware/examples/zedboard/assets/rust-logo-single-path.bmp
new file mode 100644
index 0000000..0a3a8fe
Binary files /dev/null and b/firmware/examples/zedboard/assets/rust-logo-single-path.bmp differ
diff --git a/firmware/examples/zedboard/assets/rust-logo-single-path.svg b/firmware/examples/zedboard/assets/rust-logo-single-path.svg
new file mode 100644
index 0000000..51ce9c5
--- /dev/null
+++ b/firmware/examples/zedboard/assets/rust-logo-single-path.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/firmware/examples/zedboard/assets/rust-logo-single-path.xcf b/firmware/examples/zedboard/assets/rust-logo-single-path.xcf
new file mode 100644
index 0000000..e17d1c5
Binary files /dev/null and b/firmware/examples/zedboard/assets/rust-logo-single-path.xcf differ
diff --git a/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs b/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs
index d6c1058..9afa387 100644
--- a/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs
+++ b/firmware/examples/zedboard/src/bin/l3gd20h-spi-mio.rs
@@ -57,12 +57,17 @@ async fn main(spawner: Spawner) -> ! {
// 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));
+ let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
+ // SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
+ // actually seems to be problematic. We take 200 MHz here, which is significantly larger than
+ // the CPU 1x clock which is around 110 MHz.
+ spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
+
+ assert!(
+ clocks.io_clocks().spi_clk().raw()
+ > (clocks.arm_clocks().cpu_1x_clk().raw() as f32 * 1.2) as u32,
+ "SPI reference clock must be larger than CPU 1x clock"
+ );
// Set up the global interrupt controller.
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
@@ -99,23 +104,21 @@ async fn main(spawner: Spawner) -> ! {
if DEBUG_SPI_CLK_CONFIG {
info!(
- "SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
+ "SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}",
clocks.arm_clocks().cpu_1x_clk(),
clocks.io_clocks().ref_clk(),
clocks.io_clocks().spi_clk(),
- spi_ref_clk_div
);
}
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::BaudDivSel::By64,
//l3gd20::MODE,
embedded_hal::spi::MODE_3,
- spi::SlaveSelectConfig::AutoWithAutoStart,
+ spi::SlaveSelectConfig::AutoCsAutoStart,
),
(
gpio_pins.mio.mio12,
@@ -125,10 +128,13 @@ async fn main(spawner: Spawner) -> ! {
gpio_pins.mio.mio13,
)
.unwrap();
+ let sclk = Hertz::from_raw(
+ clocks.io_clocks().spi_clk().raw() / zynq7000::spi::BaudDivSel::By64.div_value() as u32,
+ );
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;
+ assert!(sclk <= Hertz::from_raw(10_000_000));
+ let min_delay = (sclk.raw() * 5) / 1_000_000_000;
spi.inner().configure_delays(
DelayControl::builder()
.with_inter_word_cs_deassert(0)
diff --git a/firmware/examples/zedboard/src/bin/oled.rs b/firmware/examples/zedboard/src/bin/oled.rs
new file mode 100644
index 0000000..06d7295
--- /dev/null
+++ b/firmware/examples/zedboard/src/bin/oled.rs
@@ -0,0 +1,297 @@
+#![no_std]
+#![no_main]
+
+use aarch32_cpu::asm::nop;
+use core::panic::PanicInfo;
+use dummy_pin::DummyPin;
+use embassy_executor::Spawner;
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+use embassy_time::{Delay, Duration, Ticker};
+use embedded_graphics::{Drawable as _, geometry::Point};
+use embedded_hal::digital::StatefulOutputPin;
+use embedded_hal_async::delay::DelayNs as _;
+use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
+use embedded_io::Write;
+use log::{error, info};
+use ssd1306::{Ssd1306, prelude::*};
+use zedboard::PS_CLOCK_FREQUENCY;
+use zynq7000_hal::{
+ BootMode, clocks, gic, gpio, gtc, spi,
+ time::Hertz,
+ uart::{self, TxAsync},
+};
+
+use embedded_graphics::image::Image;
+use tinybmp::Bmp;
+
+use zynq7000_rt as _;
+
+const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
+
+/// Entry point which calls the embassy main method.
+#[zynq7000_rt::entry]
+fn entry_point() -> ! {
+ main();
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) -> ! {
+ let periphs = zynq7000_hal::init(zynq7000_hal::Config {
+ init_l2_cache: true,
+ level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
+ interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
+ })
+ .unwrap();
+ // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
+ let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
+
+ let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
+ // SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
+ // actually seems to be problematic. We take 200 MHz here, which is significantly larger than
+ // the CPU 1x clock which is around 110 MHz.
+ spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
+
+ assert!(
+ clocks.io_clocks().spi_clk().raw()
+ > (clocks.arm_clocks().cpu_1x_clk().raw() as f32 * 1.2) as u32,
+ "SPI reference clock must be larger than CPU 1x clock"
+ );
+
+ let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
+
+ // Set up global timer counter and embassy time driver.
+ let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
+ zynq7000_embassy::init(clocks.arm_clocks(), gtc);
+
+ // Set up the UART, we are logging with it.
+ let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
+ .unwrap()
+ .0;
+ let mut uart = uart::Uart::new_with_mio_for_uart_1(
+ periphs.uart_1,
+ uart::Config::new_with_clk_config(uart_clk_config),
+ (gpio_pins.mio.mio48, gpio_pins.mio.mio49),
+ )
+ .unwrap();
+ uart.write_all(INIT_STRING.as_bytes()).unwrap();
+ uart.flush().unwrap();
+
+ // Safety: We are not multi-threaded yet.
+ let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace)
+ .expect("Failed to initialize async logger");
+
+ let boot_mode = BootMode::new_from_regs();
+ info!("Boot mode: {:?}", boot_mode);
+ info!(
+ "SPI reference clock speed: {:?}",
+ clocks.io_clocks().spi_clk()
+ );
+
+ spawner.spawn(logger_task(uart, log_reader).unwrap());
+
+ let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
+ let emio_leds: [gpio::Output; 8] = [
+ gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
+ gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
+ ];
+ spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
+
+ let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
+
+ let mut reset_pin =
+ gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
+ let mut oled_vdd_switch =
+ gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
+ let mut oled_vbat_switch =
+ gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
+ Delay.delay_ms(100).await;
+
+ let spi = spi::Spi::new_for_emio(
+ periphs.spi_0,
+ spi::Config::calculate_for_io_clock(
+ Hertz::MHz(8),
+ clocks.io_clocks(),
+ embedded_hal::spi::MODE_0,
+ spi::SlaveSelectConfig::AutoCsManualStart,
+ ),
+ )
+ .expect("Failed to initialize SPI");
+ let exclusive_device = ExclusiveDevice::new(spi, DummyPin::new_high(), NoDelay)
+ .expect("Failed to create exclusive SPI device");
+ let spi_if = SPIInterface::new(exclusive_device, dc_pin);
+ let mut ssd1306 = Ssd1306::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
+
+ oled_vdd_switch.set_low();
+ oled_vbat_switch.set_low();
+ Delay.delay_ms(100).await;
+
+ ssd1306.reset(&mut reset_pin, &mut embassy_time::Delay {});
+ let mut display = ssd1306.into_buffered_graphics_mode();
+ display.init().unwrap();
+
+ // Include the BMP file data.
+ let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
+ let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
+ // Parse the BMP file.
+ let bmp_rust = Bmp::from_slice(rust_logo_data).unwrap();
+ let bmp_ferris = Bmp::from_slice(ferris_data).unwrap();
+ // Draw the image with the top left corner at (10, 20) by wrapping it in
+ // an embedded-graphics `Image`.
+ Image::new(&bmp_rust, Point::new(0, 0))
+ .draw(&mut display)
+ .unwrap();
+ Image::new(&bmp_ferris, Point::new(32, 0))
+ .draw(&mut display)
+ .unwrap();
+ display.flush().unwrap();
+ let mut ticker = Ticker::every(Duration::from_millis(50));
+ let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
+
+ loop {
+ Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
+ .draw(&mut display)
+ .unwrap();
+ display.flush().unwrap();
+ ferris.step();
+ ticker.next().await;
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Direction {
+ Right,
+ Left,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct FerrisMovement {
+ pub pos: u16,
+ pub dir: Direction,
+ pub left_threshold: u16,
+ pub right_threshold: u16,
+}
+
+impl FerrisMovement {
+ /// Creates a new movement controller.
+ /// Assumes: left_threshold <= right_threshold and pos is within that range.
+ pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
+ Self {
+ pos,
+ dir,
+ left_threshold,
+ right_threshold,
+ }
+ }
+
+ /// Move one tick and "bounce" between thresholds.
+ pub fn step(&mut self) {
+ match self.dir {
+ Direction::Right => {
+ if self.pos >= self.right_threshold {
+ self.dir = Direction::Left; // flip at right boundary
+ } else {
+ self.pos += 1;
+ }
+ }
+ Direction::Left => {
+ if self.pos <= self.left_threshold {
+ self.dir = Direction::Right; // flip at left boundary
+ } else {
+ self.pos -= 1;
+ }
+ }
+ }
+ }
+}
+
+#[embassy_executor::task]
+pub async fn logger_task(
+ uart: uart::Uart,
+ reader: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 4096>,
+) -> ! {
+ let (tx, _) = uart.split();
+ let mut tx_async = TxAsync::new(tx);
+ let mut log_buf: [u8; 2048] = [0; 2048];
+ loop {
+ let read_bytes = reader.read(&mut log_buf).await;
+ if read_bytes > 0 {
+ tx_async.write(&log_buf[0..read_bytes]).unwrap().await;
+ }
+ }
+}
+
+#[embassy_executor::task]
+pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
+ let mut ticker = Ticker::every(Duration::from_millis(200));
+ 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;
+ }
+}
+
+#[zynq7000_rt::irq]
+fn irq_handler() {
+ let mut gic_helper = gic::GicInterruptHelper::new();
+ let irq_info = gic_helper.acknowledge_interrupt();
+ match irq_info.interrupt() {
+ gic::Interrupt::Sgi(_) => (),
+ gic::Interrupt::Ppi(ppi_interrupt) => {
+ if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
+ unsafe {
+ zynq7000_embassy::on_interrupt();
+ }
+ }
+ }
+ gic::Interrupt::Spi(spi_interrupt) => match spi_interrupt {
+ gic::SpiInterrupt::Uart1 => {
+ zynq7000_hal::uart::tx_async::on_interrupt_tx(uart::UartId::Uart1);
+ }
+ _ => {
+ log::warn!("Unhandled SPI interrupt: {:?}", spi_interrupt);
+ }
+ },
+ gic::Interrupt::Invalid(_) => (),
+ gic::Interrupt::Spurious => (),
+ }
+ gic_helper.end_of_interrupt(irq_info);
+}
+
+#[zynq7000_rt::exception(DataAbort)]
+fn data_abort_handler(_faulting_addr: usize) -> ! {
+ loop {
+ nop();
+ }
+}
+
+#[zynq7000_rt::exception(Undefined)]
+fn undefined_handler(_faulting_addr: usize) -> ! {
+ loop {
+ nop();
+ }
+}
+
+#[zynq7000_rt::exception(PrefetchAbort)]
+fn prefetch_handler(_faulting_addr: usize) -> ! {
+ loop {
+ nop();
+ }
+}
+
+/// Panic handler
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ error!("Panic: {info:?}");
+ loop {}
+}
diff --git a/firmware/zynq7000-hal/src/spi/asynch.rs b/firmware/zynq7000-hal/src/spi/asynch.rs
index 2308ca9..01b5ac4 100644
--- a/firmware/zynq7000-hal/src/spi/asynch.rs
+++ b/firmware/zynq7000-hal/src/spi/asynch.rs
@@ -37,7 +37,7 @@ pub fn on_interrupt(peripheral: SpiId) {
let enabled_irqs = spi.read_enabled_interrupts();
// Prevent spurious interrupts from messing with out logic here.
spi.disable_interrupts();
- let interrupt_status = spi.read_isr();
+ let interrupt_status = spi.read_interrupt_status();
spi.clear_interrupts();
// IRQ is not related.
if !enabled_irqs.tx_below_threshold()
diff --git a/firmware/zynq7000-hal/src/spi/mod.rs b/firmware/zynq7000-hal/src/spi/mod.rs
index 4ad1e29..24f624b 100644
--- a/firmware/zynq7000-hal/src/spi/mod.rs
+++ b/firmware/zynq7000-hal/src/spi/mod.rs
@@ -19,9 +19,10 @@ use arbitrary_int::{prelude::*, u3, u4, u6};
use embedded_hal::delay::DelayNs;
pub use embedded_hal::spi::Mode;
use zynq7000::slcr::reset::DualRefAndClockResetSpiUart;
+pub use zynq7000::spi::DelayControl;
use zynq7000::spi::{
- BaudDivSel, DelayControl, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus,
- MmioRegisters, SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
+ BaudDivSel, FifoWrite, InterruptControl, InterruptEnabled, InterruptStatus, MmioRegisters,
+ SPI_0_BASE_ADDR, SPI_1_BASE_ADDR,
};
pub const FIFO_DEPTH: usize = 128;
@@ -352,13 +353,16 @@ impl ChipSelect {
/// Slave select configuration.
pub enum SlaveSelectConfig {
/// User must take care of controlling slave select lines as well as issuing a start command.
- ManualWithManualStart = 0b11,
- ManualAutoStart = 0b10,
+ ManualCsManualStart = 0b11,
+ /// Software controls the slave select, but the controller hardware automatically starts to
+ /// serialize data when there is data in the TxFIFO.
+ ManualCsAutoStart = 0b10,
/// Hardware slave select, but start needs to be issued manually.
- AutoWithManualStart = 0b01,
- /// Hardware slave select, auto serialiation if there is data in the TX FIFO.
+ AutoCsManualStart = 0b01,
+ /// Hardware slave select, auto serialiation if there is data in the TX FIFO. Might be
+ /// problematic for higher SPI speeds, where the processor can not fill the TX FIFO fast enough.
#[default]
- AutoWithAutoStart = 0b00,
+ AutoCsAutoStart = 0b00,
}
#[derive(Debug, Copy, Clone)]
@@ -379,6 +383,31 @@ impl Config {
}
}
+ pub fn calculate_for_io_clock(
+ target_clock: Hertz,
+ io_clock: &IoClocks,
+ init_mode: Mode,
+ ss_config: SlaveSelectConfig,
+ ) -> Self {
+ let divisor_raw = io_clock.spi_clk().raw().div_ceil(target_clock.raw());
+ let baud_div_sel = match divisor_raw {
+ 0..=4 => BaudDivSel::By4,
+ 5..=8 => BaudDivSel::By8,
+ 9..=16 => BaudDivSel::By16,
+ 17..=32 => BaudDivSel::By32,
+ 33..=64 => BaudDivSel::By64,
+ 65..=128 => BaudDivSel::By128,
+ 129..=256 => BaudDivSel::By256,
+ _ => BaudDivSel::By256,
+ };
+ Self {
+ baud_div: baud_div_sel,
+ init_mode,
+ ss_config,
+ with_ext_decoding: false,
+ }
+ }
+
pub fn enable_external_decoding(&mut self) {
self.with_ext_decoding = true;
}
@@ -474,10 +503,10 @@ impl SpiLowLevel {
pub fn reconfigure(&mut self, config: Config) {
self.regs.write_enable(0);
let (man_ss, man_start) = match config.ss_config {
- SlaveSelectConfig::ManualWithManualStart => (true, true),
- SlaveSelectConfig::ManualAutoStart => (true, false),
- SlaveSelectConfig::AutoWithManualStart => (false, true),
- SlaveSelectConfig::AutoWithAutoStart => (false, false),
+ SlaveSelectConfig::ManualCsManualStart => (true, true),
+ SlaveSelectConfig::ManualCsAutoStart => (true, false),
+ SlaveSelectConfig::AutoCsManualStart => (false, true),
+ SlaveSelectConfig::AutoCsAutoStart => (false, false),
};
let (cpol, cpha) = spi_mode_const_to_cpol_cpha(config.init_mode);
@@ -530,14 +559,11 @@ impl SpiLowLevel {
#[inline]
pub fn issue_manual_start(&mut self) {
- self.regs.modify_cr(|mut val| {
- val.set_manual_start(true);
- val
- });
+ self.regs.modify_cr(|val| val.with_manual_start(true));
}
#[inline]
- pub fn read_isr(&self) -> InterruptStatus {
+ pub fn read_interrupt_status(&self) -> InterruptStatus {
self.regs.read_interrupt_status()
}
@@ -569,6 +595,11 @@ impl SpiLowLevel {
Ok(())
}
+ #[inline]
+ pub fn write_delay_control(&mut self, delay_control: DelayControl) {
+ self.regs.write_delay_control(delay_control);
+ }
+
/// This disables all interrupts relevant for non-blocking interrupt driven SPI operation
/// in SPI master mode.
#[inline]
@@ -638,9 +669,7 @@ impl core::ops::DerefMut for SpiLowLevel {
/// Blocking Driver for the PS SPI peripheral in master mode.
pub struct Spi {
inner: SpiLowLevel,
- sclk: Hertz,
config: Config,
- outstanding_rx: bool,
}
#[derive(Debug, thiserror::Error)]
@@ -669,7 +698,6 @@ pub enum SpiConstructionError {
impl Spi {
pub fn new_no_hw_ss(
spi: impl PsSpi,
- clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
) -> Result {
@@ -687,17 +715,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
- Ok(Self::new_generic_unchecked(
- spi_id,
- spi.reg_block(),
- clocks,
- config,
- ))
+ Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_one_hw_cs(
spi: impl PsSpi,
- clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pin: Ss,
@@ -717,17 +739,11 @@ impl Spi {
IoPeriphPin::new(spi_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pin, SPI_MUX_CONF, Some(false));
- Ok(Self::new_generic_unchecked(
- spi_id,
- spi.reg_block(),
- clocks,
- config,
- ))
+ Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_two_hw_cs(
spi: impl PsSpi,
- clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1),
@@ -757,12 +773,7 @@ impl Spi {
IoPeriphPin::new(spi_pins.2, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
- Ok(Self::new_generic_unchecked(
- spi_id,
- spi.reg_block(),
- clocks,
- config,
- ))
+ Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
}
pub fn new_with_three_hw_cs<
@@ -774,7 +785,6 @@ impl Spi {
Ss2: SsPin,
>(
spi: impl PsSpi,
- clocks: &IoClocks,
config: Config,
spi_pins: (Sck, Mosi, Miso),
ss_pins: (Ss0, Ss1, Ss2),
@@ -807,36 +817,40 @@ impl Spi {
IoPeriphPin::new(ss_pins.0, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.1, SPI_MUX_CONF, Some(false));
IoPeriphPin::new(ss_pins.2, SPI_MUX_CONF, Some(false));
+ Ok(Self::new_generic_unchecked(spi_id, spi.reg_block(), config))
+ }
+
+ /// Constructor for usage with EMIO pins.
+ pub fn new_for_emio(spi: impl PsSpi, config: Config) -> Result {
+ let spi_id = spi.id();
+ if spi_id.is_none() {
+ return Err(InvalidPsSpiError);
+ }
Ok(Self::new_generic_unchecked(
- spi_id,
+ spi_id.unwrap(),
spi.reg_block(),
- clocks,
config,
))
}
- pub fn new_generic_unchecked(
- id: SpiId,
- regs: MmioRegisters<'static>,
- clocks: &IoClocks,
- config: Config,
- ) -> Self {
+ pub fn new_generic_unchecked(id: SpiId, regs: MmioRegisters<'static>, config: Config) -> Self {
let periph_sel = match id {
SpiId::Spi0 => crate::PeriphSelect::Spi0,
SpiId::Spi1 => crate::PeriphSelect::Spi1,
};
enable_amba_peripheral_clock(periph_sel);
- let sclk = clocks.spi_clk() / config.baud_div.div_value() as u32;
let mut spi = Self {
inner: SpiLowLevel { regs, id },
- sclk,
config,
- outstanding_rx: false,
};
spi.reset_and_reconfigure();
spi
}
+ pub fn write_delay_control(&mut self, delay_control: DelayControl) {
+ self.inner.write_delay_control(delay_control);
+ }
+
/// Re-configures the SPI peripheral with the initial configuration.
pub fn reconfigure(&mut self) {
self.inner.reconfigure(self.config);
@@ -852,19 +866,13 @@ impl Spi {
#[inline]
pub fn issue_manual_start_for_manual_cfg(&mut self) {
- if self.config.ss_config == SlaveSelectConfig::AutoWithManualStart
- || self.config.ss_config == SlaveSelectConfig::ManualWithManualStart
+ if self.config.ss_config == SlaveSelectConfig::AutoCsManualStart
+ || self.config.ss_config == SlaveSelectConfig::ManualCsManualStart
{
self.inner.issue_manual_start();
}
}
- /// Retrieve SCLK clock frequency currently configured for this SPI.
- #[inline]
- pub const fn sclk(&self) -> Hertz {
- self.sclk
- }
-
/// Retrieve inner low-level helper.
#[inline]
pub const fn inner(&mut self) -> &mut SpiLowLevel {
@@ -876,7 +884,7 @@ impl Spi {
&mut self.inner.regs
}
- fn initial_fifo_fill(&mut self, words: &[u8]) -> usize {
+ fn prefill_fifo(&mut self, words: &[u8]) -> usize {
let write_len = core::cmp::min(FIFO_DEPTH, words.len());
(0..write_len).for_each(|idx| {
self.inner.write_fifo_unchecked(words[idx]);
@@ -892,7 +900,7 @@ impl Spi {
self.inner.regs.write_rx_trig(1);
// Fill the FIFO with initial data.
- let written = self.initial_fifo_fill(words);
+ let written = self.prefill_fifo(words);
// We assume that the slave select configuration was already performed, but we take
// care of issuing a start if necessary.
@@ -900,7 +908,7 @@ impl Spi {
written
}
- fn read(&mut self, words: &mut [u8]) {
+ pub fn read(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
@@ -935,33 +943,38 @@ impl Spi {
}
}
- fn write(&mut self, words: &[u8]) {
+ pub fn write(&mut self, words: &[u8]) {
if words.is_empty() {
return;
}
let mut written = self.prepare_generic_blocking_transfer(words);
let mut read_idx = 0;
+ if words.len() > FIFO_DEPTH {
+ self.inner.regs.write_tx_trig(FIFO_DEPTH as u32 / 2);
+ }
- while written < words.len() {
+ loop {
let status = self.regs().read_interrupt_status();
+ let rx_pending = read_idx < words.len();
+ let tx_pending = written < words.len();
// We empty the FIFO to prevent it filling up completely, as long as we have to write
// bytes
- if status.rx_not_empty() {
+ if status.rx_not_empty() && rx_pending {
self.inner.read_fifo_unchecked();
read_idx += 1;
}
- if !status.tx_full() {
+ if !status.tx_full() && tx_pending {
self.inner.write_fifo_unchecked(words[written]);
written += 1;
}
+ if !rx_pending && !tx_pending {
+ break;
+ }
}
- // We exit once all bytes have been written, so some bytes to read might be outstanding.
- // We use the FIFO trigger mechanism to determine when we can read all the remaining bytes.
- self.regs().write_rx_trig((words.len() - read_idx) as u32);
- self.outstanding_rx = true;
+ self.inner.regs.write_tx_trig(1);
}
- fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
+ pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) {
if read.is_empty() {
return;
}
@@ -997,7 +1010,7 @@ impl Spi {
}
}
- fn transfer_in_place(&mut self, words: &mut [u8]) {
+ pub fn transfer_in_place(&mut self, words: &mut [u8]) {
if words.is_empty() {
return;
}
@@ -1007,7 +1020,7 @@ impl Spi {
let mut writes_finished = write_idx == words.len();
let mut reads_finished = false;
while !writes_finished || !reads_finished {
- let status = self.inner.read_isr();
+ let status = self.inner.read_interrupt_status();
if status.rx_not_empty() && !reads_finished {
words[read_idx] = self.inner.read_fifo_unchecked();
read_idx += 1;
@@ -1024,16 +1037,16 @@ impl Spi {
/// Blocking flush implementation.
fn flush(&mut self) {
- if !self.outstanding_rx {
- return;
- }
- let rx_trig = self.inner.read_rx_not_empty_threshold();
- while !self.inner.read_isr().rx_not_empty() {}
- (0..rx_trig).for_each(|_| {
+ self.inner.write_tx_trig(1);
+ let status = self.inner.read_interrupt_status();
+ while self.inner.read_interrupt_status().rx_not_empty() {
self.inner.read_fifo_unchecked();
- });
- self.inner.set_rx_fifo_trigger(1).unwrap();
- self.outstanding_rx = false;
+ }
+ while status.tx_full() {
+ while self.inner.read_interrupt_status().rx_not_empty() {
+ self.inner.read_fifo_unchecked();
+ }
+ }
}
}
@@ -1145,7 +1158,7 @@ pub fn reset(id: SpiId) {
regs.reset_ctrl().write_spi(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..5 {
+ for _ in 0..10 {
aarch32_cpu::asm::nop();
}
regs.reset_ctrl()
@@ -1159,22 +1172,32 @@ pub fn reset(id: SpiId) {
/// The Zynq7000 SPI peripheral has the following requirement for the SPI reference clock:
/// It must be larger than the CPU 1X clock. Therefore, the divisor used to calculate the reference
/// clock has a maximum value, which can be calculated with this function.
+/// [configure_spi_ref_clk_with_divisor] can be used to configure the SPI reference clock with a
+/// divisor.
///
-/// [configure_spi_ref_clk] can be used to configure the SPI reference clock with the calculated
-/// value.
+/// *NOTE* - It is recommended to avoid the largest theoretical value which was proven to be
+/// problematic for driving certain sensors and instead take a smaller value! Reduce the divisor
+/// calculated by this function subtracting a small value to get a functioning SPI clock.
pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option {
let slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
let div = match spi_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
- clks.io_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
- }
- zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
- clks.arm_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
- }
- zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
- clks.ddr_clocks().ref_clk() / clks.arm_clocks().cpu_1x_clk()
+ clks.io_clocks()
+ .ref_clk()
+ .raw()
+ .div_ceil(clks.arm_clocks().cpu_1x_clk().raw())
}
+ zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks
+ .arm_clocks()
+ .ref_clk()
+ .raw()
+ .div_ceil(clks.arm_clocks().cpu_1x_clk().raw()),
+ zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks
+ .ddr_clocks()
+ .ref_clk()
+ .raw()
+ .div_ceil(clks.arm_clocks().cpu_1x_clk().raw()),
};
if div > u6::MAX.value() as u32 {
return None;
@@ -1183,7 +1206,28 @@ pub fn calculate_largest_allowed_spi_ref_clk_divisor(clks: &Clocks) -> Option {
+ clks.io_clocks().ref_clk().raw()
+ }
+ zynq7000::slcr::clocks::SrcSelIo::ArmPll => clks.arm_clocks().ref_clk().raw(),
+ zynq7000::slcr::clocks::SrcSelIo::DdrPll => clks.ddr_clocks().ref_clk().raw(),
+ };
+ let div = ref_clk.div_ceil(target_clock.raw());
+ if div > u6::MAX.value() as u32 {
+ configure_spi_ref_clock_with_divisor(clks, u6::new(div as u8));
+ }
+}
+
+pub fn configure_spi_ref_clock_with_divisor(clks: &mut Clocks, divisor: u6) {
let mut slcr = unsafe { Slcr::steal() };
let spi_clk_ctrl = slcr.regs().clk_ctrl_shared().read_spi_clk_ctrl();
slcr.modify(|regs| {
diff --git a/firmware/zynq7000/src/spi.rs b/firmware/zynq7000/src/spi.rs
index e424952..5c9e410 100644
--- a/firmware/zynq7000/src/spi.rs
+++ b/firmware/zynq7000/src/spi.rs
@@ -6,7 +6,7 @@ pub use crate::{SpiClockPhase, SpiClockPolarity};
pub const SPI_0_BASE_ADDR: usize = 0xE000_6000;
pub const SPI_1_BASE_ADDR: usize = 0xE000_7000;
-/// The SPI reference block will be divided by a divisor value.
+/// The SPI reference clock will be divided by a divisor value.
#[bitbybit::bitenum(u3)]
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
diff --git a/justfile b/justfile
index 09b78b7..3d51b06 100644
--- a/justfile
+++ b/justfile
@@ -1,5 +1,8 @@
-all: check build check-fmt clippy docs-zynq
+# Common paths (single source of truth)
+INIT_SCRIPT := justfile_directory() / "scripts/zynq7000-init.py"
+GDB_CMD := justfile_directory() / "firmware/gdb.gdb"
+all: check build check-fmt clippy docs-zynq
check: (check-dir "firmware") (check-dir "host")
clean: (clean-dir "firmware") (clean-dir "host")
build: build-zynq (build-dir "host")
@@ -52,13 +55,19 @@ bootgen:
bootgen -arch zynq -image boot.bif -o boot.bin -w on
echo "Generated boot.bin at zynq-boot-image/staging"
+# Internal helper to start GDB after running init.
+# Pass init flags (if any) via `init_args`.
[no-cd]
-run binary:
- # Run the initialization script. It needs to be run inside the justfile directory.
- python3 {{justfile_directory()}}/scripts/zynq7000-init.py
+run_generic binary init_args="":
+ python3 {{INIT_SCRIPT}} {{init_args}}
+ gdb-multiarch -q -x {{GDB_CMD}} {{binary}} -tui
- # Run the GDB debugger in GUI mode.
- gdb-multiarch -q -x {{justfile_directory()}}/firmware/gdb.gdb {{binary}} -tui
+# Public targets
+[no-cd]
+run binary: (run_generic binary)
+
+[no-cd]
+run-no-reset binary: (run_generic binary "-s")
flash-nor-zedboard boot_binary:
cd {{justfile_directory()}}/firmware/zedboard-qspi-flasher && cargo build --release
diff --git a/scripts/ps7_init.tcl b/scripts/ps7_init.tcl
index 1520ce6..e87b433 100644
--- a/scripts/ps7_init.tcl
+++ b/scripts/ps7_init.tcl
@@ -33,10 +33,11 @@ proc ps7_clock_init_data_3_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
+ mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
- mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
+ mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_3_0 {} {
@@ -149,7 +150,7 @@ proc ps7_mio_init_data_3_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
- mask_write 0XF8000720 0x00003FFF 0x00000702
+ mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -188,14 +189,14 @@ proc ps7_mio_init_data_3_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
- mask_write 0XF80007BC 0x00003F01 0x00000201
+ mask_write 0XF80007BC 0x00003FFF 0x00000200
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
+ mask_write 0XF80007D0 0x00003FFF 0x00000280
+ mask_write 0XF80007D4 0x00003FFF 0x00000280
+ mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_3_0 {} {
@@ -268,10 +269,11 @@ proc ps7_clock_init_data_2_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
+ mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
- mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
+ mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_2_0 {} {
@@ -385,7 +387,7 @@ proc ps7_mio_init_data_2_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
- mask_write 0XF8000720 0x00003FFF 0x00000702
+ mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -424,14 +426,14 @@ proc ps7_mio_init_data_2_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
- mask_write 0XF80007BC 0x00003F01 0x00000201
+ mask_write 0XF80007BC 0x00003FFF 0x00000200
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
+ mask_write 0XF80007D0 0x00003FFF 0x00000280
+ mask_write 0XF80007D4 0x00003FFF 0x00000280
+ mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_2_0 {} {
@@ -504,10 +506,11 @@ proc ps7_clock_init_data_1_0 {} {
mask_write 0XF800014C 0x00003F31 0x00000501
mask_write 0XF8000150 0x00003F33 0x00001401
mask_write 0XF8000154 0x00003F33 0x00000A03
+ mask_write 0XF8000158 0x00003F33 0x00000601
mask_write 0XF8000168 0x00003F31 0x00000501
mask_write 0XF8000170 0x03F03F30 0x00200500
mask_write 0XF80001C4 0x00000001 0x00000001
- mask_write 0XF800012C 0x01FFCCCD 0x01FC044D
+ mask_write 0XF800012C 0x01FFCCCD 0x01FC444D
mwr -force 0XF8000004 0x0000767B
}
proc ps7_ddr_init_data_1_0 {} {
@@ -619,7 +622,7 @@ proc ps7_mio_init_data_1_0 {} {
mask_write 0XF8000714 0x00003FFF 0x00000702
mask_write 0XF8000718 0x00003FFF 0x00000702
mask_write 0XF800071C 0x00003FFF 0x00000600
- mask_write 0XF8000720 0x00003FFF 0x00000702
+ mask_write 0XF8000720 0x00003FFF 0x00000700
mask_write 0XF8000724 0x00003FFF 0x00001600
mask_write 0XF8000728 0x00003FFF 0x00001600
mask_write 0XF800072C 0x00003FFF 0x00001600
@@ -658,14 +661,14 @@ proc ps7_mio_init_data_1_0 {} {
mask_write 0XF80007B0 0x00003FFF 0x00000380
mask_write 0XF80007B4 0x00003FFF 0x00000380
mask_write 0XF80007B8 0x00003FFF 0x00001200
- mask_write 0XF80007BC 0x00003F01 0x00000201
+ mask_write 0XF80007BC 0x00003FFF 0x00000200
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
+ mask_write 0XF80007D0 0x00003FFF 0x00000280
+ mask_write 0XF80007D4 0x00003FFF 0x00000280
+ mask_write 0XF8000830 0x003F003F 0x00380037
mwr -force 0XF8000004 0x0000767B
}
proc ps7_peripherals_init_data_1_0 {} {
diff --git a/scripts/xsct-flasher.tcl b/scripts/xsct-flasher.tcl
index 1aad46a..3fea446 100644
--- a/scripts/xsct-flasher.tcl
+++ b/scripts/xsct-flasher.tcl
@@ -13,13 +13,15 @@ set bitstream ""
proc usage {} {
puts "Usage: xsct xsct-helper.tcl \[-a|--app app.elf\] \[-b|--bit design.bit]"
puts "Options:"
- puts " -a, --app Path to application ELF to download"
- puts " -b, --bit Path to FPGA bitstream (.bit) to program"
- puts " -h, --help Show this help"
+ puts " -a, --app Path to application ELF to download"
+ puts " -b, --bit Path to FPGA bitstream (.bit) to program"
+ puts " -s, --skip-reset Skip reset"
+ puts " -h, --help Show this help"
}
# Compact, robust parser
set expecting ""
+set skip_reset 0
set endopts 0
foreach arg $argv {
# If previous option expects a value, take this arg
@@ -34,6 +36,7 @@ foreach arg $argv {
if {$arg eq "--"} { set endopts 1; continue }
if {$arg eq "-h" || $arg eq "--help"} { usage; exit 0 }
if {$arg eq "-a" || $arg eq "--app"} { set expecting app; continue }
+ if {$arg eq "-s" || $arg eq "--skip-reset"} { set skip_reset 1; continue }
if {$arg eq "-b" || $arg eq "--bit"} { set expecting bitstream; continue }
puts "error: unknown option: $arg"; usage; exit 1
}
@@ -100,10 +103,11 @@ 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
+if {!$skip_reset} {
+ puts "Reset target"
+ # Reset the target
+ rst
+}
# Check if bitstream is set and the file exists before programming FPGA
if {$bitstream eq ""} {
diff --git a/scripts/zynq7000-init.py b/scripts/zynq7000-init.py
index 0c12ec0..0130adf 100755
--- a/scripts/zynq7000-init.py
+++ b/scripts/zynq7000-init.py
@@ -40,6 +40,12 @@ def main():
action="store_true",
help="No bitstream flashing for initialization with SDT.",
)
+ parser.add_argument(
+ "-s",
+ "--skip-reset",
+ action="store_true",
+ help="Skip device reset",
+ )
parser.add_argument("-a", "--app", dest="app", help="Path to the app to program")
default_ip = os.getenv("HW_SERVER_IP")
if not default_ip:
@@ -141,6 +147,8 @@ def main():
if args.app:
cmd_list.append("--app")
cmd_list.append(args.app)
+ if args.skip_reset:
+ cmd_list.append("-s")
# Join safely for shell execution
xsct_cmd = shlex.join(cmd_list)
diff --git a/zedboard-fpga-design/README.md b/zedboard-fpga-design/README.md
index 44ec137..a73069d 100644
--- a/zedboard-fpga-design/README.md
+++ b/zedboard-fpga-design/README.md
@@ -39,6 +39,12 @@ vivado zedboard-rust.xpr
You can perform all the steps specified in the Vivado GUI as well using `Execute TCL script` and
`Load Project`.
+# Updating the project
+
+If you add custom RTL code, you might have to edit the `zedboard-rust.tcl` project file and
+add the source files there. This file was created using the `write_project_tcl` but was
+optimized, so it might be easier to manually update the file.
+
# Generating the SDT folder from a hardware description
You can generate a hardware description by building the block design by using `Generate Bitstream`
diff --git a/zedboard-fpga-design/src/zedboard-bd.tcl b/zedboard-fpga-design/src/zedboard-bd.tcl
index 8b1c3d4..307d7f0 100644
--- a/zedboard-fpga-design/src/zedboard-bd.tcl
+++ b/zedboard-fpga-design/src/zedboard-bd.tcl
@@ -246,6 +246,10 @@ proc create_root_design { parentCell } {
set TTC0_WAVEOUT [ create_bd_port -dir O TTC0_WAVEOUT ]
set OLED_SCLK [ create_bd_port -dir O OLED_SCLK ]
set OLED_SDIN [ create_bd_port -dir O OLED_SDIN ]
+ set OLED_DC [ create_bd_port -dir O -from 0 -to 0 OLED_DC ]
+ set OLED_RESET [ create_bd_port -dir O -from 0 -to 0 OLED_RESET ]
+ set OLED_VDD [ create_bd_port -dir O -from 0 -to 0 OLED_VDD ]
+ set OLED_VBAT [ create_bd_port -dir O -from 0 -to 0 OLED_VBAT ]
# 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 ]
@@ -283,25 +287,27 @@ proc create_root_design { parentCell } {
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_GRP_MDIO_IO {MIO 52 .. 53} \
+ CONFIG.PCW_ENET0_PERIPHERAL_CLKSRC {IO PLL} \
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_ENET0 {0} \
CONFIG.PCW_EN_EMIO_GPIO {1} \
- CONFIG.PCW_EN_EMIO_SPI0 {0} \
- CONFIG.PCW_EN_EMIO_SPI1 {1} \
+ CONFIG.PCW_EN_EMIO_SPI0 {1} \
+ CONFIG.PCW_EN_EMIO_SPI1 {0} \
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_EMIO_WP_SDIO0 {0} \
CONFIG.PCW_EN_ENET0 {1} \
CONFIG.PCW_EN_GPIO {1} \
CONFIG.PCW_EN_QSPI {1} \
CONFIG.PCW_EN_SDIO0 {1} \
- CONFIG.PCW_EN_SPI0 {0} \
- CONFIG.PCW_EN_SPI1 {1} \
+ CONFIG.PCW_EN_SPI0 {1} \
+ CONFIG.PCW_EN_SPI1 {0} \
CONFIG.PCW_EN_TTC0 {1} \
CONFIG.PCW_EN_TTC1 {0} \
CONFIG.PCW_EN_UART0 {1} \
@@ -472,9 +478,9 @@ proc create_root_design { parentCell } {
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]}\
+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#GPIO#UART 1#UART 1#GPIO#GPIO#Enet\
+0#Enet 0} \
+ 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#gpio[47]#tx#rx#gpio[50]#gpio[51]#mdc#mdio}\
\
CONFIG.PCW_PRESET_BANK1_VOLTAGE {LVCMOS 1.8V} \
CONFIG.PCW_QSPI_GRP_FBCLK_ENABLE {1} \
@@ -486,19 +492,17 @@ proc create_root_design { parentCell } {
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_CD_ENABLE {0} \
CONFIG.PCW_SD0_GRP_POW_ENABLE {0} \
- CONFIG.PCW_SD0_GRP_WP_ENABLE {1} \
- CONFIG.PCW_SD0_GRP_WP_IO {EMIO} \
+ CONFIG.PCW_SD0_GRP_WP_ENABLE {0} \
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_SPI0_PERIPHERAL_ENABLE {0} \
- CONFIG.PCW_SPI1_PERIPHERAL_ENABLE {1} \
- CONFIG.PCW_SPI1_SPI1_IO {EMIO} \
+ CONFIG.PCW_SPI0_PERIPHERAL_ENABLE {1} \
+ CONFIG.PCW_SPI0_SPI0_IO {EMIO} \
+ CONFIG.PCW_SPI1_PERIPHERAL_ENABLE {0} \
CONFIG.PCW_SPI_PERIPHERAL_FREQMHZ {166.666666} \
CONFIG.PCW_SPI_PERIPHERAL_VALID {1} \
CONFIG.PCW_TTC0_PERIPHERAL_ENABLE {1} \
@@ -654,6 +658,50 @@ proc create_root_design { parentCell } {
# Create instance: IRQ_F2P, and set properties
set IRQ_F2P [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconcat:1.0 IRQ_F2P ]
+ # Create instance: OLED_DC, and set properties
+ set OLED_DC [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_DC ]
+ set_property -dict [list \
+ CONFIG.DIN_FROM {11} \
+ CONFIG.DIN_TO {11} \
+ CONFIG.DIN_WIDTH {16} \
+ ] $OLED_DC
+
+
+ # Create instance: OLED_RESET, and set properties
+ set OLED_RESET [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_RESET ]
+ set_property -dict [list \
+ CONFIG.DIN_FROM {12} \
+ CONFIG.DIN_TO {12} \
+ CONFIG.DIN_WIDTH {16} \
+ ] $OLED_RESET
+
+
+ # Create instance: OLED_VDD, and set properties
+ set OLED_VDD [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_VDD ]
+ set_property -dict [list \
+ CONFIG.DIN_FROM {13} \
+ CONFIG.DIN_TO {13} \
+ CONFIG.DIN_WIDTH {16} \
+ ] $OLED_VDD
+
+
+ # Create instance: OLED_VBAT, and set properties
+ set OLED_VBAT [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilslice:1.0 OLED_VBAT ]
+ set_property -dict [list \
+ CONFIG.DIN_FROM {14} \
+ CONFIG.DIN_TO {14} \
+ CONFIG.DIN_WIDTH {16} \
+ ] $OLED_VBAT
+
+
+ # Create instance: ilconstant_2, and set properties
+ set ilconstant_2 [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconstant:1.0 ilconstant_2 ]
+
+ # Create instance: ilconstant_3, and set properties
+ set ilconstant_3 [ create_bd_cell -type inline_hdl -vlnv xilinx.com:inline_hdl:ilconstant:1.0 ilconstant_3 ]
+ set_property CONFIG.CONST_VAL {0} $ilconstant_3
+
+
# 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]
@@ -668,6 +716,12 @@ proc create_root_design { parentCell } {
[get_bd_pins EMIO_I/In3]
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 OLED_RESET_Dout [get_bd_pins OLED_RESET/Dout] \
+ [get_bd_ports OLED_RESET]
+ connect_bd_net -net OLED_VBAT_Dout [get_bd_pins OLED_VBAT/Dout] \
+ [get_bd_ports OLED_VBAT]
+ connect_bd_net -net OLED_VDD_Dout [get_bd_pins OLED_VDD/Dout] \
+ [get_bd_ports OLED_VDD]
connect_bd_net -net SWITCHES_1 [get_bd_ports SWITCHES] \
[get_bd_pins EMIO_I_0/In0]
connect_bd_net -net axi_uart16550_0_ip2intc_irpt [get_bd_pins axi_uart16550_0/ip2intc_irpt] \
@@ -686,10 +740,18 @@ proc create_root_design { parentCell } {
[get_bd_pins processing_system7_0/IRQ_F2P]
connect_bd_net -net ilconstant_0_dout [get_bd_pins ilconstant_0/dout] \
[get_bd_pins EMIO_I_0/In2]
+ connect_bd_net -net ilconstant_2_dout [get_bd_pins ilconstant_2/dout] \
+ [get_bd_pins processing_system7_0/SPI0_MISO_I] \
+ [get_bd_pins processing_system7_0/SPI0_SS_I]
+ connect_bd_net -net ilconstant_3_dout [get_bd_pins ilconstant_3/dout] \
+ [get_bd_pins processing_system7_0/SPI0_MOSI_I] \
+ [get_bd_pins processing_system7_0/SPI0_SCLK_I]
connect_bd_net -net ilslice_0_Dout [get_bd_pins UART_MUX/Dout] \
[get_bd_pins uart_mux_0/sel]
connect_bd_net -net ilslice_0_Dout1 [get_bd_pins LEDS/Dout] \
[get_bd_ports LEDS]
+ connect_bd_net -net ilslice_0_Dout2 [get_bd_pins OLED_DC/Dout] \
+ [get_bd_ports OLED_DC]
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 rst_ps7_0_100M/slowest_sync_clk] \
@@ -702,9 +764,9 @@ proc create_root_design { parentCell } {
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_SPI1_MOSI_O [get_bd_pins processing_system7_0/SPI1_MOSI_O] \
+ connect_bd_net -net processing_system7_0_SPI0_MOSI_O [get_bd_pins processing_system7_0/SPI0_MOSI_O] \
[get_bd_ports OLED_SDIN]
- connect_bd_net -net processing_system7_0_SPI1_SCLK_O [get_bd_pins processing_system7_0/SPI1_SCLK_O] \
+ connect_bd_net -net processing_system7_0_SPI0_SCLK_O [get_bd_pins processing_system7_0/SPI0_SCLK_O] \
[get_bd_ports OLED_SCLK]
connect_bd_net -net processing_system7_0_TTC0_WAVE0_OUT [get_bd_pins processing_system7_0/TTC0_WAVE0_OUT] \
[get_bd_ports TTC0_WAVEOUT]
@@ -732,7 +794,11 @@ proc create_root_design { parentCell } {
connect_bd_net -net xlslice_1_Dout [get_bd_pins EMIO_O_0/Dout] \
[get_bd_pins EMIO_I/In0] \
[get_bd_pins UART_MUX/Din] \
- [get_bd_pins LEDS/Din]
+ [get_bd_pins LEDS/Din] \
+ [get_bd_pins OLED_DC/Din] \
+ [get_bd_pins OLED_RESET/Din] \
+ [get_bd_pins OLED_VDD/Din] \
+ [get_bd_pins OLED_VBAT/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
diff --git a/zedboard-fpga-design/src/zedboard.xdc b/zedboard-fpga-design/src/zedboard.xdc
index 8ea29a3..80954d2 100644
--- a/zedboard-fpga-design/src/zedboard.xdc
+++ b/zedboard-fpga-design/src/zedboard.xdc
@@ -72,11 +72,44 @@ set_property IOSTANDARD LVCMOS33 [get_ports UART_txd]
# OLED SPI
set_property PACKAGE_PIN AB12 [get_ports OLED_SCLK]
-set_property IOSTANDARD LVCMOS33 [get_ports OLED_SCLK]
set_property PACKAGE_PIN AA12 [get_ports OLED_SDIN]
-set_property IOSTANDARD LVCMOS33 [get_ports OLED_SDIN]
-set_property PULLUP TRUE [get_ports OLED_SDIN]
+set_property PACKAGE_PIN U9 [get_ports OLED_RESET]
+set_property PULLUP TRUE [get_ports OLED_RESET]
+set_property PACKAGE_PIN U10 [get_ports OLED_DC]
+set_property PACKAGE_PIN U12 [get_ports OLED_VDD]
+set_property PACKAGE_PIN U11 [get_ports OLED_VBAT]
# TTC0 Wave Out
set_property PACKAGE_PIN W12 [get_ports {TTC0_WAVEOUT}]
set_property IOSTANDARD LVCMOS33 [get_ports {TTC0_WAVEOUT}]
+
+# ----------------------------------------------------------------------------
+# IOSTANDARD Constraints
+#
+# Note that these IOSTANDARD constraints are applied to all IOs currently
+# assigned within an I/O bank. If these IOSTANDARD constraints are
+# evaluated prior to other PACKAGE_PIN constraints being applied, then
+# the IOSTANDARD specified will likely not be applied properly to those
+# pins. Therefore, bank wide IOSTANDARD constraints should be placed
+# within the XDC file in a location that is evaluated AFTER all
+# PACKAGE_PIN constraints within the target bank have been evaluated.
+#
+# Un-comment one or more of the following IOSTANDARD constraints according to
+# the bank pin assignments that are required within a design.
+# ----------------------------------------------------------------------------
+
+# Note that the bank voltage for IO Bank 33 is fixed to 3.3V on ZedBoard.
+set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 33]];
+
+# Set the bank voltage for IO Bank 34 to 1.8V by default.
+# set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 34]];
+# set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 34]];
+set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 34]];
+
+# Set the bank voltage for IO Bank 35 to 1.8V by default.
+# set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 35]];
+# set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 35]];
+set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 35]];
+
+# Note that the bank voltage for IO Bank 13 is fixed to 3.3V on ZedBoard.
+set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 13]];