From 6461b1dc1bb8de44d53a55401822634330834973 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 01/12] timing periph --- gateware/src/tiliqua/dma_framebuffer.py | 145 ++++++++++++++++++++++++ gateware/src/tiliqua/tiliqua_soc.py | 31 ----- 2 files changed, 145 insertions(+), 31 deletions(-) diff --git a/gateware/src/tiliqua/dma_framebuffer.py b/gateware/src/tiliqua/dma_framebuffer.py index 6566bcf4..fa229087 100644 --- a/gateware/src/tiliqua/dma_framebuffer.py +++ b/gateware/src/tiliqua/dma_framebuffer.py @@ -198,3 +198,148 @@ def elaborate(self, platform) -> Module: ] return m + +class Peripheral(wiring.Component): + """ + CSR peripheral for tweaking framebuffer timing/palette parameters from an SoC. + Timing values follow the same format as DVIModeline in dvi_modeline.py. + """ + class HTimingReg(csr.Register, access="w"): + h_active: csr.Field(csr.action.W, unsigned(16)) + h_sync_start: csr.Field(csr.action.W, unsigned(16)) + + class HTimingReg2(csr.Register, access="w"): + h_sync_end: csr.Field(csr.action.W, unsigned(16)) + h_total: csr.Field(csr.action.W, unsigned(16)) + + class VTimingReg(csr.Register, access="w"): + v_active: csr.Field(csr.action.W, unsigned(16)) + v_sync_start: csr.Field(csr.action.W, unsigned(16)) + + class VTimingReg2(csr.Register, access="w"): + v_sync_end: csr.Field(csr.action.W, unsigned(16)) + v_total: csr.Field(csr.action.W, unsigned(16)) + + class HVTimingReg(csr.Register, access="w"): + h_sync_invert: csr.Field(csr.action.W, unsigned(1)) + v_sync_invert: csr.Field(csr.action.W, unsigned(1)) + active_pixels: csr.Field(csr.action.W, unsigned(30)) + + class FlagsReg(csr.Register, access="w"): + enable: csr.Field(csr.action.W, unsigned(1)) + + class PaletteReg(csr.Register, access="w"): + position: csr.Field(csr.action.W, unsigned(8)) + red: csr.Field(csr.action.W, unsigned(8)) + green: csr.Field(csr.action.W, unsigned(8)) + blue: csr.Field(csr.action.W, unsigned(8)) + + class PaletteBusyReg(csr.Register, access="r"): + busy: csr.Field(csr.action.R, unsigned(1)) + + def __init__(self, fb, default_modeline: DVIModeline, **kwargs): + self.fb = framebuffer + self.default_modeline = default_modeline + + regs = csr.Builder(addr_width=5, data_width=32) + + self._h_timing = regs.add("h_timing", self.HTimingReg(), offset=0x00) + self._h_timing2 = regs.add("h_timing2", self.HTimingReg2(), offset=0x04) + self._v_timing = regs.add("v_timing", self.VTimingReg(), offset=0x08) + self._v_timing2 = regs.add("v_timing2", self.VTimingReg2(), offset=0x0C) + self._hv_timing = regs.add("hv_timing", self.HVTimingReg(), offset=0x10) + self._flags = regs.add("flags", self.SyncFlagsReg(), offset=0x14) + self._palette = regs.add("palette", self.PaletteReg(), offset=0x18) + self._palette_busy = regs.add("palette_busy", self.PaletteBusyReg(), offset=0x1C) + + self._bridge = csr.Bridge(regs.as_memory_map()) + + super().__init__({ + "bus": In(csr.Signature(addr_width=regs.addr_width, data_width=regs.data_width)), + }) + + self.bus.memory_map = self._bridge.bus.memory_map + + def elaborate(self, platform) -> Module: + m = Module() + + m.submodules.bridge = self._bridge + + wiring.connect(m, wiring.flipped(self.bus), self._bridge.bus) + + # Timing registers with default initialization from modeline + h_active = Signal(16, reset=self.default_modeline.h_active) + h_sync_start = Signal(16, reset=self.default_modeline.h_sync_start) + h_sync_end = Signal(16, reset=self.default_modeline.h_sync_end) + h_total = Signal(16, reset=self.default_modeline.h_total) + v_active = Signal(16, reset=self.default_modeline.v_active) + v_sync_start = Signal(16, reset=self.default_modeline.v_sync_start) + v_sync_end = Signal(16, reset=self.default_modeline.v_sync_end) + v_total = Signal(16, reset=self.default_modeline.v_total) + h_sync_invert = Signal(1, reset=self.default_modeline.h_sync_invert) + v_sync_invert = Signal(1, reset=self.default_modeline.v_sync_invert) + active_pixels = Signal(30, reset=self.default_modeline.active_pixels) + enable = Signal(1, reset=0) + + with m.If(self._h_timing.f.h_active.w_stb): + m.d.sync += h_active.eq(self._h_timing.f.h_active.w_data) + with m.If(self._h_timing.f.h_sync_start.w_stb): + m.d.sync += h_sync_start.eq(self._h_timing.f.h_sync_start.w_data) + with m.If(self._h_timing2.f.h_sync_end.w_stb): + m.d.sync += h_sync_end.eq(self._h_timing2.f.h_sync_end.w_data) + with m.If(self._h_timing2.f.h_total.w_stb): + m.d.sync += h_total.eq(self._h_timing2.f.h_total.w_data) + with m.If(self._v_timing.f.v_active.w_stb): + m.d.sync += v_active.eq(self._v_timing.f.v_active.w_data) + with m.If(self._v_timing.f.v_sync_start.w_stb): + m.d.sync += v_sync_start.eq(self._v_timing.f.v_sync_start.w_data) + with m.If(self._v_timing2.f.v_sync_end.w_stb): + m.d.sync += v_sync_end.eq(self._v_timing2.f.v_sync_end.w_data) + with m.If(self._v_timing2.f.v_total.w_stb): + m.d.sync += v_total.eq(self._v_timing2.f.v_total.w_data) + with m.If(self._hv_timing.f.h_sync_invert.w_stb): + m.d.sync += h_sync_invert.eq(self._hv_timing.f.h_sync_invert.w_data) + with m.If(self._hv_timing.f.v_sync_invert.w_stb): + m.d.sync += v_sync_invert.eq(self._hv_timing.f.v_sync_invert.w_data) + with m.If(self._hv_timing.f.active_pixels.w_stb): + m.d.sync += active_pixels.eq(self._hv_timing.f.active_pixels.w_data) + with m.If(self._flags.f.enable.w_stb): + m.d.sync += enable.eq(self._flags.f.enable.w_data) + + m.d.comb += [ + self.fb.timings.h_active.eq(h_active), + self.fb.timings.h_sync_start.eq(h_sync_start), + self.fb.timings.h_sync_end.eq(h_sync_end), + self.fb.timings.h_total.eq(h_total), + self.fb.timings.h_sync_invert.eq(h_sync_invert), + self.fb.timings.v_active.eq(v_active), + self.fb.timings.v_sync_start.eq(v_sync_start), + self.fb.timings.v_sync_end.eq(v_sync_end), + self.fb.timings.v_total.eq(v_total), + self.fb.timings.v_sync_invert.eq(v_sync_invert), + self.fb.timings.active_pixels.eq(active_pixels), + self.fb.enable.eq(enable), + ] + + # palette update logic + palette_busy = Signal() + m.d.comb += self._palette_busy.f.busy.r_data.eq(palette_busy) + + with m.If(self._palette.element.w_stb & ~palette_busy): + m.d.sync += [ + palette_busy .eq(1), + self.fb.palette.update.valid .eq(1), + self.fb.palette.update.payload.position .eq(self._palette.f.position.w_data), + self.fb.palette.update.payload.red .eq(self._palette.f.red.w_data), + self.fb.palette.update.payload.green .eq(self._palette.f.green.w_data), + self.fb.palette.update.payload.blue .eq(self._palette.f.blue.w_data), + ] + + with m.If(palette_busy & self.fb.palette.update.ready): + # coefficient has been written + m.d.sync += [ + palette_busy.eq(0), + self.fb.palette.update.valid.eq(0), + ] + + return m diff --git a/gateware/src/tiliqua/tiliqua_soc.py b/gateware/src/tiliqua/tiliqua_soc.py index c3b5fe69..51105c92 100644 --- a/gateware/src/tiliqua/tiliqua_soc.py +++ b/gateware/src/tiliqua/tiliqua_soc.py @@ -65,15 +65,6 @@ class PersistReg(csr.Register, access="w"): class DecayReg(csr.Register, access="w"): decay: csr.Field(csr.action.W, unsigned(8)) - class PaletteReg(csr.Register, access="w"): - position: csr.Field(csr.action.W, unsigned(8)) - red: csr.Field(csr.action.W, unsigned(8)) - green: csr.Field(csr.action.W, unsigned(8)) - blue: csr.Field(csr.action.W, unsigned(8)) - - class PaletteBusyReg(csr.Register, access="r"): - busy: csr.Field(csr.action.R, unsigned(1)) - def __init__(self, fb, bus_dma): self.en = Signal() self.fb = fb @@ -84,8 +75,6 @@ def __init__(self, fb, bus_dma): self._persist = regs.add("persist", self.PersistReg(), offset=0x0) self._decay = regs.add("decay", self.DecayReg(), offset=0x4) - self._palette = regs.add("palette", self.PaletteReg(), offset=0x8) - self._palette_busy = regs.add("palette_busy", self.PaletteBusyReg(), offset=0xC) self._bridge = csr.Bridge(regs.as_memory_map()) @@ -109,26 +98,6 @@ def elaborate(self, platform): with m.If(self._decay.f.decay.w_stb): m.d.sync += self.persist.decay.eq(self._decay.f.decay.w_data) - # palette update logic - palette_busy = Signal() - m.d.comb += self._palette_busy.f.busy.r_data.eq(palette_busy) - - with m.If(self._palette.element.w_stb & ~palette_busy): - m.d.sync += [ - palette_busy .eq(1), - self.fb.palette.update.valid .eq(1), - self.fb.palette.update.payload.position .eq(self._palette.f.position.w_data), - self.fb.palette.update.payload.red .eq(self._palette.f.red.w_data), - self.fb.palette.update.payload.green .eq(self._palette.f.green.w_data), - self.fb.palette.update.payload.blue .eq(self._palette.f.blue.w_data), - ] - - with m.If(palette_busy & self.fb.palette.update.ready): - # coefficient has been written - m.d.sync += [ - palette_busy.eq(0), - self.fb.palette.update.valid.eq(0), - ] return m From c7c7b6a00a336be53eeb2a43fca8a00820451bfb Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 02/12] compiles --- gateware/src/rs/hal/src/dma_display.rs | 62 +++++++++------ gateware/src/rs/hal/src/video.rs | 12 --- gateware/src/rs/lib/src/palette.rs | 4 +- gateware/src/tiliqua/dma_framebuffer.py | 87 ++++++++-------------- gateware/src/tiliqua/dvi.py | 1 + gateware/src/tiliqua/tiliqua_soc.py | 7 +- gateware/src/top/bootloader/fw/src/main.rs | 13 +++- 7 files changed, 89 insertions(+), 97 deletions(-) diff --git a/gateware/src/rs/hal/src/dma_display.rs b/gateware/src/rs/hal/src/dma_display.rs index f27eb72e..b5dd8168 100644 --- a/gateware/src/rs/hal/src/dma_display.rs +++ b/gateware/src/rs/hal/src/dma_display.rs @@ -1,42 +1,62 @@ +pub trait DMAFramebuffer { + fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8); +} + #[macro_export] -macro_rules! impl_dma_display { +macro_rules! impl_dma_framebuffer { ($( - $DMA_DISPLAYX:ident, $H_ACTIVE:expr, $V_ACTIVE:expr, $VIDEO_ROTATE_90: expr + $DMA_FRAMEBUFFERX:ident: $PACFRAMEBUFFERX:ty, )+) => { $( - struct $DMA_DISPLAYX { + struct $DMA_FRAMEBUFFERX { + registers: $PACFRAMEBUFFERX, framebuffer_base: *mut u32, + h_active: u32, + v_active: u32, + rotate_90: bool, + } + + impl hal::dma_display::DMAFramebuffer for $DMA_FRAMEBUFFERX { + fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8) { + /* wait until last coefficient written */ + while self.registers.palette_busy().read().bits() == 1 { } + self.registers.palette().write(|w| unsafe { + w.position().bits(((intensity&0xF) << 4) | (hue&0xF)); + w.red() .bits(r); + w.green() .bits(g); + w.blue() .bits(b) + } ); + } } - impl OriginDimensions for $DMA_DISPLAYX { + impl OriginDimensions for $DMA_FRAMEBUFFERX { fn size(&self) -> Size { - Size::new($H_ACTIVE, $V_ACTIVE) + Size::new(self.h_active, self.v_active) } } - impl DrawTarget for $DMA_DISPLAYX { + impl DrawTarget for $DMA_FRAMEBUFFERX { type Color = Gray8; type Error = core::convert::Infallible; fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, { - let xs: u32 = $H_ACTIVE; - let ys: u32 = $V_ACTIVE; for Pixel(coord, color) in pixels.into_iter() { - if let Ok((x @ 0..=$H_ACTIVE, - y @ 0..=$V_ACTIVE)) = coord.try_into() { - let xf = if $VIDEO_ROTATE_90 {$V_ACTIVE - y} else {x}; - let yf = if $VIDEO_ROTATE_90 {x} else {y}; - // Calculate the index in the framebuffer. - let index: u32 = (xf + yf * $H_ACTIVE) / 4; - unsafe { - // TODO: support anything other than Gray8 - let mut px = self.framebuffer_base.offset( - index as isize).read_volatile(); - px &= !(0xFFu32 << (8*(xf%4))); - self.framebuffer_base.offset(index as isize).write_volatile( - px | ((color.luma() as u32) << (8*(xf%4)))); + if let Ok((x, y)) = coord.try_into() { + if x >= 0 && x < self.h_active && y >= 0 && y < self.v_active { + let xf: u32 = if self.rotate_90 {self.v_active - y} else {x}; + let yf: u32 = if self.rotate_90 {x} else {y}; + // Calculate the index in the framebuffer. + let index: u32 = (xf + yf * self.h_active) / 4; + unsafe { + // TODO: support anything other than Gray8 + let mut px = self.framebuffer_base.offset( + index as isize).read_volatile(); + px &= !(0xFFu32 << (8*(xf%4))); + self.framebuffer_base.offset(index as isize).write_volatile( + px | ((color.luma() as u32) << (8*(xf%4)))); + } } } } diff --git a/gateware/src/rs/hal/src/video.rs b/gateware/src/rs/hal/src/video.rs index 8063cdee..c31f23e3 100644 --- a/gateware/src/rs/hal/src/video.rs +++ b/gateware/src/rs/hal/src/video.rs @@ -1,5 +1,4 @@ pub trait Video { - fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8); fn set_persist(&mut self, value: u16); fn set_decay(&mut self, value: u8); } @@ -22,17 +21,6 @@ macro_rules! impl_video { } impl hal::video::Video for $VIDEOX { - fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8) { - /* wait until last coefficient written */ - while self.registers.palette_busy().read().bits() == 1 { } - self.registers.palette().write(|w| unsafe { - w.position().bits(((intensity&0xF) << 4) | (hue&0xF)); - w.red() .bits(r); - w.green() .bits(g); - w.blue() .bits(b) - } ); - } - fn set_persist(&mut self, value: u16) { self.registers.persist().write(|w| unsafe { w.persist().bits(value) } ); } diff --git a/gateware/src/rs/lib/src/palette.rs b/gateware/src/rs/lib/src/palette.rs index 9daffd39..ffc8a83d 100644 --- a/gateware/src/rs/lib/src/palette.rs +++ b/gateware/src/rs/lib/src/palette.rs @@ -1,4 +1,4 @@ -use tiliqua_hal::video::Video; +use tiliqua_hal::dma_display::DMAFramebuffer; use strum_macros::{EnumIter, IntoStaticStr}; @@ -100,7 +100,7 @@ impl ColorPalette { } } - pub fn write_to_hardware(&self, video: &mut impl Video) { + pub fn write_to_hardware(&self, video: &mut impl DMAFramebuffer) { for i in 0..PX_INTENSITY_MAX { for h in 0..PX_HUE_MAX { let rgb = self.compute_color(i, h); diff --git a/gateware/src/tiliqua/dma_framebuffer.py b/gateware/src/tiliqua/dma_framebuffer.py index fa229087..61842f72 100644 --- a/gateware/src/tiliqua/dma_framebuffer.py +++ b/gateware/src/tiliqua/dma_framebuffer.py @@ -16,7 +16,7 @@ from amaranth.utils import exact_log2 from amaranth.lib.memory import Memory -from amaranth_soc import wishbone +from amaranth_soc import wishbone, csr from tiliqua import sim, dvi, palette from tiliqua.dvi_modeline import DVIModeline @@ -75,9 +75,11 @@ def __init__(self, *, fb_base_default=0, addr_width=22, def elaborate(self, platform) -> Module: m = Module() + """ if self.fixed_modeline is not None: for member in self.timings.signature.members: m.d.comb += getattr(self.timings, member).eq(getattr(self.fixed_modeline, member)) + """ m.submodules.palette = self.palette m.submodules.fifo = fifo = AsyncFIFOBuffered( @@ -237,20 +239,23 @@ class PaletteReg(csr.Register, access="w"): class PaletteBusyReg(csr.Register, access="r"): busy: csr.Field(csr.action.R, unsigned(1)) - def __init__(self, fb, default_modeline: DVIModeline, **kwargs): - self.fb = framebuffer - self.default_modeline = default_modeline + class FBBaseReg(csr.Register, access="w"): + fb_base: csr.Field(csr.action.W, unsigned(32)) + + def __init__(self, fb): + self.fb = fb - regs = csr.Builder(addr_width=5, data_width=32) + regs = csr.Builder(addr_width=6, data_width=8) self._h_timing = regs.add("h_timing", self.HTimingReg(), offset=0x00) self._h_timing2 = regs.add("h_timing2", self.HTimingReg2(), offset=0x04) self._v_timing = regs.add("v_timing", self.VTimingReg(), offset=0x08) self._v_timing2 = regs.add("v_timing2", self.VTimingReg2(), offset=0x0C) self._hv_timing = regs.add("hv_timing", self.HVTimingReg(), offset=0x10) - self._flags = regs.add("flags", self.SyncFlagsReg(), offset=0x14) + self._flags = regs.add("flags", self.FlagsReg(), offset=0x14) self._palette = regs.add("palette", self.PaletteReg(), offset=0x18) self._palette_busy = regs.add("palette_busy", self.PaletteBusyReg(), offset=0x1C) + self._fb_base = regs.add("fb_base", self.FBBaseReg(), offset=0x20) self._bridge = csr.Bridge(regs.as_memory_map()) @@ -267,59 +272,27 @@ def elaborate(self, platform) -> Module: wiring.connect(m, wiring.flipped(self.bus), self._bridge.bus) - # Timing registers with default initialization from modeline - h_active = Signal(16, reset=self.default_modeline.h_active) - h_sync_start = Signal(16, reset=self.default_modeline.h_sync_start) - h_sync_end = Signal(16, reset=self.default_modeline.h_sync_end) - h_total = Signal(16, reset=self.default_modeline.h_total) - v_active = Signal(16, reset=self.default_modeline.v_active) - v_sync_start = Signal(16, reset=self.default_modeline.v_sync_start) - v_sync_end = Signal(16, reset=self.default_modeline.v_sync_end) - v_total = Signal(16, reset=self.default_modeline.v_total) - h_sync_invert = Signal(1, reset=self.default_modeline.h_sync_invert) - v_sync_invert = Signal(1, reset=self.default_modeline.v_sync_invert) - active_pixels = Signal(30, reset=self.default_modeline.active_pixels) - enable = Signal(1, reset=0) - - with m.If(self._h_timing.f.h_active.w_stb): - m.d.sync += h_active.eq(self._h_timing.f.h_active.w_data) - with m.If(self._h_timing.f.h_sync_start.w_stb): - m.d.sync += h_sync_start.eq(self._h_timing.f.h_sync_start.w_data) - with m.If(self._h_timing2.f.h_sync_end.w_stb): - m.d.sync += h_sync_end.eq(self._h_timing2.f.h_sync_end.w_data) - with m.If(self._h_timing2.f.h_total.w_stb): - m.d.sync += h_total.eq(self._h_timing2.f.h_total.w_data) - with m.If(self._v_timing.f.v_active.w_stb): - m.d.sync += v_active.eq(self._v_timing.f.v_active.w_data) - with m.If(self._v_timing.f.v_sync_start.w_stb): - m.d.sync += v_sync_start.eq(self._v_timing.f.v_sync_start.w_data) - with m.If(self._v_timing2.f.v_sync_end.w_stb): - m.d.sync += v_sync_end.eq(self._v_timing2.f.v_sync_end.w_data) - with m.If(self._v_timing2.f.v_total.w_stb): - m.d.sync += v_total.eq(self._v_timing2.f.v_total.w_data) - with m.If(self._hv_timing.f.h_sync_invert.w_stb): - m.d.sync += h_sync_invert.eq(self._hv_timing.f.h_sync_invert.w_data) - with m.If(self._hv_timing.f.v_sync_invert.w_stb): - m.d.sync += v_sync_invert.eq(self._hv_timing.f.v_sync_invert.w_data) + with m.If(self._h_timing.element.w_stb): + m.d.sync += self.fb.timings.h_active.eq(self._h_timing.f.h_active.w_data) + m.d.sync += self.fb.timings.h_sync_start.eq(self._h_timing.f.h_sync_start.w_data) + with m.If(self._h_timing2.element.w_stb): + m.d.sync += self.fb.timings.h_sync_end.eq(self._h_timing2.f.h_sync_end.w_data) + m.d.sync += self.fb.timings.h_total.eq(self._h_timing2.f.h_total.w_data) + with m.If(self._v_timing.element.w_stb): + m.d.sync += self.fb.timings.v_active.eq(self._v_timing.f.v_active.w_data) + m.d.sync += self.fb.timings.v_sync_start.eq(self._v_timing.f.v_sync_start.w_data) + with m.If(self._v_timing2.element.w_stb): + m.d.sync += self.fb.timings.v_sync_end.eq(self._v_timing2.f.v_sync_end.w_data) + m.d.sync += self.fb.timings.v_total.eq(self._v_timing2.f.v_total.w_data) + with m.If(self._hv_timing.element.w_stb): + m.d.sync += self.fb.timings.h_sync_invert.eq(self._hv_timing.f.h_sync_invert.w_data) + m.d.sync += self.fb.timings.v_sync_invert.eq(self._hv_timing.f.v_sync_invert.w_data) with m.If(self._hv_timing.f.active_pixels.w_stb): - m.d.sync += active_pixels.eq(self._hv_timing.f.active_pixels.w_data) + m.d.sync += self.fb.timings.active_pixels.eq(self._hv_timing.f.active_pixels.w_data) with m.If(self._flags.f.enable.w_stb): - m.d.sync += enable.eq(self._flags.f.enable.w_data) - - m.d.comb += [ - self.fb.timings.h_active.eq(h_active), - self.fb.timings.h_sync_start.eq(h_sync_start), - self.fb.timings.h_sync_end.eq(h_sync_end), - self.fb.timings.h_total.eq(h_total), - self.fb.timings.h_sync_invert.eq(h_sync_invert), - self.fb.timings.v_active.eq(v_active), - self.fb.timings.v_sync_start.eq(v_sync_start), - self.fb.timings.v_sync_end.eq(v_sync_end), - self.fb.timings.v_total.eq(v_total), - self.fb.timings.v_sync_invert.eq(v_sync_invert), - self.fb.timings.active_pixels.eq(active_pixels), - self.fb.enable.eq(enable), - ] + m.d.sync += self.fb.enable.eq(self._flags.f.enable.w_data) + with m.If(self._fb_base.f.fb_base.w_stb): + m.d.sync += self.fb.fb_base.eq(self._fb_base.f.fb_base.w_data) # palette update logic palette_busy = Signal() diff --git a/gateware/src/tiliqua/dvi.py b/gateware/src/tiliqua/dvi.py index b8f909bd..7d11fe02 100644 --- a/gateware/src/tiliqua/dvi.py +++ b/gateware/src/tiliqua/dvi.py @@ -9,6 +9,7 @@ from amaranth.lib.wiring import In, Out from tiliqua import sim, tmds +from tiliqua.dvi_modeline import DVIModeline class DVITimingGen(wiring.Component): diff --git a/gateware/src/tiliqua/tiliqua_soc.py b/gateware/src/tiliqua/tiliqua_soc.py index 51105c92..5510cf04 100644 --- a/gateware/src/tiliqua/tiliqua_soc.py +++ b/gateware/src/tiliqua/tiliqua_soc.py @@ -139,6 +139,7 @@ def __init__(self, *, firmware_bin_path, default_modeline, ui_name, ui_sha, plat self.pmod0_periph_base = 0x00000700 self.dtr0_base = 0x00000800 self.video_periph_base = 0x00000900 + self.framebuffer_periph_base = 0x00000A00 # Some settings depend on whether code is in block RAM or SPI flash self.fw_location = fw_location @@ -250,6 +251,10 @@ def __init__(self, *, firmware_bin_path, default_modeline, ui_name, ui_sha, plat bus_dma=self.psram_periph) self.csr_decoder.add(self.video_periph.bus, addr=self.video_periph_base, name="video_periph") + self.framebuffer_periph = dma_framebuffer.Peripheral(fb=self.fb) + self.csr_decoder.add( + self.framebuffer_periph.bus, addr=self.framebuffer_periph_base, name="framebuffer_periph") + self.permit_bus_traffic = Signal() self.extra_rust_constants = [] @@ -338,6 +343,7 @@ def elaborate(self, platform): # video PHY m.submodules.fb = self.fb + m.submodules.framebuffer_periph = self.framebuffer_periph # video periph / persist m.submodules.video_periph = self.video_periph @@ -381,7 +387,6 @@ def elaborate(self, platform): m.d.sync += on_delay.eq(on_delay+1) with m.Else(): m.d.sync += self.permit_bus_traffic.eq(1) - m.d.sync += self.fb.enable.eq(1) m.d.sync += self.video_periph.en.eq(1) return m diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 8437b7a2..3fc58068 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -35,8 +35,9 @@ use embedded_graphics::{ use tiliqua_fw::options::*; use hal::pca9635::Pca9635Driver; -hal::impl_dma_display!(DMADisplay, H_ACTIVE, V_ACTIVE, - VIDEO_ROTATE_90); +tiliqua_hal::impl_dma_framebuffer! { + DMAFramebuffer0: tiliqua_pac::FRAMEBUFFER_PERIPH, +} pub const TIMER0_ISR_PERIOD_MS: u32 = 10; @@ -511,8 +512,12 @@ fn main() -> ! { let mut logo_coord_ix = 0u32; let mut rng = fastrand::Rng::with_seed(0); - let mut display = DMADisplay { + let mut display = DMAFramebuffer0 { + registers: peripherals.FRAMEBUFFER_PERIPH, framebuffer_base: PSRAM_FB_BASE as *mut u32, + h_active: H_ACTIVE, + v_active: V_ACTIVE, + rotate_90: VIDEO_ROTATE_90, }; video.set_persist(1024); @@ -521,7 +526,7 @@ fn main() -> ! { .stroke_width(1) .build(); - palette::ColorPalette::default().write_to_hardware(&mut video); + palette::ColorPalette::default().write_to_hardware(&mut display); log::info!("{}", startup_report); From b3890dc2c77b0544fc34c31e4a5072c10f0f5b5a Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 03/12] start modeline logic --- gateware/src/rs/hal/src/dma_display.rs | 24 ++++++++++++++++++++++ gateware/src/top/bootloader/fw/src/main.rs | 13 +++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/gateware/src/rs/hal/src/dma_display.rs b/gateware/src/rs/hal/src/dma_display.rs index b5dd8168..551cf410 100644 --- a/gateware/src/rs/hal/src/dma_display.rs +++ b/gateware/src/rs/hal/src/dma_display.rs @@ -1,4 +1,19 @@ +pub struct DVIModeline { + pub h_active: u16, + pub h_sync_start: u16, + pub h_sync_end: u16, + pub h_total: u16, + pub h_sync_invert: bool, + pub v_active: u16, + pub v_sync_start: u16, + pub v_sync_end: u16, + pub v_total: u16, + pub v_sync_invert: bool, + pub pixel_clk_mhz: f32, +} + pub trait DMAFramebuffer { + fn set_modeline(&mut self, mode: DVIModeline); fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8); } @@ -8,6 +23,8 @@ macro_rules! impl_dma_framebuffer { $DMA_FRAMEBUFFERX:ident: $PACFRAMEBUFFERX:ty, )+) => { $( + use tiliqua_hal::dma_display::DVIModeline; + struct $DMA_FRAMEBUFFERX { registers: $PACFRAMEBUFFERX, framebuffer_base: *mut u32, @@ -17,6 +34,13 @@ macro_rules! impl_dma_framebuffer { } impl hal::dma_display::DMAFramebuffer for $DMA_FRAMEBUFFERX { + fn set_modeline(&mut self, mode: DVIModeline) { + self.registers.h_timing().write(|w| unsafe { + w.h_active().bits(mode.h_active); + w.h_sync_start().bits(mode.h_sync_start) + } ); + } + fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8) { /* wait until last coefficient written */ while self.registers.palette_busy().read().bits() == 1 { } diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 3fc58068..110f7a89 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -532,6 +532,9 @@ fn main() -> ! { loop { + let h_active = display.h_active.clone(); + let v_active = display.v_active.clone(); + // Always mute the CODEC to stop pops on flashing while in the bootloader. pmod.mute(true); @@ -541,14 +544,14 @@ fn main() -> ! { app.borrow_ref(cs).error_n.clone()) }); - draw::draw_options(&mut display, &opts, 100, V_ACTIVE/2-50, 0).ok(); - draw::draw_name(&mut display, H_ACTIVE/2, V_ACTIVE-50, 0, UI_NAME, UI_SHA).ok(); + draw::draw_options(&mut display, &opts, 100, v_active/2-50, 0).ok(); + draw::draw_name(&mut display, h_active/2, v_active-50, 0, UI_NAME, UI_SHA).ok(); if let Some(n) = opts.tracker.selected { draw_summary(&mut display, &manifests[n], &error_n[n], &startup_report, -20, -18, 0); if manifests[n].is_some() { - Line::new(Point::new(255, (V_ACTIVE/2 - 55 + (n as u32)*18) as i32), - Point::new((H_ACTIVE/2-90) as i32, (V_ACTIVE/2+8) as i32)) + Line::new(Point::new(255, (v_active/2 - 55 + (n as u32)*18) as i32), + Point::new((h_active/2-90) as i32, (v_active/2+8) as i32)) .into_styled(stroke) .draw(&mut display).ok(); } @@ -556,7 +559,7 @@ fn main() -> ! { for _ in 0..5 { let _ = draw::draw_boot_logo(&mut display, - (H_ACTIVE/2) as i32, + (h_active/2) as i32, 150 as i32, logo_coord_ix); logo_coord_ix += 1; From b469dc04afe2ce1212ae78339e3b2c70e9128eb7 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 04/12] display configure compiles --- .../{dma_display.rs => dma_framebuffer.rs} | 68 ++++++++++++++++--- gateware/src/rs/hal/src/lib.rs | 2 +- gateware/src/tiliqua/dma_framebuffer.py | 3 +- gateware/src/tiliqua/dvi.py | 3 +- gateware/src/tiliqua/tiliqua_soc.py | 8 +-- gateware/src/top/bootloader/fw/src/main.rs | 34 +++++++--- 6 files changed, 90 insertions(+), 28 deletions(-) rename gateware/src/rs/hal/src/{dma_display.rs => dma_framebuffer.rs} (52%) diff --git a/gateware/src/rs/hal/src/dma_display.rs b/gateware/src/rs/hal/src/dma_framebuffer.rs similarity index 52% rename from gateware/src/rs/hal/src/dma_display.rs rename to gateware/src/rs/hal/src/dma_framebuffer.rs index 551cf410..7f49fa1f 100644 --- a/gateware/src/rs/hal/src/dma_display.rs +++ b/gateware/src/rs/hal/src/dma_framebuffer.rs @@ -13,7 +13,7 @@ pub struct DVIModeline { } pub trait DMAFramebuffer { - fn set_modeline(&mut self, mode: DVIModeline); + fn update_fb_base(&mut self, fb_base: u32); fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8); } @@ -23,22 +23,65 @@ macro_rules! impl_dma_framebuffer { $DMA_FRAMEBUFFERX:ident: $PACFRAMEBUFFERX:ty, )+) => { $( - use tiliqua_hal::dma_display::DVIModeline; + use tiliqua_hal::dma_framebuffer::DVIModeline; struct $DMA_FRAMEBUFFERX { registers: $PACFRAMEBUFFERX, + mode: DVIModeline, framebuffer_base: *mut u32, - h_active: u32, - v_active: u32, rotate_90: bool, } - impl hal::dma_display::DMAFramebuffer for $DMA_FRAMEBUFFERX { - fn set_modeline(&mut self, mode: DVIModeline) { - self.registers.h_timing().write(|w| unsafe { + impl $DMA_FRAMEBUFFERX { + fn new(registers: $PACFRAMEBUFFERX, fb_base: usize, + mode: DVIModeline, rotate_90: bool) -> Self { + registers.flags().write(|w| unsafe { + w.enable().bit(false) + }); + registers.fb_base().write(|w| unsafe { + w.fb_base().bits(fb_base as u32) + }); + registers.h_timing().write(|w| unsafe { w.h_active().bits(mode.h_active); w.h_sync_start().bits(mode.h_sync_start) } ); + registers.h_timing2().write(|w| unsafe { + w.h_sync_end().bits(mode.h_sync_end); + w.h_total().bits(mode.h_total) + } ); + registers.v_timing().write(|w| unsafe { + w.v_active().bits(mode.v_active); + w.v_sync_start().bits(mode.v_sync_start) + } ); + registers.v_timing2().write(|w| unsafe { + w.v_sync_end().bits(mode.v_sync_end); + w.v_total().bits(mode.v_total) + } ); + registers.hv_timing().write(|w| unsafe { + w.h_sync_invert().bit(mode.h_sync_invert); + w.v_sync_invert().bit(mode.h_sync_invert); + w.active_pixels().bits( + mode.h_active as u32 * mode.v_active as u32) + } ); + registers.flags().write(|w| unsafe { + w.enable().bit(true) + }); + Self { + registers, + mode, + framebuffer_base: fb_base as *mut u32, + rotate_90 + } + } + + } + + impl hal::dma_framebuffer::DMAFramebuffer for $DMA_FRAMEBUFFERX { + fn update_fb_base(&mut self, fb_base: u32) { + self.registers.fb_base().write(|w| unsafe { + w.fb_base().bits(fb_base) + }); + self.framebuffer_base = fb_base as *mut u32 } fn set_palette_rgb(&mut self, intensity: u8, hue: u8, r: u8, g: u8, b: u8) { @@ -55,7 +98,8 @@ macro_rules! impl_dma_framebuffer { impl OriginDimensions for $DMA_FRAMEBUFFERX { fn size(&self) -> Size { - Size::new(self.h_active, self.v_active) + Size::new(self.mode.h_active as u32, + self.mode.v_active as u32) } } @@ -66,13 +110,15 @@ macro_rules! impl_dma_framebuffer { where I: IntoIterator>, { + let h_active = self.size().width; + let v_active = self.size().height; for Pixel(coord, color) in pixels.into_iter() { if let Ok((x, y)) = coord.try_into() { - if x >= 0 && x < self.h_active && y >= 0 && y < self.v_active { - let xf: u32 = if self.rotate_90 {self.v_active - y} else {x}; + if x >= 0 && x < h_active && y >= 0 && y < v_active { + let xf: u32 = if self.rotate_90 {v_active - y} else {x}; let yf: u32 = if self.rotate_90 {x} else {y}; // Calculate the index in the framebuffer. - let index: u32 = (xf + yf * self.h_active) / 4; + let index: u32 = (xf + yf * h_active) / 4; unsafe { // TODO: support anything other than Gray8 let mut px = self.framebuffer_base.offset( diff --git a/gateware/src/rs/hal/src/lib.rs b/gateware/src/rs/hal/src/lib.rs index 1e67a948..f84f8989 100644 --- a/gateware/src/rs/hal/src/lib.rs +++ b/gateware/src/rs/hal/src/lib.rs @@ -7,7 +7,7 @@ extern crate std; // modules -pub mod dma_display; +pub mod dma_framebuffer; pub mod encoder; pub mod i2c; pub mod pca9635; diff --git a/gateware/src/tiliqua/dma_framebuffer.py b/gateware/src/tiliqua/dma_framebuffer.py index 61842f72..02016f8c 100644 --- a/gateware/src/tiliqua/dma_framebuffer.py +++ b/gateware/src/tiliqua/dma_framebuffer.py @@ -115,6 +115,8 @@ def elaborate(self, platform) -> Module: with m.FSM() as fsm: with m.State('OFF'): with m.If(self.enable): + # TODO FFsync + m.d.dvi += dvi_tgen.enable.eq(1) m.next = 'BURST' with m.State('BURST'): m.d.comb += [ @@ -287,7 +289,6 @@ def elaborate(self, platform) -> Module: with m.If(self._hv_timing.element.w_stb): m.d.sync += self.fb.timings.h_sync_invert.eq(self._hv_timing.f.h_sync_invert.w_data) m.d.sync += self.fb.timings.v_sync_invert.eq(self._hv_timing.f.v_sync_invert.w_data) - with m.If(self._hv_timing.f.active_pixels.w_stb): m.d.sync += self.fb.timings.active_pixels.eq(self._hv_timing.f.active_pixels.w_data) with m.If(self._flags.f.enable.w_stb): m.d.sync += self.fb.enable.eq(self._flags.f.enable.w_data) diff --git a/gateware/src/tiliqua/dvi.py b/gateware/src/tiliqua/dvi.py index 7d11fe02..db41513e 100644 --- a/gateware/src/tiliqua/dvi.py +++ b/gateware/src/tiliqua/dvi.py @@ -51,6 +51,7 @@ def __init__(self): def __init__(self): super().__init__({ + "enable": In(1), "timings": In(self.TimingProperties()), # Control signals without inversion applied. # Useful for driving logic external to this core (e.g. do something @@ -79,7 +80,7 @@ def elaborate(self, platform) -> Module: self.x = Signal(signed(12)) self.y = Signal(signed(12)) - with m.If(ResetSignal("dvi")): + with m.If(ResetSignal("dvi") | ~self.enable): m.d.dvi += [ self.x.eq(self.h_reset), self.y.eq(self.v_reset), diff --git a/gateware/src/tiliqua/tiliqua_soc.py b/gateware/src/tiliqua/tiliqua_soc.py index 5510cf04..9fbf59cc 100644 --- a/gateware/src/tiliqua/tiliqua_soc.py +++ b/gateware/src/tiliqua/tiliqua_soc.py @@ -90,7 +90,7 @@ def elaborate(self, platform): connect(m, flipped(self.bus), self._bridge.bus) - m.d.comb += self.persist.enable.eq(self.en) + m.d.comb += self.persist.enable.eq(self.fb.enable) with m.If(self._persist.f.persist.w_stb): m.d.sync += self.persist.holdoff.eq(self._persist.f.persist.w_data) @@ -382,12 +382,10 @@ def elaborate(self, platform): # Memory controller hangs if we start making requests to it straight away. on_delay = Signal(32) with m.If(on_delay < 0xFF): - m.d.comb += self.cpu.ext_reset.eq(1) - with m.If(on_delay < 0xFFFF): m.d.sync += on_delay.eq(on_delay+1) + m.d.comb += self.cpu.ext_reset.eq(1) with m.Else(): - m.d.sync += self.permit_bus_traffic.eq(1) - m.d.sync += self.video_periph.en.eq(1) + m.d.comb += self.cpu.ext_reset.eq(0) return m diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 110f7a89..327317a6 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -512,13 +512,29 @@ fn main() -> ! { let mut logo_coord_ix = 0u32; let mut rng = fastrand::Rng::with_seed(0); - let mut display = DMAFramebuffer0 { - registers: peripherals.FRAMEBUFFER_PERIPH, - framebuffer_base: PSRAM_FB_BASE as *mut u32, - h_active: H_ACTIVE, - v_active: V_ACTIVE, - rotate_90: VIDEO_ROTATE_90, - }; + + let mut display = DMAFramebuffer0::new( + peripherals.FRAMEBUFFER_PERIPH, + PSRAM_FB_BASE, + DVIModeline { + h_active : 1280, + h_sync_start : 1390, + h_sync_end : 1430, + h_total : 1650, + h_sync_invert : false, + v_active : 720, + v_sync_start : 725, + v_sync_end : 730, + v_total : 750, + v_sync_invert : false, + pixel_clk_mhz : 74.25, + }, + VIDEO_ROTATE_90, + ); + + info!("display configured for {}x{}", + display.size().width, display.size().height); + video.set_persist(1024); let stroke = PrimitiveStyleBuilder::new() @@ -532,8 +548,8 @@ fn main() -> ! { loop { - let h_active = display.h_active.clone(); - let v_active = display.v_active.clone(); + let h_active = display.size().width; + let v_active = display.size().height; // Always mute the CODEC to stop pops on flashing while in the bootloader. pmod.mute(true); From d61b16baa526077530fe6726a25ab05be819b44c Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 05/12] dynamic modeline switch (doesn't work) --- gateware/src/top/bootloader/fw/src/main.rs | 70 +++++++++++++++++----- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 327317a6..6bf724c9 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -420,6 +420,23 @@ where } } +fn edid_test(i2cdev: &mut I2c0) -> u8 { + info!("Read EDID..."); + let mut edid: [u8; 8] = [0; 8]; + const EDID_ADDR: u8 = 0x50; + let mut mfg: u8 = 0; + for i in 0..16 { + let _ = i2cdev.transaction(EDID_ADDR, &mut [Operation::Write(&[(i*8) as u8]), + Operation::Read(&mut edid)]); + info!("{:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x}", + edid[0], edid[1], edid[2], edid[3], edid[4], edid[5], edid[6], edid[7]); + if i == 1 { + mfg = edid[2]; + } + } + mfg +} + #[entry] fn main() -> ! { let peripherals = pac::Peripherals::take().unwrap(); @@ -435,6 +452,43 @@ fn main() -> ! { let mut startup_report: String<256> = Default::default(); + // Determine display modeline + let mut mfg: u8 = 0; + { + let mut i2cdev0 = I2c0::new(unsafe { pac::I2C0::steal() } ); + mfg = edid_test(&mut i2cdev0); + } + + let modeline = if mfg == 0x32 { + DVIModeline { + h_active : 720, + h_sync_start : 760, + h_sync_end : 780, + h_total : 820, + h_sync_invert : false, + v_active : 720, + v_sync_start : 744, + v_sync_end : 748, + v_total : 760, + v_sync_invert : false, + pixel_clk_mhz : 37.40, + } + } else { + DVIModeline { + h_active : 1280, + h_sync_start : 1390, + h_sync_end : 1430, + h_total : 1650, + h_sync_invert : false, + v_active : 720, + v_sync_start : 725, + v_sync_end : 730, + v_total : 750, + v_sync_invert : false, + pixel_clk_mhz : 74.25, + } + }; + // Verify/reprogram touch sensing NVM { @@ -454,7 +508,7 @@ fn main() -> ! { let mut si5351drv = Si5351Device::new_adafruit_module(i2cdev_mobo_pll); configure_external_pll(&ExternalPLLConfig{ clk0_hz: CLOCK_AUDIO_HZ, - clk1_hz: Some(CLOCK_DVI_HZ), + clk1_hz: Some((modeline.pixel_clk_mhz*1e6) as u32), spread_spectrum: Some(0.01), }, &mut si5351drv).unwrap(); Some(si5351drv) @@ -516,19 +570,7 @@ fn main() -> ! { let mut display = DMAFramebuffer0::new( peripherals.FRAMEBUFFER_PERIPH, PSRAM_FB_BASE, - DVIModeline { - h_active : 1280, - h_sync_start : 1390, - h_sync_end : 1430, - h_total : 1650, - h_sync_invert : false, - v_active : 720, - v_sync_start : 725, - v_sync_end : 730, - v_total : 750, - v_sync_invert : false, - pixel_clk_mhz : 74.25, - }, + modeline, VIDEO_ROTATE_90, ); From 5ad7cd0e7338ac7f0eda43e99602fbbcda6656bb Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:24:09 +0100 Subject: [PATCH 06/12] works now with ffsync --- gateware/src/tiliqua/dma_framebuffer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gateware/src/tiliqua/dma_framebuffer.py b/gateware/src/tiliqua/dma_framebuffer.py index 02016f8c..d4e3785d 100644 --- a/gateware/src/tiliqua/dma_framebuffer.py +++ b/gateware/src/tiliqua/dma_framebuffer.py @@ -111,12 +111,16 @@ def elaborate(self, platform) -> Module: fb_size_words = (self.timings.active_pixels * self.bytes_per_pixel) // 4 + + tgen_en = Signal() + m.submodules.en_ff = FFSynchronizer( + i=tgen_en, o=dvi_tgen.enable, o_domain="dvi") # Read to FIFO in sync domain with m.FSM() as fsm: with m.State('OFF'): with m.If(self.enable): # TODO FFsync - m.d.dvi += dvi_tgen.enable.eq(1) + m.d.sync += tgen_en.eq(1) m.next = 'BURST' with m.State('BURST'): m.d.comb += [ From 7a671112a09f7a99aa4a666b32211d6dec35865e Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sat, 15 Mar 2025 12:27:09 +0100 Subject: [PATCH 07/12] bad rebase --- gateware/src/rs/lib/src/palette.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateware/src/rs/lib/src/palette.rs b/gateware/src/rs/lib/src/palette.rs index ffc8a83d..85e27bed 100644 --- a/gateware/src/rs/lib/src/palette.rs +++ b/gateware/src/rs/lib/src/palette.rs @@ -1,4 +1,4 @@ -use tiliqua_hal::dma_display::DMAFramebuffer; +use tiliqua_hal::dma_framebuffer::DMAFramebuffer; use strum_macros::{EnumIter, IntoStaticStr}; From 6517a4aaca869f3fb67af68b12ed2d517c6e4559 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sun, 16 Mar 2025 11:30:21 +0100 Subject: [PATCH 08/12] improve edid detection reliability --- gateware/src/top/bootloader/fw/src/main.rs | 37 +++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 6bf724c9..b1fbead1 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -452,13 +452,26 @@ fn main() -> ! { let mut startup_report: String<256> = Default::default(); - // Determine display modeline - let mut mfg: u8 = 0; + // Verify/reprogram touch sensing NVM + { - let mut i2cdev0 = I2c0::new(unsafe { pac::I2C0::steal() } ); - mfg = edid_test(&mut i2cdev0); + // TODO more sensible bus sharing + let i2cdev1 = I2c1::new(unsafe { pac::I2C1::steal() } ); + let mut cy8 = Cy8cmbr3108Driver::new(i2cdev1); + if let Err(e) = maybe_reprogram_cy8cmbr3xxx(&mut cy8) { + let s: &'static str = e.into(); + write!(startup_report, "{}\r\n", s).ok(); + } } + // Determine display modeline + let mfg: u8 = { + let mut i2cdev0 = I2c0::new(unsafe { pac::I2C0::steal() } ); + use embedded_hal::delay::DelayNs; + timer.delay_ms(10); + edid_test(&mut i2cdev0) + }; + let modeline = if mfg == 0x32 { DVIModeline { h_active : 720, @@ -489,18 +502,6 @@ fn main() -> ! { } }; - // Verify/reprogram touch sensing NVM - - { - // TODO more sensible bus sharing - let i2cdev1 = I2c1::new(unsafe { pac::I2C1::steal() } ); - let mut cy8 = Cy8cmbr3108Driver::new(i2cdev1); - if let Err(e) = maybe_reprogram_cy8cmbr3xxx(&mut cy8) { - let s: &'static str = e.into(); - write!(startup_report, "{}\r\n", s).ok(); - } - } - // Setup external PLL let maybe_external_pll = if HW_REV_MAJOR >= 4 { @@ -574,8 +575,8 @@ fn main() -> ! { VIDEO_ROTATE_90, ); - info!("display configured for {}x{}", - display.size().width, display.size().height); + write!(startup_report, "display: {}x{}\r\n", + display.size().width, display.size().height); video.set_persist(1024); From be7115933766f55d6994b097a48b9f30624311ec Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sun, 16 Mar 2025 11:41:14 +0100 Subject: [PATCH 09/12] top/bootloader: pickup h_active/v_active/rotate_90 in draw calls --- gateware/src/top/bootloader/fw/src/main.rs | 33 +++++++++++++--------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index b1fbead1..81e1ebed 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -30,6 +30,7 @@ use embedded_graphics::{ prelude::*, primitives::{PrimitiveStyleBuilder, Line}, text::{Alignment, Text}, + geometry::OriginDimensions, }; use tiliqua_fw::options::*; @@ -110,12 +111,14 @@ impl App { fn print_rebooting(d: &mut D, rng: &mut fastrand::Rng) where - D: DrawTarget, + D: DrawTarget + OriginDimensions, { let style = MonoTextStyle::new(&FONT_9X15_BOLD, Gray8::WHITE); + let h_active = d.size().width as i32; + let v_active = d.size().height as i32; Text::with_alignment( "REBOOTING", - Point::new(rng.i32(0..H_ACTIVE as i32), rng.i32(0..V_ACTIVE as i32)), + Point::new(rng.i32(0..h_active), rng.i32(0..v_active)), style, Alignment::Center, ) @@ -128,48 +131,50 @@ fn draw_summary(d: &mut D, startup_report: &String<256>, or: i32, ot: i32, hue: u8) where - D: DrawTarget, + D: DrawTarget + OriginDimensions, { + let h_active = d.size().width as i32; + let v_active = d.size().height as i32; let norm = MonoTextStyle::new(&FONT_9X15, Gray8::new(0xB0 + hue)); if let Some(bitstream) = bitstream_manifest { Text::with_alignment( "brief:".into(), - Point::new((H_ACTIVE/2 - 10) as i32 + or, (V_ACTIVE/2+20) as i32 + ot), + Point::new((h_active/2 - 10) as i32 + or, (v_active/2+20) as i32 + ot), norm, Alignment::Right, ) .draw(d).ok(); Text::with_alignment( &bitstream.brief, - Point::new((H_ACTIVE/2) as i32 + or, (V_ACTIVE/2+20) as i32 + ot), + Point::new((h_active/2) as i32 + or, (v_active/2+20) as i32 + ot), norm, Alignment::Left, ) .draw(d).ok(); Text::with_alignment( "video:".into(), - Point::new((H_ACTIVE/2 - 10) as i32 + or, (V_ACTIVE/2+40) as i32 + ot), + Point::new((h_active/2 - 10) as i32 + or, (v_active/2+40) as i32 + ot), norm, Alignment::Right, ) .draw(d).ok(); Text::with_alignment( &bitstream.video, - Point::new((H_ACTIVE/2) as i32 + or, (V_ACTIVE/2+40) as i32 + ot), + Point::new((h_active/2) as i32 + or, (v_active/2+40) as i32 + ot), norm, Alignment::Left, ) .draw(d).ok(); Text::with_alignment( "sha:".into(), - Point::new((H_ACTIVE/2 - 10) as i32 + or, (V_ACTIVE/2+60) as i32 + ot), + Point::new((h_active/2 - 10) as i32 + or, (v_active/2+60) as i32 + ot), norm, Alignment::Right, ) .draw(d).ok(); Text::with_alignment( &bitstream.sha, - Point::new((H_ACTIVE/2) as i32 + or, (V_ACTIVE/2+60) as i32 + ot), + Point::new((h_active/2) as i32 + or, (v_active/2+60) as i32 + ot), norm, Alignment::Left, ) @@ -178,14 +183,14 @@ where if let Some(error_string) = &error { Text::with_alignment( "error:".into(), - Point::new((H_ACTIVE/2 - 10) as i32 + or, (V_ACTIVE/2+80) as i32 + ot), + Point::new((h_active/2 - 10) as i32 + or, (v_active/2+80) as i32 + ot), norm, Alignment::Right, ) .draw(d).ok(); Text::with_alignment( &error_string, - Point::new((H_ACTIVE/2) as i32 + or, (V_ACTIVE/2+80) as i32 + ot), + Point::new((h_active/2) as i32 + or, (v_active/2+80) as i32 + ot), norm, Alignment::Left, ) @@ -193,7 +198,7 @@ where } Text::with_alignment( &startup_report, - Point::new((H_ACTIVE/2) as i32 + or, (V_ACTIVE/2-70) as i32 + ot), + Point::new((h_active/2) as i32 + or, (v_active/2-70) as i32 + ot), norm, Alignment::Center, ) @@ -472,7 +477,9 @@ fn main() -> ! { edid_test(&mut i2cdev0) }; + let mut video_rotate_90 = false; let modeline = if mfg == 0x32 { + video_rotate_90 = true; DVIModeline { h_active : 720, h_sync_start : 760, @@ -572,7 +579,7 @@ fn main() -> ! { peripherals.FRAMEBUFFER_PERIPH, PSRAM_FB_BASE, modeline, - VIDEO_ROTATE_90, + video_rotate_90, ); write!(startup_report, "display: {}x{}\r\n", From 80c53890d90429264c9326c3c84917cfbad44be6 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sun, 16 Mar 2025 12:32:35 +0100 Subject: [PATCH 10/12] rs/lib: add tiny edid library --- gateware/src/rs/lib/src/edid.rs | 294 ++++++++++++++++++++++++++++++++ gateware/src/rs/lib/src/lib.rs | 1 + 2 files changed, 295 insertions(+) create mode 100644 gateware/src/rs/lib/src/edid.rs diff --git a/gateware/src/rs/lib/src/edid.rs b/gateware/src/rs/lib/src/edid.rs new file mode 100644 index 00000000..7cd8dc3f --- /dev/null +++ b/gateware/src/rs/lib/src/edid.rs @@ -0,0 +1,294 @@ +/// Tiny EDID parser, only handles the header and detailed timing descriptor. +/// Does not handle extension blocks. This should be enough for most small embedded monitors. + +/// Main EDID structure representing the first 128 bytes of an EDID block +#[derive(Debug)] +pub struct Edid { + // Header (bytes 0-19) + pub header: EdidHeader, + // Detailed timing descriptors (bytes 54-125) + pub descriptors: [Descriptor; 4], + // Extension flag (byte 126) + pub extensions: u8, + // Checksum (byte 127) + pub checksum: u8, +} + +/// EDID Header information (bytes 0-19) +#[derive(Debug)] +pub struct EdidHeader { + // Fixed header pattern (bytes 0-7) + pub pattern: [u8; 8], + // Manufacturer ID (bytes 8-9) + pub manufacturer_id: [u8; 2], + // Manufacturer product code (bytes 10-11) + pub product_code: u16, + // Serial number (bytes 12-15) + pub serial_number: u32, + // Week of manufacture (byte 16) + pub manufacture_week: u8, + // Year of manufacture (byte 17) + pub manufacture_year: u8, + // EDID version & revision (bytes 18-19) + pub version: u8, + pub revision: u8, +} + +/// Detailed timing descriptors (18 bytes each) +/// For simplicity, we're just storing the raw data for now +#[derive(Debug, Copy, Clone)] +pub struct RawDescriptor { + pub data: [u8; 18], +} + +/// Descriptor types for the 18-byte descriptor blocks +#[derive(Debug, Copy, Clone)] +pub enum Descriptor { + DetailedTiming(DetailedTimingDescriptor), + RawDescriptor([u8; 18]), +} + +/// Detailed timing descriptor (used when pixel clock != 0) +#[derive(Debug, Copy, Clone)] +pub struct DetailedTimingDescriptor { + pub pixel_clock_khz: u32, // in 10 kHz units + pub horizontal_active: u16, + pub horizontal_blanking: u16, + pub vertical_active: u16, + pub vertical_blanking: u16, + pub horizontal_sync_offset: u16, + pub horizontal_sync_pulse_width: u16, + pub vertical_sync_offset: u16, + pub vertical_sync_pulse_width: u16, + pub horizontal_image_size_mm: u16, + pub vertical_image_size_mm: u16, + pub horizontal_border: u8, + pub vertical_border: u8, + pub features: TimingFeatures, +} + +/// Timing features bitmap +#[derive(Debug, Copy, Clone)] +pub struct TimingFeatures { + pub interlaced: bool, + pub stereo_mode: StereoMode, + pub sync_type: SyncType, +} + +/// Stereo mode options +#[derive(Debug, Copy, Clone)] +pub enum StereoMode { + None, + FieldSequentialRight, + FieldSequentialLeft, + InterleavedRightEven, + InterleavedLeftEven, + FourWayInterleaved, + SideBySideInterleaved, +} + +/// Sync type options +#[derive(Debug, Copy, Clone)] +pub enum SyncType { + Analog { + bipolar: bool, + serration: bool, + sync_on_rgb: bool, + }, + DigitalComposite { + serration: bool, + hsync_positive: bool, + }, + DigitalSeparate { + vsync_positive: bool, + hsync_positive: bool, + }, +} + +impl Edid { + /// Parse the EDID from raw bytes + pub fn parse(edid_data: &[u8; 128]) -> Result { + // Verify checksum + let mut checksum: u8 = 0; + for &byte in edid_data.iter() { + checksum = checksum.wrapping_add(byte); + } + if checksum != 0 { + return Err(EdidError::InvalidChecksum); + } + // Parse EDID header + let header = EdidHeader { + pattern: [ + edid_data[0], edid_data[1], edid_data[2], edid_data[3], + edid_data[4], edid_data[5], edid_data[6], edid_data[7], + ], + manufacturer_id: [edid_data[8], edid_data[9]], + product_code: u16::from_le_bytes([edid_data[10], edid_data[11]]), + serial_number: u32::from_le_bytes([ + edid_data[12], edid_data[13], edid_data[14], edid_data[15], + ]), + manufacture_week: edid_data[16], + manufacture_year: edid_data[17], + version: edid_data[18], + revision: edid_data[19], + }; + // Verify header pattern + if header.pattern != [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00] { + return Err(EdidError::InvalidHeaderPattern); + } + let mut descriptors = [Descriptor::RawDescriptor([0; 18]); 4]; + for i in 0..4 { + let offset = 54 + i * 18; + let mut data = [0; 18]; + data.copy_from_slice(&edid_data[offset..offset + 18]); + // Parse the descriptor based on its format + descriptors[i] = Self::parse_descriptor(&data); + } + + Ok(Edid { + header, + descriptors, + extensions: edid_data[126], + checksum: edid_data[127], + }) + } + + /// Parse a descriptor block + fn parse_descriptor(data: &[u8; 18]) -> Descriptor { + // Check if it's a detailed timing descriptor (pixel clock != 0) + let pixel_clock = u16::from_le_bytes([data[0], data[1]]); + if pixel_clock != 0 { + // Detailed timing descriptor + return Descriptor::DetailedTiming(Self::parse_detailed_timing(data)); + } + // It's not a detailed timing descriptor, store raw data + Descriptor::RawDescriptor(*data) + } + + /// Parse a detailed timing descriptor + fn parse_detailed_timing(data: &[u8; 18]) -> DetailedTimingDescriptor { + let pixel_clock = u16::from_le_bytes([data[0], data[1]]) as u32 * 10; // 10 kHz units + // + // Horizontal active/blanking + let h_active = ((data[4] as u16 & 0xF0) << 4) | data[2] as u16; + let h_blanking = ((data[4] as u16 & 0x0F) << 8) | data[3] as u16; + + // Vertical active/blanking + let v_active = ((data[7] as u16 & 0xF0) << 4) | data[5] as u16; + let v_blanking = ((data[7] as u16 & 0x0F) << 8) | data[6] as u16; + + // Sync offsets and pulse widths + let h_sync_offset = ((data[11] as u16 & 0xC0) << 2) | data[8] as u16; + let h_sync_pulse_width = ((data[11] as u16 & 0x30) << 4) | data[9] as u16; + let v_sync_offset = ((data[11] as u16 & 0x0C) << 2) | ((data[10] as u16 & 0xF0) >> 4); + let v_sync_pulse_width = ((data[11] as u16 & 0x03) << 4) | (data[10] as u16 & 0x0F); + + // Image size + let h_image_size = ((data[14] as u16 & 0xF0) << 4) | data[12] as u16; + let v_image_size = ((data[14] as u16 & 0x0F) << 8) | data[13] as u16; + + // Borders + let h_border = data[15]; + let v_border = data[16]; + + // Features + let interlaced = (data[17] & 0x80) != 0; + + // Stereo mode + let stereo_bits = ((data[17] & 0x60) >> 4) | (data[17] & 0x01); + let stereo_mode = match stereo_bits { + 0x00 | 0x01 => StereoMode::None, + 0x02 => StereoMode::FieldSequentialRight, + 0x04 => StereoMode::FieldSequentialLeft, + 0x03 => StereoMode::InterleavedRightEven, + 0x05 => StereoMode::InterleavedLeftEven, + 0x06 => StereoMode::FourWayInterleaved, + 0x07 => StereoMode::SideBySideInterleaved, + _ => StereoMode::None, // Should not happen + }; + + // Sync type + let sync_type = match (data[17] >> 3) & 0x03 { + 0x00 => SyncType::Analog { + bipolar: (data[17] & 0x04) != 0, + serration: (data[17] & 0x02) != 0, + sync_on_rgb: (data[17] & 0x01) != 0, + }, + 0x02 => SyncType::DigitalComposite { + serration: (data[17] & 0x04) != 0, + hsync_positive: (data[17] & 0x02) != 0, + }, + 0x03 => SyncType::DigitalSeparate { + vsync_positive: (data[17] & 0x04) != 0, + hsync_positive: (data[17] & 0x02) != 0, + }, + _ => SyncType::Analog { + bipolar: false, + serration: false, + sync_on_rgb: false, + }, + }; + + DetailedTimingDescriptor { + pixel_clock_khz: pixel_clock, + horizontal_active: h_active, + horizontal_blanking: h_blanking, + vertical_active: v_active, + vertical_blanking: v_blanking, + horizontal_sync_offset: h_sync_offset, + horizontal_sync_pulse_width: h_sync_pulse_width, + vertical_sync_offset: v_sync_offset, + vertical_sync_pulse_width: v_sync_pulse_width, + horizontal_image_size_mm: h_image_size, + vertical_image_size_mm: v_image_size, + horizontal_border: h_border, + vertical_border: v_border, + features: TimingFeatures { + interlaced, + stereo_mode, + sync_type, + }, + } + } +} + +/// Error type for EDID parsing +#[derive(Debug)] +pub enum EdidError { + InvalidChecksum, + InvalidHeaderPattern, +} + +// A simple example of how to use the parser +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_edid_parse() { + // Example EDID data from Tiliqua screen + let edid_data = [ + 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, + 0xff, 0xff, 0x32, 0x31, 0x45, 0x6, 0x0, 0x0, + 0xc, 0x1c, 0x1, 0x3, 0x80, 0xf, 0xa, 0x78, + 0xa, 0xd, 0xc9, 0xa0, 0x57, 0x47, 0x98, 0x27, + 0x12, 0x48, 0x4c, 0x0, 0x0, 0x0, 0x1, 0xc1, + 0x1, 0x1, 0x1, 0xc1, 0x1, 0x1, 0x1, 0x1, + 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x9b, 0xe, + 0xd0, 0x64, 0x20, 0xd0, 0x28, 0x20, 0x28, 0x14, + 0x84, 0x4, 0xd0, 0xd0, 0x22, 0x0, 0x0, 0x1e, + 0x9c, 0xe, 0xd0, 0x64, 0x20, 0xd0, 0x28, 0x20, + 0x14, 0x28, 0x48, 0x1, 0x5, 0x28, 0x0, 0x20, + 0x20, 0x20, 0x0, 0x0, 0x0, 0xfa, 0x0, 0xa, + 0x20, 0x20, 0x20, 0x20, 0x2, 0x0, 0x20, 0x20, + 0x20, 0x20, 0x20, 0xa, 0x0, 0x0, 0x0, 0xfc, + 0x0, 0x5a, 0x4c, 0x37, 0x32, 0x30, 0x58, 0x37, + 0x32, 0x30, 0xa, 0x20, 0x20, 0x20, 0x1, 0x62, + ]; + + let edid = Edid::parse(&edid_data); + match edid { + Ok(data) => println!("Successfully parsed EDID: {:#?}", data), + Err(e) => panic!("Failed to parse EDID: {:?}", e), + } + } +} diff --git a/gateware/src/rs/lib/src/lib.rs b/gateware/src/rs/lib/src/lib.rs index fa34b3c2..28b44d6f 100644 --- a/gateware/src/rs/lib/src/lib.rs +++ b/gateware/src/rs/lib/src/lib.rs @@ -10,3 +10,4 @@ pub mod ui; pub mod dsp; pub mod midi; pub mod calibration; +pub mod edid; From f2e961460f2e440d5190391fc6c19aeeb38342ef Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sun, 16 Mar 2025 12:45:26 +0100 Subject: [PATCH 11/12] top/bootloader: parse edid before determining modeline --- gateware/src/top/bootloader/fw/src/main.rs | 82 +++++++++++----------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/gateware/src/top/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 81e1ebed..f1c3c895 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -425,21 +425,15 @@ where } } -fn edid_test(i2cdev: &mut I2c0) -> u8 { +fn edid_test(i2cdev: &mut I2c0) -> Result { info!("Read EDID..."); - let mut edid: [u8; 8] = [0; 8]; + let mut edid: [u8; 128] = [0; 128]; const EDID_ADDR: u8 = 0x50; - let mut mfg: u8 = 0; for i in 0..16 { - let _ = i2cdev.transaction(EDID_ADDR, &mut [Operation::Write(&[(i*8) as u8]), - Operation::Read(&mut edid)]); - info!("{:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x}", - edid[0], edid[1], edid[2], edid[3], edid[4], edid[5], edid[6], edid[7]); - if i == 1 { - mfg = edid[2]; - } + i2cdev.transaction(EDID_ADDR, &mut [Operation::Write(&[(i*8) as u8]), + Operation::Read(&mut edid[i*8..i*8+8])]).ok(); } - mfg + edid::Edid::parse(&edid) } #[entry] @@ -470,45 +464,51 @@ fn main() -> ! { } // Determine display modeline - let mfg: u8 = { + let edid = { let mut i2cdev0 = I2c0::new(unsafe { pac::I2C0::steal() } ); use embedded_hal::delay::DelayNs; timer.delay_ms(10); edid_test(&mut i2cdev0) }; + info!("edid: {:?}", edid); + + // Default rotation and modeline let mut video_rotate_90 = false; - let modeline = if mfg == 0x32 { - video_rotate_90 = true; - DVIModeline { - h_active : 720, - h_sync_start : 760, - h_sync_end : 780, - h_total : 820, - h_sync_invert : false, - v_active : 720, - v_sync_start : 744, - v_sync_end : 748, - v_total : 760, - v_sync_invert : false, - pixel_clk_mhz : 37.40, - } - } else { - DVIModeline { - h_active : 1280, - h_sync_start : 1390, - h_sync_end : 1430, - h_total : 1650, - h_sync_invert : false, - v_active : 720, - v_sync_start : 725, - v_sync_end : 730, - v_total : 750, - v_sync_invert : false, - pixel_clk_mhz : 74.25, - } + let mut modeline = DVIModeline { + h_active : 1280, + h_sync_start : 1390, + h_sync_end : 1430, + h_total : 1650, + h_sync_invert : false, + v_active : 720, + v_sync_start : 725, + v_sync_end : 730, + v_total : 750, + v_sync_invert : false, + pixel_clk_mhz : 74.25, }; + // If connected to 720x720 screen, use native resolution. + if let Ok(edid_parsed) = edid { + if edid_parsed.header.product_code == 0x3132 { + video_rotate_90 = true; + modeline = DVIModeline { + h_active : 720, + h_sync_start : 760, + h_sync_end : 780, + h_total : 820, + h_sync_invert : false, + v_active : 720, + v_sync_start : 744, + v_sync_end : 748, + v_total : 760, + v_sync_invert : false, + pixel_clk_mhz : 37.40, + }; + } + } + // Setup external PLL let maybe_external_pll = if HW_REV_MAJOR >= 4 { From 868a726844c46c8545dec503654fbab01c4e0886 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Tue, 22 Apr 2025 21:57:54 +0200 Subject: [PATCH 12/12] tiliqua_soc: reduce i2c0 bus speed to 100kHz for more reliable EDID reads --- gateware/src/tiliqua/tiliqua_platform.py | 18 ++++++++++++++++++ gateware/src/tiliqua/tiliqua_soc.py | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/gateware/src/tiliqua/tiliqua_platform.py b/gateware/src/tiliqua/tiliqua_platform.py index 2bc87775..bdc41c59 100644 --- a/gateware/src/tiliqua/tiliqua_platform.py +++ b/gateware/src/tiliqua/tiliqua_platform.py @@ -322,10 +322,28 @@ class _TiliquaR4Mobo: Attrs(IO_TYPE="LVCMOS33"))), # Motherboard PCBA I2C bus. Includes: + # # - address 0x05: PCA9635 LED driver # - address 0x47: TUSB322I USB-C controller # - address 0x50: DVI EDID EEPROM (through 3V3 <-> 5V translator) # - address 0x60: SI5351A external PLL + # + # WARN: mobo i2c bus speed should never exceed 100kHz if you want to reliably + # be able to read the EDID of external monitors. I have seen cases where going + # above 100kHz causes the monitor to ACK packets that were not destined for + # its EEPROM (instead for other i2c peripherals on i2c0) - this is dangerous and + # could unintentionally write to the monitor EEPROM (!!!) + # + # There are some slave addresses on the DDC channel that are reserved according + # to the standard. As far as I can tell, we have no collisions. Reference: + # + # VESA Display Data Channel Command Interface (DDC/CI) + # Standard Version 1.1, October 29, 2004 + # + # Reserved for VCP: 0x6E + # Reserved in table 4: 0xF0 - 0xFF + # Reserved in table 5: 0x12, 0x14, 0x16, 0x80, 0x40, 0xA0 + # Resource("i2c", 0, Subsignal("sda", Pins("51", dir="io", conn=("m2", 0))), Subsignal("scl", Pins("53", dir="io", conn=("m2", 0))), diff --git a/gateware/src/tiliqua/tiliqua_soc.py b/gateware/src/tiliqua/tiliqua_soc.py index 9fbf59cc..6b777411 100644 --- a/gateware/src/tiliqua/tiliqua_soc.py +++ b/gateware/src/tiliqua/tiliqua_soc.py @@ -221,7 +221,9 @@ def __init__(self, *, firmware_bin_path, default_modeline, ui_name, ui_sha, plat # mobo i2c self.i2c0 = i2c.Peripheral() - self.i2c_stream0 = i2c.I2CStreamer(period_cyc=256) + # XXX: 100kHz bus speed. DO NOT INCREASE THIS. See comment on this bus in + # tiliqua_platform.py for more details. + self.i2c_stream0 = i2c.I2CStreamer(period_cyc=600) self.csr_decoder.add(self.i2c0.bus, addr=self.i2c0_base, name="i2c0") # eurorack-pmod i2c