video: reintegrate dvisampler from mixxeo (DVI/HDMI interfaces are common in today...
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Sun, 1 Mar 2015 09:01:23 +0000 (10:01 +0100)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Sun, 1 Mar 2015 09:07:52 +0000 (10:07 +0100)
12 files changed:
misoclib/video/dvisampler/__init__.py [new file with mode: 0644]
misoclib/video/dvisampler/analysis.py [new file with mode: 0644]
misoclib/video/dvisampler/chansync.py [new file with mode: 0644]
misoclib/video/dvisampler/charsync.py [new file with mode: 0644]
misoclib/video/dvisampler/clocking.py [new file with mode: 0644]
misoclib/video/dvisampler/common.py [new file with mode: 0644]
misoclib/video/dvisampler/datacapture.py [new file with mode: 0644]
misoclib/video/dvisampler/debug.py [new file with mode: 0644]
misoclib/video/dvisampler/decoding.py [new file with mode: 0644]
misoclib/video/dvisampler/dma.py [new file with mode: 0644]
misoclib/video/dvisampler/edid.py [new file with mode: 0644]
misoclib/video/dvisampler/wer.py [new file with mode: 0644]

diff --git a/misoclib/video/dvisampler/__init__.py b/misoclib/video/dvisampler/__init__.py
new file mode 100644 (file)
index 0000000..aaf28d0
--- /dev/null
@@ -0,0 +1,79 @@
+from migen.fhdl.std import *
+from migen.bank.description import AutoCSR
+
+from misoclib.video.dvisampler.edid import EDID
+from misoclib.video.dvisampler.clocking import Clocking
+from misoclib.video.dvisampler.datacapture import DataCapture
+from misoclib.video.dvisampler.charsync import CharSync
+from misoclib.video.dvisampler.wer import WER
+from misoclib.video.dvisampler.decoding import Decoding
+from misoclib.video.dvisampler.chansync import ChanSync
+from misoclib.video.dvisampler.analysis import SyncPolarity, ResolutionDetection, FrameExtraction
+from misoclib.video.dvisampler.dma import DMA
+
+class DVISampler(Module, AutoCSR):
+       def __init__(self, pads, lasmim, n_dma_slots=2):
+               self.submodules.edid = EDID(pads)
+               self.submodules.clocking = Clocking(pads)
+
+               for datan in range(3):
+                       name = "data" + str(datan)
+
+                       cap = DataCapture(getattr(pads, name + "_p"), getattr(pads, name + "_n"), 8)
+                       setattr(self.submodules, name + "_cap", cap)
+                       self.comb += cap.serdesstrobe.eq(self.clocking.serdesstrobe)
+
+                       charsync = CharSync()
+                       setattr(self.submodules, name + "_charsync", charsync)
+                       self.comb += charsync.raw_data.eq(cap.d)
+
+                       wer = WER()
+                       setattr(self.submodules, name + "_wer", wer)
+                       self.comb += wer.data.eq(charsync.data)
+
+                       decoding = Decoding()
+                       setattr(self.submodules, name + "_decod", decoding)
+                       self.comb += [
+                               decoding.valid_i.eq(charsync.synced),
+                               decoding.input.eq(charsync.data)
+                       ]
+
+               self.submodules.chansync = ChanSync()
+               self.comb += [
+                       self.chansync.valid_i.eq(self.data0_decod.valid_o & \
+                         self.data1_decod.valid_o & self.data2_decod.valid_o),
+                       self.chansync.data_in0.eq(self.data0_decod.output),
+                       self.chansync.data_in1.eq(self.data1_decod.output),
+                       self.chansync.data_in2.eq(self.data2_decod.output),
+               ]
+
+               self.submodules.syncpol = SyncPolarity()
+               self.comb += [
+                       self.syncpol.valid_i.eq(self.chansync.chan_synced),
+                       self.syncpol.data_in0.eq(self.chansync.data_out0),
+                       self.syncpol.data_in1.eq(self.chansync.data_out1),
+                       self.syncpol.data_in2.eq(self.chansync.data_out2)
+               ]
+
+               self.submodules.resdetection = ResolutionDetection()
+               self.comb += [
+                       self.resdetection.valid_i.eq(self.syncpol.valid_o),
+                       self.resdetection.de.eq(self.syncpol.de),
+                       self.resdetection.vsync.eq(self.syncpol.vsync)
+               ]
+
+               self.submodules.frame = FrameExtraction(24*lasmim.dw//32)
+               self.comb += [
+                       self.frame.valid_i.eq(self.syncpol.valid_o),
+                       self.frame.de.eq(self.syncpol.de),
+                       self.frame.vsync.eq(self.syncpol.vsync),
+                       self.frame.r.eq(self.syncpol.r),
+                       self.frame.g.eq(self.syncpol.g),
+                       self.frame.b.eq(self.syncpol.b)
+               ]
+
+               self.submodules.dma = DMA(lasmim, n_dma_slots)
+               self.comb += self.frame.frame.connect(self.dma.frame)
+               self.ev = self.dma.ev
+
+       autocsr_exclude = {"ev"}
diff --git a/misoclib/video/dvisampler/analysis.py b/misoclib/video/dvisampler/analysis.py
new file mode 100644 (file)
index 0000000..0b70806
--- /dev/null
@@ -0,0 +1,205 @@
+from migen.fhdl.std import *
+from migen.genlib.cdc import MultiReg, PulseSynchronizer
+from migen.genlib.fifo import AsyncFIFO
+from migen.genlib.record import Record
+from migen.bank.description import *
+from migen.flow.actor import *
+
+from misoclib.video.dvisampler.common import channel_layout
+
+class SyncPolarity(Module):
+       def __init__(self):
+               self.valid_i = Signal()
+               self.data_in0 = Record(channel_layout)
+               self.data_in1 = Record(channel_layout)
+               self.data_in2 = Record(channel_layout)
+
+               self.valid_o = Signal()
+               self.de = Signal()
+               self.hsync = Signal()
+               self.vsync = Signal()
+               self.r = Signal(8)
+               self.g = Signal(8)
+               self.b = Signal(8)
+
+               ###
+
+               de = self.data_in0.de
+               de_r = Signal()
+               c = self.data_in0.c
+               c_polarity = Signal(2)
+               c_out = Signal(2)
+
+               self.comb += [
+                       self.de.eq(de_r),
+                       self.hsync.eq(c_out[0]),
+                       self.vsync.eq(c_out[1])
+               ]
+
+               self.sync.pix += [
+                       self.valid_o.eq(self.valid_i),
+                       self.r.eq(self.data_in2.d),
+                       self.g.eq(self.data_in1.d),
+                       self.b.eq(self.data_in0.d),
+
+                       de_r.eq(de),
+                       If(de_r & ~de,
+                               c_polarity.eq(c),
+                               c_out.eq(0)
+                       ).Else(
+                               c_out.eq(c ^ c_polarity)
+                       )
+               ]
+
+class ResolutionDetection(Module, AutoCSR):
+       def __init__(self, nbits=11):
+               self.valid_i = Signal()
+               self.vsync = Signal()
+               self.de = Signal()
+
+               self._hres = CSRStatus(nbits)
+               self._vres = CSRStatus(nbits)
+
+               ###
+
+               # Detect DE transitions
+               de_r = Signal()
+               pn_de = Signal()
+               self.sync.pix += de_r.eq(self.de)
+               self.comb += pn_de.eq(~self.de & de_r)
+
+               # HRES
+               hcounter = Signal(nbits)
+               self.sync.pix += If(self.valid_i & self.de,
+                               hcounter.eq(hcounter + 1)
+                       ).Else(
+                               hcounter.eq(0)
+                       )
+
+               hcounter_st = Signal(nbits)
+               self.sync.pix += If(self.valid_i,
+                               If(pn_de, hcounter_st.eq(hcounter))
+                       ).Else(
+                               hcounter_st.eq(0)
+                       )
+               self.specials += MultiReg(hcounter_st, self._hres.status)
+
+               # VRES
+               vsync_r = Signal()
+               p_vsync = Signal()
+               self.sync.pix += vsync_r.eq(self.vsync),
+               self.comb += p_vsync.eq(self.vsync & ~vsync_r)
+
+               vcounter = Signal(nbits)
+               self.sync.pix += If(self.valid_i & p_vsync,
+                               vcounter.eq(0)
+                       ).Elif(pn_de,
+                               vcounter.eq(vcounter + 1)
+                       )
+
+               vcounter_st = Signal(nbits)
+               self.sync.pix += If(self.valid_i,
+                               If(p_vsync, vcounter_st.eq(vcounter))
+                       ).Else(
+                               vcounter_st.eq(0)
+                       )
+               self.specials += MultiReg(vcounter_st, self._vres.status)
+
+class FrameExtraction(Module, AutoCSR):
+       def __init__(self, word_width):
+               # in pix clock domain
+               self.valid_i = Signal()
+               self.vsync = Signal()
+               self.de = Signal()
+               self.r = Signal(8)
+               self.g = Signal(8)
+               self.b = Signal(8)
+
+               # in sys clock domain
+               word_layout = [("sof", 1), ("pixels", word_width)]
+               self.frame = Source(word_layout)
+               self.busy = Signal()
+
+               self._r_overflow = CSR()
+
+               ###
+
+               # start of frame detection
+               vsync_r = Signal()
+               new_frame = Signal()
+               self.comb += new_frame.eq(self.vsync & ~vsync_r)
+               self.sync.pix += vsync_r.eq(self.vsync)
+
+               # pack pixels into words
+               cur_word = Signal(word_width)
+               cur_word_valid = Signal()
+               encoded_pixel = Signal(24)
+               self.comb += encoded_pixel.eq(Cat(self.b, self.g, self.r))
+               pack_factor = word_width//24
+               assert(pack_factor & (pack_factor - 1) == 0) # only support powers of 2
+               pack_counter = Signal(max=pack_factor)
+               self.sync.pix += [
+                       cur_word_valid.eq(0),
+                       If(new_frame,
+                               cur_word_valid.eq(pack_counter == (pack_factor - 1)),
+                               pack_counter.eq(0),
+                       ).Elif(self.valid_i & self.de,
+                               [If(pack_counter == (pack_factor-i-1),
+                                       cur_word[24*i:24*(i+1)].eq(encoded_pixel)) for i in range(pack_factor)],
+                               cur_word_valid.eq(pack_counter == (pack_factor - 1)),
+                               pack_counter.eq(pack_counter + 1)
+                       )
+               ]
+
+               # FIFO
+               fifo = RenameClockDomains(AsyncFIFO(word_layout, 512),
+                       {"write": "pix", "read": "sys"})
+               self.submodules += fifo
+               self.comb += [
+                       fifo.din.pixels.eq(cur_word),
+                       fifo.we.eq(cur_word_valid)
+               ]
+               self.sync.pix += \
+                       If(new_frame,
+                               fifo.din.sof.eq(1)
+                       ).Elif(cur_word_valid,
+                               fifo.din.sof.eq(0)
+                       )
+               self.comb += [
+                       self.frame.stb.eq(fifo.readable),
+                       self.frame.payload.eq(fifo.dout),
+                       fifo.re.eq(self.frame.ack),
+                       self.busy.eq(0)
+               ]
+
+               # overflow detection
+               pix_overflow = Signal()
+               pix_overflow_reset = Signal()
+               self.sync.pix += [
+                       If(fifo.we & ~fifo.writable,
+                               pix_overflow.eq(1)
+                       ).Elif(pix_overflow_reset,
+                               pix_overflow.eq(0)
+                       )
+               ]
+
+               sys_overflow = Signal()
+               self.specials += MultiReg(pix_overflow, sys_overflow)
+               self.submodules.overflow_reset = PulseSynchronizer("sys", "pix")
+               self.submodules.overflow_reset_ack = PulseSynchronizer("pix", "sys")
+               self.comb += [
+                       pix_overflow_reset.eq(self.overflow_reset.o),
+                       self.overflow_reset_ack.i.eq(pix_overflow_reset)
+               ]
+
+               overflow_mask = Signal()
+               self.comb += [
+                       self._r_overflow.w.eq(sys_overflow & ~overflow_mask),
+                       self.overflow_reset.i.eq(self._r_overflow.re)
+               ]
+               self.sync += \
+                       If(self._r_overflow.re,
+                               overflow_mask.eq(1)
+                       ).Elif(self.overflow_reset_ack.o,
+                               overflow_mask.eq(0)
+                       )
diff --git a/misoclib/video/dvisampler/chansync.py b/misoclib/video/dvisampler/chansync.py
new file mode 100644 (file)
index 0000000..2aeb6db
--- /dev/null
@@ -0,0 +1,129 @@
+from migen.fhdl.std import *
+from migen.genlib.cdc import MultiReg
+from migen.genlib.fifo import _inc
+from migen.genlib.record import Record, layout_len
+from migen.genlib.misc import optree
+from migen.bank.description import *
+
+from misoclib.video.dvisampler.common import channel_layout
+
+class _SyncBuffer(Module):
+       def __init__(self, width, depth):
+               self.din = Signal(width)
+               self.dout = Signal(width)
+               self.re = Signal()
+
+               ###
+
+               produce = Signal(max=depth)
+               consume = Signal(max=depth)
+               storage = Memory(width, depth)
+               self.specials += storage
+
+               wrport = storage.get_port(write_capable=True)
+               self.specials += wrport
+               self.comb += [
+                       wrport.adr.eq(produce),
+                       wrport.dat_w.eq(self.din),
+                       wrport.we.eq(1)
+               ]
+               self.sync += _inc(produce, depth)
+
+               rdport = storage.get_port(async_read=True)
+               self.specials += rdport
+               self.comb += [
+                       rdport.adr.eq(consume),
+                       self.dout.eq(rdport.dat_r)
+               ]
+               self.sync += If(self.re, _inc(consume, depth))
+
+class ChanSync(Module, AutoCSR):
+       def __init__(self, nchan=3, depth=8):
+               self.valid_i = Signal()
+               self.chan_synced = Signal()
+
+               self._r_channels_synced = CSRStatus()
+
+               lst_control = []
+               all_control = Signal()
+               for i in range(nchan):
+                       name = "data_in" + str(i)
+                       data_in = Record(channel_layout, name=name)
+                       setattr(self, name, data_in)
+                       name = "data_out" + str(i)
+                       data_out = Record(channel_layout, name=name)
+                       setattr(self, name, data_out)
+
+                       ###
+
+                       syncbuffer = RenameClockDomains(_SyncBuffer(layout_len(channel_layout), depth), "pix")
+                       self.submodules += syncbuffer
+                       self.comb += [
+                               syncbuffer.din.eq(data_in.raw_bits()),
+                               data_out.raw_bits().eq(syncbuffer.dout)
+                       ]
+                       is_control = Signal()
+                       self.comb += [
+                               is_control.eq(~data_out.de),
+                               syncbuffer.re.eq(~is_control | all_control)
+                       ]
+                       lst_control.append(is_control)
+
+               some_control = Signal()
+               self.comb += [
+                       all_control.eq(optree("&", lst_control)),
+                       some_control.eq(optree("|", lst_control))
+               ]
+               self.sync.pix += If(~self.valid_i,
+                               self.chan_synced.eq(0)
+                       ).Else(
+                               If(some_control,
+                                       If(all_control,
+                                               self.chan_synced.eq(1)
+                                       ).Else(
+                                               self.chan_synced.eq(0)
+                                       )
+                               )
+                       )
+               self.specials += MultiReg(self.chan_synced, self._r_channels_synced.status)
+
+class _TB(Module):
+       def __init__(self, test_seq_it):
+               self.test_seq_it = test_seq_it
+
+               self.submodules.chansync = RenameClockDomains(ChanSync(), {"pix": "sys"})
+               self.comb += self.chansync.valid_i.eq(1)
+
+       def do_simulation(self, selfp):
+               try:
+                       de0, de1, de2 = next(self.test_seq_it)
+               except StopIteration:
+                       raise StopSimulation
+
+               selfp.chansync.data_in0.de = de0
+               selfp.chansync.data_in1.de = de1
+               selfp.chansync.data_in2.de = de2
+               selfp.chansync.data_in0.d = selfp.simulator.cycle_counter
+               selfp.chansync.data_in1.d = selfp.simulator.cycle_counter
+               selfp.chansync.data_in2.d = selfp.simulator.cycle_counter
+
+               out0 = selfp.chansync.data_out0.d
+               out1 = selfp.chansync.data_out1.d
+               out2 = selfp.chansync.data_out2.d
+
+               print("{0:5} {1:5} {2:5}".format(out0, out1, out2))
+
+if __name__ == "__main__":
+       from migen.sim.generic import run_simulation
+
+       test_seq = [
+               (1, 1, 1),
+               (1, 1, 0),
+               (0, 0, 0),
+               (0, 0, 0),
+               (0, 0, 1),
+               (1, 1, 1),
+               (1, 1, 1),
+       ]
+       tb = _TB(iter(test_seq*2))
+       run_simulation(tb)
diff --git a/misoclib/video/dvisampler/charsync.py b/misoclib/video/dvisampler/charsync.py
new file mode 100644 (file)
index 0000000..c33c44c
--- /dev/null
@@ -0,0 +1,53 @@
+from migen.fhdl.std import *
+from migen.genlib.cdc import MultiReg
+from migen.genlib.misc import optree
+from migen.bank.description import *
+
+from misoclib.video.dvisampler.common import control_tokens
+
+class CharSync(Module, AutoCSR):
+       def __init__(self, required_controls=8):
+               self.raw_data = Signal(10)
+               self.synced = Signal()
+               self.data = Signal(10)
+
+               self._r_char_synced = CSRStatus()
+               self._r_ctl_pos = CSRStatus(bits_for(9))
+
+               ###
+
+               raw_data1 = Signal(10)
+               self.sync.pix += raw_data1.eq(self.raw_data)
+               raw = Signal(20)
+               self.comb += raw.eq(Cat(raw_data1, self.raw_data))
+
+               found_control = Signal()
+               control_position = Signal(max=10)
+               self.sync.pix += found_control.eq(0)
+               for i in range(10):
+                       self.sync.pix += If(optree("|", [raw[i:i+10] == t for t in control_tokens]),
+                               found_control.eq(1),
+                               control_position.eq(i)
+                       )
+
+               control_counter = Signal(max=required_controls)
+               previous_control_position = Signal(max=10)
+               word_sel = Signal(max=10)
+               self.sync.pix += [
+                       If(found_control & (control_position == previous_control_position),
+                               If(control_counter == (required_controls - 1),
+                                       control_counter.eq(0),
+                                       self.synced.eq(1),
+                                       word_sel.eq(control_position)
+                               ).Else(
+                                       control_counter.eq(control_counter + 1)
+                               )
+                       ).Else(
+                               control_counter.eq(0)
+                       ),
+                       previous_control_position.eq(control_position)
+               ]
+               self.specials += MultiReg(self.synced, self._r_char_synced.status)
+               self.specials += MultiReg(word_sel, self._r_ctl_pos.status)
+
+               self.sync.pix += self.data.eq(raw >> word_sel)
diff --git a/misoclib/video/dvisampler/clocking.py b/misoclib/video/dvisampler/clocking.py
new file mode 100644 (file)
index 0000000..36f91b2
--- /dev/null
@@ -0,0 +1,79 @@
+from migen.fhdl.std import *
+from migen.genlib.cdc import MultiReg
+from migen.bank.description import *
+
+class Clocking(Module, AutoCSR):
+       def __init__(self, pads):
+               self._r_pll_reset = CSRStorage(reset=1)
+               self._r_locked = CSRStatus()
+
+               # DRP
+               self._r_pll_adr = CSRStorage(5)
+               self._r_pll_dat_r = CSRStatus(16)
+               self._r_pll_dat_w = CSRStorage(16)
+               self._r_pll_read = CSR()
+               self._r_pll_write = CSR()
+               self._r_pll_drdy = CSRStatus()
+
+               self.locked = Signal()
+               self.serdesstrobe = Signal()
+               self.clock_domains._cd_pix = ClockDomain()
+               self.clock_domains._cd_pix2x = ClockDomain()
+               self.clock_domains._cd_pix10x = ClockDomain(reset_less=True)
+
+               ###
+
+               clk_se = Signal()
+               self.specials += Instance("IBUFDS", i_I=pads.clk_p, i_IB=pads.clk_n, o_O=clk_se)
+
+               clkfbout = Signal()
+               pll_locked = Signal()
+               pll_clk0 = Signal()
+               pll_clk1 = Signal()
+               pll_clk2 = Signal()
+               pll_drdy = Signal()
+               self.sync += If(self._r_pll_read.re | self._r_pll_write.re,
+                       self._r_pll_drdy.status.eq(0)
+               ).Elif(pll_drdy,
+                       self._r_pll_drdy.status.eq(1)
+               )
+               self.specials += Instance("PLL_ADV",
+                       p_CLKFBOUT_MULT=10,
+                       p_CLKOUT0_DIVIDE=1,  # pix10x
+                       p_CLKOUT1_DIVIDE=5,  # pix2x
+                       p_CLKOUT2_DIVIDE=10, # pix
+                       p_COMPENSATION="INTERNAL",
+
+                       i_CLKINSEL=1,
+                       i_CLKIN1=clk_se,
+                       o_CLKOUT0=pll_clk0, o_CLKOUT1=pll_clk1, o_CLKOUT2=pll_clk2,
+                       o_CLKFBOUT=clkfbout, i_CLKFBIN=clkfbout,
+                       o_LOCKED=pll_locked, i_RST=self._r_pll_reset.storage,
+
+                       i_DADDR=self._r_pll_adr.storage,
+                       o_DO=self._r_pll_dat_r.status,
+                       i_DI=self._r_pll_dat_w.storage,
+                       i_DEN=self._r_pll_read.re | self._r_pll_write.re,
+                       i_DWE=self._r_pll_write.re,
+                       o_DRDY=pll_drdy,
+                       i_DCLK=ClockSignal())
+
+               locked_async = Signal()
+               self.specials += [
+                       Instance("BUFPLL", p_DIVIDE=5,
+                               i_PLLIN=pll_clk0, i_GCLK=ClockSignal("pix2x"), i_LOCKED=pll_locked,
+                               o_IOCLK=self._cd_pix10x.clk, o_LOCK=locked_async, o_SERDESSTROBE=self.serdesstrobe),
+                       Instance("BUFG", i_I=pll_clk1, o_O=self._cd_pix2x.clk),
+                       Instance("BUFG", i_I=pll_clk2, o_O=self._cd_pix.clk),
+                       MultiReg(locked_async, self.locked, "sys")
+               ]
+               self.comb += self._r_locked.status.eq(self.locked)
+
+               # sychronize pix+pix2x reset
+               pix_rst_n = 1
+               for i in range(2):
+                       new_pix_rst_n = Signal()
+                       self.specials += Instance("FDCE", i_D=pix_rst_n, i_CE=1, i_C=ClockSignal("pix"),
+                               i_CLR=~locked_async, o_Q=new_pix_rst_n)
+                       pix_rst_n = new_pix_rst_n
+               self.comb += self._cd_pix.rst.eq(~pix_rst_n), self._cd_pix2x.rst.eq(~pix_rst_n)
diff --git a/misoclib/video/dvisampler/common.py b/misoclib/video/dvisampler/common.py
new file mode 100644 (file)
index 0000000..7fb9a42
--- /dev/null
@@ -0,0 +1,2 @@
+control_tokens = [0b1101010100, 0b0010101011, 0b0101010100, 0b1010101011]
+channel_layout = [("d", 8), ("c", 2), ("de", 1)]
diff --git a/misoclib/video/dvisampler/datacapture.py b/misoclib/video/dvisampler/datacapture.py
new file mode 100644 (file)
index 0000000..d788843
--- /dev/null
@@ -0,0 +1,186 @@
+from migen.fhdl.std import *
+from migen.genlib.cdc import MultiReg, PulseSynchronizer
+from migen.bank.description import *
+
+class DataCapture(Module, AutoCSR):
+       def __init__(self, pad_p, pad_n, ntbits):
+               self.serdesstrobe = Signal()
+               self.d = Signal(10)
+
+               self._r_dly_ctl = CSR(6)
+               self._r_dly_busy = CSRStatus(2)
+               self._r_phase = CSRStatus(2)
+               self._r_phase_reset = CSR()
+
+               ###
+
+               # IO
+               pad_se = Signal()
+               self.specials += Instance("IBUFDS", i_I=pad_p, i_IB=pad_n, o_O=pad_se)
+
+               pad_delayed_master = Signal()
+               pad_delayed_slave = Signal()
+               delay_inc = Signal()
+               delay_ce = Signal()
+               delay_master_cal = Signal()
+               delay_master_rst = Signal()
+               delay_master_busy = Signal()
+               delay_slave_cal = Signal()
+               delay_slave_rst = Signal()
+               delay_slave_busy = Signal()
+               self.specials += Instance("IODELAY2",
+                       p_SERDES_MODE="MASTER",
+                       p_DELAY_SRC="IDATAIN", p_IDELAY_TYPE="DIFF_PHASE_DETECTOR",
+                       p_COUNTER_WRAPAROUND="STAY_AT_LIMIT", p_DATA_RATE="SDR",
+
+                       i_IDATAIN=pad_se, o_DATAOUT=pad_delayed_master,
+                       i_CLK=ClockSignal("pix2x"), i_IOCLK0=ClockSignal("pix10x"),
+
+                       i_INC=delay_inc, i_CE=delay_ce,
+                       i_CAL=delay_master_cal, i_RST=delay_master_rst, o_BUSY=delay_master_busy,
+                       i_T=1)
+               self.specials += Instance("IODELAY2",
+                       p_SERDES_MODE="SLAVE",
+                       p_DELAY_SRC="IDATAIN", p_IDELAY_TYPE="DIFF_PHASE_DETECTOR",
+                       p_COUNTER_WRAPAROUND="WRAPAROUND", p_DATA_RATE="SDR",
+
+                       i_IDATAIN=pad_se, o_DATAOUT=pad_delayed_slave,
+                       i_CLK=ClockSignal("pix2x"), i_IOCLK0=ClockSignal("pix10x"),
+
+                       i_INC=delay_inc, i_CE=delay_ce,
+                       i_CAL=delay_slave_cal, i_RST=delay_slave_rst, o_BUSY=delay_slave_busy,
+                       i_T=1)
+
+               dsr2 = Signal(5)
+               pd_valid = Signal()
+               pd_incdec = Signal()
+               pd_edge = Signal()
+               pd_cascade = Signal()
+               self.specials += Instance("ISERDES2",
+                       p_SERDES_MODE="MASTER",
+                       p_BITSLIP_ENABLE="FALSE", p_DATA_RATE="SDR", p_DATA_WIDTH=5,
+                       p_INTERFACE_TYPE="RETIMED",
+
+                       i_D=pad_delayed_master,
+                       o_Q4=dsr2[4], o_Q3=dsr2[3], o_Q2=dsr2[2], o_Q1=dsr2[1],
+
+                       i_BITSLIP=0, i_CE0=1, i_RST=0,
+                       i_CLK0=ClockSignal("pix10x"), i_CLKDIV=ClockSignal("pix2x"),
+                       i_IOCE=self.serdesstrobe,
+
+                       o_VALID=pd_valid, o_INCDEC=pd_incdec,
+                       i_SHIFTIN=pd_edge, o_SHIFTOUT=pd_cascade)
+               self.specials += Instance("ISERDES2",
+                       p_SERDES_MODE="SLAVE",
+                       p_BITSLIP_ENABLE="FALSE", p_DATA_RATE="SDR", p_DATA_WIDTH=5,
+                       p_INTERFACE_TYPE="RETIMED",
+
+                       i_D=pad_delayed_slave,
+                       o_Q4=dsr2[0],
+
+                       i_BITSLIP=0, i_CE0=1, i_RST=0,
+                       i_CLK0=ClockSignal("pix10x"), i_CLKDIV=ClockSignal("pix2x"),
+                       i_IOCE=self.serdesstrobe,
+
+                       i_SHIFTIN=pd_cascade, o_SHIFTOUT=pd_edge)
+
+               # Phase error accumulator
+               lateness = Signal(ntbits, reset=2**(ntbits - 1))
+               too_late = Signal()
+               too_early = Signal()
+               reset_lateness = Signal()
+               self.comb += [
+                       too_late.eq(lateness == (2**ntbits - 1)),
+                       too_early.eq(lateness == 0)
+               ]
+               self.sync.pix2x += [
+                       If(reset_lateness,
+                               lateness.eq(2**(ntbits - 1))
+                       ).Elif(~delay_master_busy & ~delay_slave_busy & ~too_late & ~too_early,
+                               If(pd_valid &  pd_incdec, lateness.eq(lateness - 1)),
+                               If(pd_valid & ~pd_incdec, lateness.eq(lateness + 1))
+                       )
+               ]
+
+               # Delay control
+               self.submodules.delay_master_done = PulseSynchronizer("pix2x", "sys")
+               delay_master_pending = Signal()
+               self.sync.pix2x += [
+                       self.delay_master_done.i.eq(0),
+                       If(~delay_master_pending,
+                               If(delay_master_cal | delay_ce, delay_master_pending.eq(1))
+                       ).Else(
+                               If(~delay_master_busy,
+                                       self.delay_master_done.i.eq(1),
+                                       delay_master_pending.eq(0)
+                               )
+                       )
+               ]
+               self.submodules.delay_slave_done = PulseSynchronizer("pix2x", "sys")
+               delay_slave_pending = Signal()
+               self.sync.pix2x += [
+                       self.delay_slave_done.i.eq(0),
+                       If(~delay_slave_pending,
+                               If(delay_slave_cal | delay_ce, delay_slave_pending.eq(1))
+                       ).Else(
+                               If(~delay_slave_busy,
+                                       self.delay_slave_done.i.eq(1),
+                                       delay_slave_pending.eq(0)
+                               )
+                       )
+               ]
+
+               self.submodules.do_delay_master_cal = PulseSynchronizer("sys", "pix2x")
+               self.submodules.do_delay_master_rst = PulseSynchronizer("sys", "pix2x")
+               self.submodules.do_delay_slave_cal = PulseSynchronizer("sys", "pix2x")
+               self.submodules.do_delay_slave_rst = PulseSynchronizer("sys", "pix2x")
+               self.submodules.do_delay_inc = PulseSynchronizer("sys", "pix2x")
+               self.submodules.do_delay_dec = PulseSynchronizer("sys", "pix2x")
+               self.comb += [
+                       delay_master_cal.eq(self.do_delay_master_cal.o),
+                       delay_master_rst.eq(self.do_delay_master_rst.o),
+                       delay_slave_cal.eq(self.do_delay_slave_cal.o),
+                       delay_slave_rst.eq(self.do_delay_slave_rst.o),
+                       delay_inc.eq(self.do_delay_inc.o),
+                       delay_ce.eq(self.do_delay_inc.o | self.do_delay_dec.o),
+               ]
+
+               sys_delay_master_pending = Signal()
+               self.sync += [
+                       If(self.do_delay_master_cal.i | self.do_delay_inc.i | self.do_delay_dec.i,
+                               sys_delay_master_pending.eq(1)
+                       ).Elif(self.delay_master_done.o,
+                               sys_delay_master_pending.eq(0)
+                       )
+               ]
+               sys_delay_slave_pending = Signal()
+               self.sync += [
+                       If(self.do_delay_slave_cal.i | self.do_delay_inc.i | self.do_delay_dec.i,
+                               sys_delay_slave_pending.eq(1)
+                       ).Elif(self.delay_slave_done.o,
+                               sys_delay_slave_pending.eq(0)
+                       )
+               ]
+
+               self.comb += [
+                       self.do_delay_master_cal.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[0]),
+                       self.do_delay_master_rst.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[1]),
+                       self.do_delay_slave_cal.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[2]),
+                       self.do_delay_slave_rst.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[3]),
+                       self.do_delay_inc.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[4]),
+                       self.do_delay_dec.i.eq(self._r_dly_ctl.re & self._r_dly_ctl.r[5]),
+                       self._r_dly_busy.status.eq(Cat(sys_delay_master_pending, sys_delay_slave_pending))
+               ]
+
+               # Phase detector control
+               self.specials += MultiReg(Cat(too_late, too_early), self._r_phase.status)
+               self.submodules.do_reset_lateness = PulseSynchronizer("sys", "pix2x")
+               self.comb += [
+                       reset_lateness.eq(self.do_reset_lateness.o),
+                       self.do_reset_lateness.i.eq(self._r_phase_reset.re)
+               ]
+
+               # 5:10 deserialization
+               dsr = Signal(10)
+               self.sync.pix2x += dsr.eq(Cat(dsr[5:], dsr2))
+               self.sync.pix += self.d.eq(dsr)
diff --git a/misoclib/video/dvisampler/debug.py b/misoclib/video/dvisampler/debug.py
new file mode 100644 (file)
index 0000000..de1d6ca
--- /dev/null
@@ -0,0 +1,46 @@
+from migen.fhdl.std import *
+from migen.genlib.fifo import AsyncFIFO
+from migen.genlib.record import layout_len
+from migen.bank.description import AutoCSR
+from migen.actorlib import structuring, dma_lasmi, spi
+
+from misoclib.video.dvisampler.edid import EDID
+from misoclib.video.dvisampler.clocking import Clocking
+from misoclib.video.dvisampler.datacapture import DataCapture
+
+class RawDVISampler(Module, AutoCSR):
+       def __init__(self, pads, asmiport):
+               self.submodules.edid = EDID(pads)
+               self.submodules.clocking = Clocking(pads)
+
+               invert = False
+               try:
+                       s = getattr(pads, "data0")
+               except AttributeError:
+                       s = getattr(pads, "data0_n")
+                       invert = True
+               self.submodules.data0_cap = DataCapture(8, invert)
+               self.comb += [
+                       self.data0_cap.pad.eq(s),
+                       self.data0_cap.serdesstrobe.eq(self.clocking.serdesstrobe)
+               ]
+
+               fifo = RenameClockDomains(AsyncFIFO(10, 256),
+                       {"write": "pix", "read": "sys"})
+               self.submodules += fifo
+               self.comb += [
+                       fifo.din.eq(self.data0_cap.d),
+                       fifo.we.eq(1)
+               ]
+
+               pack_factor = asmiport.hub.dw//16
+               self.submodules.packer = structuring.Pack([("word", 10), ("pad", 6)], pack_factor)
+               self.submodules.cast = structuring.Cast(self.packer.source.payload.layout, asmiport.hub.dw)
+               self.submodules.dma = spi.DMAWriteController(dma_lasmi.Writer(lasmim), spi.MODE_SINGLE_SHOT)
+               self.comb += [
+                       self.packer.sink.stb.eq(fifo.readable),
+                       fifo.re.eq(self.packer.sink.ack),
+                       self.packer.sink.word.eq(fifo.dout),
+                       self.packer.source.connect_flat(self.cast.sink),
+                       self.cast.source.connect_flat(self.dma.data)
+               ]
diff --git a/misoclib/video/dvisampler/decoding.py b/misoclib/video/dvisampler/decoding.py
new file mode 100644 (file)
index 0000000..fe79961
--- /dev/null
@@ -0,0 +1,24 @@
+from migen.fhdl.std import *
+from migen.genlib.record import Record
+
+from misoclib.video.dvisampler.common import control_tokens, channel_layout
+
+class Decoding(Module):
+       def __init__(self):
+               self.valid_i = Signal()
+               self.input = Signal(10)
+               self.valid_o = Signal()
+               self.output = Record(channel_layout)
+
+               ###
+
+               self.sync.pix += self.output.de.eq(1)
+               for i, t in enumerate(control_tokens):
+                       self.sync.pix += If(self.input == t,
+                               self.output.de.eq(0),
+                               self.output.c.eq(i)
+                       )
+               self.sync.pix += self.output.d[0].eq(self.input[0] ^ self.input[9])
+               for i in range(1, 8):
+                       self.sync.pix += self.output.d[i].eq(self.input[i] ^ self.input[i-1] ^ ~self.input[8])
+               self.sync.pix += self.valid_o.eq(self.valid_i)
diff --git a/misoclib/video/dvisampler/dma.py b/misoclib/video/dvisampler/dma.py
new file mode 100644 (file)
index 0000000..3bcb6e3
--- /dev/null
@@ -0,0 +1,140 @@
+from migen.fhdl.std import *
+from migen.genlib.fsm import FSM, NextState
+from migen.bank.description import *
+from migen.bank.eventmanager import *
+from migen.flow.actor import *
+from migen.actorlib import dma_lasmi
+
+# Slot status: EMPTY=0 LOADED=1 PENDING=2
+class _Slot(Module, AutoCSR):
+       def __init__(self, addr_bits, alignment_bits):
+               self.ev_source = EventSourceLevel()
+               self.address = Signal(addr_bits)
+               self.address_reached = Signal(addr_bits)
+               self.address_valid = Signal()
+               self.address_done = Signal()
+
+               self._r_status = CSRStorage(2, write_from_dev=True)
+               self._r_address = CSRStorage(addr_bits + alignment_bits, alignment_bits=alignment_bits, write_from_dev=True)
+
+               ###
+
+               self.comb += [
+                       self.address.eq(self._r_address.storage),
+                       self.address_valid.eq(self._r_status.storage[0]),
+                       self._r_status.dat_w.eq(2),
+                       self._r_status.we.eq(self.address_done),
+                       self._r_address.dat_w.eq(self.address_reached),
+                       self._r_address.we.eq(self.address_done),
+                       self.ev_source.trigger.eq(self._r_status.storage[1])
+               ]
+
+class _SlotArray(Module, AutoCSR):
+       def __init__(self, nslots, addr_bits, alignment_bits):
+               self.submodules.ev = EventManager()
+               self.address = Signal(addr_bits)
+               self.address_reached = Signal(addr_bits)
+               self.address_valid = Signal()
+               self.address_done = Signal()
+
+               ###
+
+               slots = [_Slot(addr_bits, alignment_bits) for i in range(nslots)]
+               for n, slot in enumerate(slots):
+                       setattr(self.submodules, "slot"+str(n), slot)
+                       setattr(self.ev, "slot"+str(n), slot.ev_source)
+               self.ev.finalize()
+
+               change_slot = Signal()
+               current_slot = Signal(max=nslots)
+               self.sync += If(change_slot, [If(slot.address_valid, current_slot.eq(n)) for n, slot in reversed(list(enumerate(slots)))])
+               self.comb += change_slot.eq(~self.address_valid | self.address_done)
+
+               self.comb += [
+                       self.address.eq(Array(slot.address for slot in slots)[current_slot]),
+                       self.address_valid.eq(Array(slot.address_valid for slot in slots)[current_slot])
+               ]
+               self.comb += [slot.address_reached.eq(self.address_reached) for slot in slots]
+               self.comb += [slot.address_done.eq(self.address_done & (current_slot == n)) for n, slot in enumerate(slots)]
+
+class DMA(Module):
+       def __init__(self, lasmim, nslots):
+               bus_aw = lasmim.aw
+               bus_dw = lasmim.dw
+               alignment_bits = bits_for(bus_dw//8) - 1
+
+               fifo_word_width = 24*bus_dw//32
+               self.frame = Sink([("sof", 1), ("pixels", fifo_word_width)])
+               self._r_frame_size = CSRStorage(bus_aw + alignment_bits, alignment_bits=alignment_bits)
+               self.submodules._slot_array = _SlotArray(nslots, bus_aw, alignment_bits)
+               self.ev = self._slot_array.ev
+
+               ###
+
+               # address generator + maximum memory word count to prevent DMA buffer overrun
+               reset_words = Signal()
+               count_word = Signal()
+               last_word = Signal()
+               current_address = Signal(bus_aw)
+               mwords_remaining = Signal(bus_aw)
+               self.comb += [
+                       self._slot_array.address_reached.eq(current_address),
+                       last_word.eq(mwords_remaining == 1)
+               ]
+               self.sync += [
+                       If(reset_words,
+                               current_address.eq(self._slot_array.address),
+                               mwords_remaining.eq(self._r_frame_size.storage)
+                       ).Elif(count_word,
+                               current_address.eq(current_address + 1),
+                               mwords_remaining.eq(mwords_remaining - 1)
+                       )
+               ]
+
+               # 24bpp -> 32bpp
+               memory_word = Signal(bus_dw)
+               pixbits = []
+               for i in range(bus_dw//32):
+                       for j in range(3):
+                               b = (i*3+j)*8
+                               pixbits.append(self.frame.pixels[b+6:b+8])
+                               pixbits.append(self.frame.pixels[b:b+8])
+                       pixbits.append(0)
+                       pixbits.append(0)
+               self.comb += memory_word.eq(Cat(*pixbits))
+
+               # bus accessor
+               self.submodules._bus_accessor = dma_lasmi.Writer(lasmim)
+               self.comb += [
+                       self._bus_accessor.address_data.a.eq(current_address),
+                       self._bus_accessor.address_data.d.eq(memory_word)
+               ]
+
+               # control FSM
+               fsm = FSM()
+               self.submodules += fsm
+
+               fsm.act("WAIT_SOF",
+                       reset_words.eq(1),
+                       self.frame.ack.eq(~self._slot_array.address_valid | ~self.frame.sof),
+                       If(self._slot_array.address_valid & self.frame.sof & self.frame.stb, NextState("TRANSFER_PIXELS"))
+               )
+               fsm.act("TRANSFER_PIXELS",
+                       self.frame.ack.eq(self._bus_accessor.address_data.ack),
+                       If(self.frame.stb,
+                               self._bus_accessor.address_data.stb.eq(1),
+                               If(self._bus_accessor.address_data.ack,
+                                       count_word.eq(1),
+                                       If(last_word, NextState("EOF"))
+                               )
+                       )
+               )
+               fsm.act("EOF",
+                       If(~self._bus_accessor.busy,
+                               self._slot_array.address_done.eq(1),
+                               NextState("WAIT_SOF")
+                       )
+               )
+
+       def get_csrs(self):
+               return [self._r_frame_size] + self._slot_array.get_csrs()
diff --git a/misoclib/video/dvisampler/edid.py b/misoclib/video/dvisampler/edid.py
new file mode 100644 (file)
index 0000000..f2e6b2e
--- /dev/null
@@ -0,0 +1,189 @@
+from migen.fhdl.std import *
+from migen.fhdl.specials import Tristate
+from migen.genlib.cdc import MultiReg
+from migen.genlib.fsm import FSM, NextState
+from migen.genlib.misc import chooser
+from migen.bank.description import CSRStorage, CSRStatus, AutoCSR
+
+_default_edid = [
+       0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x3D, 0x17, 0x32, 0x12, 0x2A, 0x6A, 0xBF, 0x00,
+       0x05, 0x17, 0x01, 0x03, 0x80, 0x28, 0x1E, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+       0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xB2, 0x0C, 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x18, 0x88,
+       0x36, 0x00, 0x28, 0x1E, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x4D, 0x31, 0x20,
+       0x44, 0x56, 0x49, 0x20, 0x6D, 0x69, 0x78, 0x65, 0x72, 0x0A, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34,
+]
+
+class EDID(Module, AutoCSR):
+       def __init__(self, pads, default=_default_edid):
+               self._r_hpd_notif = CSRStatus()
+               self._r_hpd_en = CSRStorage()
+               self.specials.mem = Memory(8, 128, init=default)
+
+               ###
+
+               # HPD
+               if hasattr(pads, "hpd_notif"):
+                       self.specials += MultiReg(pads.hpd_notif, self._r_hpd_notif.status)
+               else:
+                       self.comb += self._r_hpd_notif.status.eq(1)
+               if hasattr(pads, "hpd_en"):
+                       self.comb += pads.hpd_en.eq(self._r_hpd_en.storage)
+
+               # EDID
+               scl_raw = Signal()
+               sda_i = Signal()
+               sda_drv = Signal()
+               _sda_drv_reg = Signal()
+               _sda_i_async = Signal()
+               self.sync += _sda_drv_reg.eq(sda_drv)
+               self.specials += [
+                       MultiReg(pads.scl, scl_raw),
+                       Tristate(pads.sda, 0, _sda_drv_reg, _sda_i_async),
+                       MultiReg(_sda_i_async, sda_i)
+               ]
+
+               scl_i = Signal()
+               samp_count = Signal(6)
+               samp_carry = Signal()
+               self.sync += [
+                       Cat(samp_count, samp_carry).eq(samp_count + 1),
+                       If(samp_carry, scl_i.eq(scl_raw))
+               ]
+
+               scl_r = Signal()
+               sda_r = Signal()
+               scl_rising = Signal()
+               sda_rising = Signal()
+               sda_falling = Signal()
+               self.sync += [
+                       scl_r.eq(scl_i),
+                       sda_r.eq(sda_i)
+               ]
+               self.comb += [
+                       scl_rising.eq(scl_i & ~scl_r),
+                       sda_rising.eq(sda_i & ~sda_r),
+                       sda_falling.eq(~sda_i & sda_r)
+               ]
+
+               start = Signal()
+               self.comb += start.eq(scl_i & sda_falling)
+
+               din = Signal(8)
+               counter = Signal(max=9)
+               self.sync += [
+                       If(start, counter.eq(0)),
+                       If(scl_rising,
+                               If(counter == 8,
+                                       counter.eq(0)
+                               ).Else(
+                                       counter.eq(counter + 1),
+                                       din.eq(Cat(sda_i, din[:7]))
+                               )
+                       )
+               ]
+
+               is_read = Signal()
+               update_is_read = Signal()
+               self.sync += If(update_is_read, is_read.eq(din[0]))
+
+               offset_counter = Signal(max=128)
+               oc_load = Signal()
+               oc_inc = Signal()
+               self.sync += [
+                       If(oc_load,
+                               offset_counter.eq(din)
+                       ).Elif(oc_inc,
+                               offset_counter.eq(offset_counter + 1)
+                       )
+               ]
+               rdport = self.mem.get_port()
+               self.specials += rdport
+               self.comb += rdport.adr.eq(offset_counter)
+               data_bit = Signal()
+
+               zero_drv = Signal()
+               data_drv = Signal()
+               self.comb += If(zero_drv, sda_drv.eq(1)).Elif(data_drv, sda_drv.eq(~data_bit))
+
+               data_drv_en = Signal()
+               data_drv_stop = Signal()
+               self.sync += If(data_drv_en, data_drv.eq(1)).Elif(data_drv_stop, data_drv.eq(0))
+               self.sync += If(data_drv_en, chooser(rdport.dat_r, counter, data_bit, 8, reverse=True))
+
+               fsm = FSM()
+               self.submodules += fsm
+
+               fsm.act("WAIT_START")
+               fsm.act("RCV_ADDRESS",
+                       If(counter == 8,
+                               If(din[1:] == 0x50,
+                                       update_is_read.eq(1),
+                                       NextState("ACK_ADDRESS0")
+                               ).Else(
+                                       NextState("WAIT_START")
+                               )
+                       )
+               )
+               fsm.act("ACK_ADDRESS0",
+                       If(~scl_i, NextState("ACK_ADDRESS1"))
+               )
+               fsm.act("ACK_ADDRESS1",
+                       zero_drv.eq(1),
+                       If(scl_i, NextState("ACK_ADDRESS2"))
+               )
+               fsm.act("ACK_ADDRESS2",
+                       zero_drv.eq(1),
+                       If(~scl_i,
+                               If(is_read,
+                                       NextState("READ")
+                               ).Else(
+                                       NextState("RCV_OFFSET")
+                               )
+                       )
+               )
+
+               fsm.act("RCV_OFFSET",
+                       If(counter == 8,
+                               oc_load.eq(1),
+                               NextState("ACK_OFFSET0")
+                       )
+               )
+               fsm.act("ACK_OFFSET0",
+                       If(~scl_i, NextState("ACK_OFFSET1"))
+               )
+               fsm.act("ACK_OFFSET1",
+                       zero_drv.eq(1),
+                       If(scl_i, NextState("ACK_OFFSET2"))
+               )
+               fsm.act("ACK_OFFSET2",
+                       zero_drv.eq(1),
+                       If(~scl_i, NextState("RCV_ADDRESS"))
+               )
+
+               fsm.act("READ",
+                       If(~scl_i,
+                               If(counter == 8,
+                                       data_drv_stop.eq(1),
+                                       NextState("ACK_READ")
+                               ).Else(
+                                       data_drv_en.eq(1)
+                               )
+                       )
+               )
+               fsm.act("ACK_READ",
+                       If(scl_rising,
+                               oc_inc.eq(1),
+                               If(sda_i,
+                                       NextState("WAIT_START")
+                               ).Else(
+                                       NextState("READ")
+                               )
+                       )
+               )
+
+               for state in fsm.actions.keys():
+                       fsm.act(state, If(start, NextState("RCV_ADDRESS")))
+                       fsm.act(state, If(~self._r_hpd_en.storage, NextState("WAIT_START")))
diff --git a/misoclib/video/dvisampler/wer.py b/misoclib/video/dvisampler/wer.py
new file mode 100644 (file)
index 0000000..bb8553c
--- /dev/null
@@ -0,0 +1,59 @@
+from migen.fhdl.std import *
+from migen.bank.description import *
+from migen.genlib.misc import optree
+from migen.genlib.cdc import PulseSynchronizer
+
+from misoclib.video.dvisampler.common import control_tokens
+
+class WER(Module, AutoCSR):
+       def __init__(self, period_bits=24):
+               self.data = Signal(10)
+               self._r_update = CSR()
+               self._r_value = CSRStatus(period_bits)
+
+               ###
+
+               # pipeline stage 1
+               # we ignore the 10th (inversion) bit, as it is independent of the transition minimization
+               data_r = Signal(9)
+               self.sync.pix += data_r.eq(self.data[:9])
+
+               # pipeline stage 2
+               transitions = Signal(8)
+               self.comb += [transitions[i].eq(data_r[i] ^ data_r[i+1]) for i in range(8)]
+               transition_count = Signal(max=9)
+               self.sync.pix += transition_count.eq(optree("+", [transitions[i] for i in range(8)]))
+
+               is_control = Signal()
+               self.sync.pix += is_control.eq(optree("|", [data_r == ct for ct in control_tokens]))
+
+               # pipeline stage 3
+               is_error = Signal()
+               self.sync.pix += is_error.eq((transition_count > 4) & ~is_control)
+
+               # counter
+               period_counter = Signal(period_bits)
+               period_done = Signal()
+               self.sync.pix += Cat(period_counter, period_done).eq(period_counter + 1)
+
+               wer_counter = Signal(period_bits)
+               wer_counter_r = Signal(period_bits)
+               wer_counter_r_updated = Signal()
+               self.sync.pix += [
+                       wer_counter_r_updated.eq(period_done),
+                       If(period_done,
+                               wer_counter_r.eq(wer_counter),
+                               wer_counter.eq(0)
+                       ).Elif(is_error,
+                               wer_counter.eq(wer_counter + 1)
+                       )
+               ]
+
+               # sync to system clock domain
+               wer_counter_sys = Signal(period_bits)
+               self.submodules.ps_counter = PulseSynchronizer("pix", "sys")
+               self.comb += self.ps_counter.i.eq(wer_counter_r_updated)
+               self.sync += If(self.ps_counter.o, wer_counter_sys.eq(wer_counter_r))
+
+               # register interface
+               self.sync += If(self._r_update.re, self._r_value.status.eq(wer_counter_sys))