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. +

+ Ferris on the Zedboard +

+ # 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]];