From: Florent Kermarrec Date: Sun, 1 Mar 2015 09:01:23 +0000 (+0100) Subject: video: reintegrate dvisampler from mixxeo (DVI/HDMI interfaces are common in today... X-Git-Tag: 24jan2021_ls180~2554 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=1b7f8d0439b0dc4173d026b62277e0d967a05c5e;p=litex.git video: reintegrate dvisampler from mixxeo (DVI/HDMI interfaces are common in today's SoCs) --- diff --git a/misoclib/video/dvisampler/__init__.py b/misoclib/video/dvisampler/__init__.py new file mode 100644 index 00000000..aaf28d05 --- /dev/null +++ b/misoclib/video/dvisampler/__init__.py @@ -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 index 00000000..0b70806a --- /dev/null +++ b/misoclib/video/dvisampler/analysis.py @@ -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 index 00000000..2aeb6dbd --- /dev/null +++ b/misoclib/video/dvisampler/chansync.py @@ -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 index 00000000..c33c44c8 --- /dev/null +++ b/misoclib/video/dvisampler/charsync.py @@ -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 index 00000000..36f91b25 --- /dev/null +++ b/misoclib/video/dvisampler/clocking.py @@ -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 index 00000000..7fb9a420 --- /dev/null +++ b/misoclib/video/dvisampler/common.py @@ -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 index 00000000..d7888433 --- /dev/null +++ b/misoclib/video/dvisampler/datacapture.py @@ -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 index 00000000..de1d6cac --- /dev/null +++ b/misoclib/video/dvisampler/debug.py @@ -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 index 00000000..fe799611 --- /dev/null +++ b/misoclib/video/dvisampler/decoding.py @@ -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 index 00000000..3bcb6e35 --- /dev/null +++ b/misoclib/video/dvisampler/dma.py @@ -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 index 00000000..f2e6b2e5 --- /dev/null +++ b/misoclib/video/dvisampler/edid.py @@ -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 index 00000000..bb8553cc --- /dev/null +++ b/misoclib/video/dvisampler/wer.py @@ -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))