def _synchronize_(self, m, output, o_domain="sync", stages=2): """ Creates a synchronized copy of this interface's I/O. """ # Synchronize our inputs... m.submodules += [ FFSynchronizer(self.sck, output.sck, o_domain=o_domain, stages=stages), FFSynchronizer(self.sdi, output.sdi, o_domain=o_domain, stages=stages), FFSynchronizer(self.cs, output.cs, o_domain=o_domain, stages=stages), ] # ... and connect our output directly through. m.d.comb += self.sdo.eq(output.sdo)
def elaborate(self, platform): m = Module() self.elaborate_read(m) self.elaborate_write(m) mem = Memory(width=16, depth=256) w_pointer = Signal() r_pointer = Signal() # Connect read and write sides to the memory # Addresses from r_addr, w_addr and pointers # NOTE: transparent=False is required or BRAM will not be inferred m.submodules.rp = rp = mem.read_port(domain=self.read_domain, transparent=False) m.d.comb += rp.addr.eq(Cat(self.read.addr, ~r_pointer)) m.d.comb += self.read.data.eq(rp.data) m.submodules.wp = wp = mem.write_port(domain=self.write_domain) m.d.comb += wp.en.eq(self.write.en) m.d.comb += wp.addr.eq(Cat(self.write.addr, w_pointer)) m.d.comb += wp.data.eq(self.write.data) # Handle read pointer toggle and send it cross domain to write pointer with m.If(self.read.toggle): m.d[self.read_domain] += r_pointer.eq(~r_pointer) m.submodules.pointer = FFSynchronizer(r_pointer, w_pointer, o_domain=self.write_domain, stages=3) last_w_pointer = Signal() m.d[self.write_domain] += last_w_pointer.eq(w_pointer) m.d.comb += self.write.ready.eq(w_pointer != last_w_pointer) return m
def elaborate(self, platform): m = Module() memory = m.submodules.memory = self.memory address_stream = PacketizedStream(bits_for(self.max_packet_size)) with m.If(~self.done): m.d.comb += address_stream.valid.eq(1) m.d.comb += address_stream.last.eq(self.read_ptr == self.packet_length) m.d.comb += address_stream.payload.eq(self.read_ptr) with m.If(address_stream.ready): m.d.sync += self.read_ptr.eq(self.read_ptr + 1) m.d.sync += self.done.eq(self.read_ptr == self.packet_length) reset = Signal() m.submodules += FFSynchronizer(self.reset, reset) with m.If(Changed(m, reset)): m.d.sync += self.read_ptr.eq(0) m.d.sync += self.done.eq(0) reader = m.submodules.reader = StreamMemoryReader(address_stream, memory) buffer = m.submodules.buffer = StreamBuffer(reader.output) m.d.comb += self.output.connect_upstream(buffer.output) return m
def elaborate(self, platform): m = Module() board_spi = platform.request("debug_spi") # Create a set of registers, and expose them over SPI. spi_registers = SPIRegisterInterface( default_read_value=0x4C554E41) #default read = u'LUNA' m.submodules.spi_registers = spi_registers # Fill in some example registers. # (Register 0 is reserved for size autonegotiation). spi_registers.add_read_only_register(1, read=0xc001cafe) led_reg = spi_registers.add_register(2, size=6, name="leds") spi_registers.add_read_only_register(3, read=0xdeadbeef) # ... and tie our LED register to our LEDs. led_out = Cat( [platform.request("led", i, dir="o") for i in range(0, 6)]) m.d.comb += led_out.eq(led_reg) # # Structural connections. # sck = Signal() sdi = Signal() sdo = Signal() cs = Signal() # # Synchronize each of our I/O SPI signals, where necessary. # m.submodules += FFSynchronizer(board_spi.sck, sck) m.submodules += FFSynchronizer(board_spi.sdi, sdi) m.submodules += FFSynchronizer(board_spi.cs, cs) m.d.comb += board_spi.sdo.eq(sdo) # Connect our register interface to our board SPI. m.d.comb += [ spi_registers.sck.eq(sck), spi_registers.sdi.eq(sdi), sdo.eq(spi_registers.sdo), spi_registers.cs.eq(cs) ] return m
def elaborate(self, platform): m = Module() if self.has_tx: m.d.comb += self.tx_t.oe.eq(1) if self.invert_tx: m.d.comb += self.tx_t.o.eq(~self.tx_o) else: m.d.comb += self.tx_t.o.eq(self.tx_o) if self.has_rx: if self.invert_rx: m.submodules += FFSynchronizer(~self.rx_t.i, self.rx_i, reset=1) else: m.submodules += FFSynchronizer(self.rx_t.i, self.rx_i, reset=1) return m
def elaborate(self, platform): m = Module() # Keep track of how many cycles we'll keep our PHY in reset. # This is larger than any requirement, in order to work with a broad swathe of PHYs, # in case a PHY other than the TUSB1310A ever makes it to the market. cycles_in_reset = int(5e-6 * 50e6) cycles_left_in_reset = Signal(range(cycles_in_reset), reset=cycles_in_reset - 1) # Create versions of our phy_status signals that are observable: # 1) as an asynchronous inputs for startup pulses # 2) as a single-cycle pulse, for power-state-change notifications phy_status = Signal() # Convert our PHY status signal into a simple, sync-domain signal. m.submodules += FFSynchronizer(self.phy_status[0] | self.phy_status[1], phy_status), with m.FSM(): # STARTUP_RESET -- post configuration, we'll reset the PIPE PHY. # This is distinct from the PHY's built-in power-on-reset, as we run this # on every FPGA configuration. with m.State("STARTUP_RESET"): m.d.comb += [ self.reset.eq(1), ] # Once we've extended past a reset time, we can move on. m.d.sync += cycles_left_in_reset.eq(cycles_left_in_reset - 1) with m.If(cycles_left_in_reset == 0): m.next = "DETECT_PHY_STARTUP" # DETECT_PHY_STARTUP -- post-reset, the PHY should drive its status line high. # We'll wait for this to happen, so we can track the PHY's progress. with m.State("DETECT_PHY_STARTUP"): with m.If(phy_status): m.next = "WAIT_FOR_STARTUP" # WAIT_FOR_STARTUP -- we've now detected that the PHY is starting up. # We'll wait for that startup signal to be de-asserted, indicating that the PHY is ready. with m.State("WAIT_FOR_STARTUP"): # For now, we'll start up in P0. This will change once we implement proper RxDetect. with m.If(~phy_status): m.next = "READY" # READY -- our PHY is all started up and ready for use. # For now, we'll remain here until we're reset. with m.State("READY"): m.d.comb += self.ready.eq(1) return m
def elaborate(self, platform): m = Module() ddr_reset = Signal() m.submodules += FFSynchronizer(self.reset, ddr_reset, o_domain=self.ddr_domain) m.d.comb += self.pins.o_clk.eq(ClockSignal(self.ddr_domain)) m.d.comb += self.pins.oe.eq(~self.reset) hs_fifo = m.submodules.hs_fifo = BufferedAsyncStreamFIFO( self.input, 8, o_domain=self.ddr_domain) hs_payload = Signal(8) was_valid = Signal() m.submodules += FFSynchronizer(~was_valid, self.is_idle) with m.FSM(domain=self.ddr_domain): for i in range(4): with m.State(f"{i}"): if i == 3: with m.If(hs_fifo.output.valid): m.d[self.ddr_domain] += hs_payload.eq( hs_fifo.output.payload) m.d[self.ddr_domain] += self.idle.eq( Repl(hs_fifo.output.payload[7], 8)) m.d[self.ddr_domain] += was_valid.eq(1) with m.Else(): m.d[self.ddr_domain] += hs_payload.eq(self.idle) m.d[self.ddr_domain] += was_valid.eq(0) m.d.comb += hs_fifo.output.ready.eq(was_valid) m.d.comb += self.pins.o0.eq( fake_differential(hs_payload[i * 2 + 0])) m.d.comb += self.pins.o1.eq( fake_differential(hs_payload[i * 2 + 1])) m.next = f"{(i + 1) % 4}" return ResetInserter({ self.ddr_domain: ddr_reset, "sync": self.reset })(m)
def elaborate(self, platform): m = Module() pads = self._pads m.d.comb += [ pads.sbwtck_t.oe.eq(1), pads.sbwtck_t.o.eq(self.sbwtck), pads.sbwtdio_t.oe.eq(~self.sbwtd_z), pads.sbwtdio_t.o.eq(self.sbwtd_o), ] m.submodules += [ FFSynchronizer(pads.sbwtdio_t.i, self.sbwtd_i), ] return m
def elaborate(self, platform): m = Module() pads = self._pads m.submodules += [ FFSynchronizer(pads.tck_t.i, self.tck), FFSynchronizer(pads.tms_t.i, self.tms), FFSynchronizer(pads.tdi_t.i, self.tdi), FFSynchronizer(pads.tdo_t.i, self.tdo), FFSynchronizer(pads.srst_t.i, self.srst), ] m.d.comb += [ pads.tck_t.oe.eq(0), pads.tms_t.oe.eq(0), pads.tdi_t.oe.eq(0), pads.tdo_t.oe.eq(0), pads.srst_t.oe.eq(0), self.states.tck.eq(self.tck), self.states.tms.eq(self.tms), self.states.tdi.eq(self.tdi), self.states.tdo.eq(self.tdo), self.states.srst.eq(self.srst), ] return m
def elaborate(self, platform): m = Module() board_spi = platform.request("debug_spi") # Use our command interface. m.submodules.interface = self.interface sck = Signal() sdi = Signal() sdo = Signal() cs = Signal() # # Synchronize each of our I/O SPI signals, where necessary. # m.submodules += FFSynchronizer(board_spi.sck, sck) m.submodules += FFSynchronizer(board_spi.sdi, sdi) m.submodules += FFSynchronizer(board_spi.cs, cs) m.d.comb += board_spi.sdo.eq(sdo) # Connect our command interface to our board SPI. m.d.comb += [ self.interface.sck.eq(sck), self.interface.sdi.eq(sdi), sdo.eq(self.interface.sdo), self.interface.cs .eq(cs) ] # Turn on a single LED, just to show something's running. led = platform.request('led', 0) m.d.comb += led.eq(1) # Echo back the last received data. m.d.comb += self.interface.word_out.eq(self.interface.word_in) return m
def elaborate(self, platform): m = Module() m.d.comb += [ self.pads.clk_t.oe.eq(1), self.pads.clk_t.o.eq(self.clk), ] m.submodules += [ FFSynchronizer(self.pads.din_t.i, self.din), ] if hasattr(self.pads, "osc_t"): m.d.comb += [ self.pads.osc_t.oe.eq(1), self.pads.osc_t.o.eq(self.osc), ] return m
def elaborate(self, platform): m = Module() m.submodules += self.analyzer pins_i = Signal.like(self.pads.i_t.i) pins_r = Signal.like(self.pads.i_t.i) m.submodules += FFSynchronizer(self.pads.i_t.i, pins_i) m.d.sync += pins_r.eq(pins_i) m.d.comb += [ self.event_source.data.eq(pins_i), self.event_source.trigger.eq(pins_i != pins_r) ] return m
def elaborate(self, platform): m = Module() m.d.comb += [ self.pads.clock_t.o.eq(0), self.pads.clock_t.oe.eq(~self.clock_o), self.pads.data_t.o.eq(0), self.pads.data_t.oe.eq(~self.data_o), ] m.submodules += [ FFSynchronizer(self.pads.clock_t.i, self.clock_i, reset=1), FFSynchronizer(self.pads.data_t.i, self.data_i, reset=1), ] clock_s = Signal(reset=1) clock_r = Signal(reset=1) m.d.sync += [ clock_s.eq(self.clock_i), clock_r.eq(clock_s), self.falling.eq(clock_r & ~clock_s), self.rising.eq(~clock_r & clock_s), ] return m
def elaborate(self, platform): m = Module() timer = Signal.like(self.divisor) shreg = Record(_wire_layout(len(self.data), self._parity)) bitno = Signal(range(len(shreg))) if self._pins is not None: m.submodules += FFSynchronizer(self._pins.rx.i, self.i, reset=1) with m.FSM() as fsm: with m.State("IDLE"): with m.If(~self.i): m.d.sync += [ bitno.eq(len(shreg) - 1), timer.eq(self.divisor >> 1), ] m.next = "BUSY" with m.State("BUSY"): with m.If(timer != 0): m.d.sync += timer.eq(timer - 1) with m.Else(): m.d.sync += [ shreg.eq(Cat(shreg[1:], self.i)), bitno.eq(bitno - 1), timer.eq(self.divisor - 1), ] with m.If(bitno == 0): m.next = "DONE" with m.State("DONE"): with m.If(self.ack): m.d.sync += [ self.data.eq(shreg.data), self.err.frame.eq(~( (shreg.start == 0) & (shreg.stop == 1))), self.err.parity.eq( ~(shreg.parity == _compute_parity_bit( shreg.data, self._parity))), ] m.d.sync += self.err.overflow.eq(~self.ack) m.next = "IDLE" with m.If(self.ack): m.d.sync += self.rdy.eq(fsm.ongoing("DONE")) return m
def elaborate(self, platform: Platform) -> Module: m = Module() # Do TX CDC # FFSynchronizer if False: m.submodules += FFSynchronizer(Cat(self.tx_symbol, self.tx_set_disp, self.tx_disp, self.tx_e_idle), Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp, self.__lane.tx_disp, self.__lane.tx_e_idle), o_domain="tx", stages=4) # No CDC if False: m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp, self.__lane.tx_disp, self.__lane.tx_e_idle).eq( Cat(self.tx_symbol, self.tx_set_disp, self.tx_disp, self.tx_e_idle)) # AsyncFIFOBuffered if True: tx_fifo = m.submodules.tx_fifo = AsyncFIFOBuffered(width=24, depth=10, r_domain="tx", w_domain="rx") m.d.comb += tx_fifo.w_data.eq(Cat(self.tx_symbol, self.tx_set_disp, self.tx_disp, self.tx_e_idle)) m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp, self.__lane.tx_disp, self.__lane.tx_e_idle).eq(tx_fifo.r_data) m.d.comb += tx_fifo.r_en.eq(1) m.d.comb += tx_fifo.w_en.eq(1) # Testing symbols if False: m.d.comb += self.__lane.tx_symbol.eq(Cat(Ctrl.COM, D(10, 2))) self.slip = SymbolSlip(symbol_size=10, word_size=self.__lane.ratio, comma=Cat(Ctrl.COM, 1)) m.submodules += self.slip m.d.comb += [ self.slip.en.eq(self.rx_align), self.slip.i.eq(Cat( (self.__lane.rx_symbol.word_select(n, 9), self.__lane.rx_valid[n]) for n in range(self.__lane.ratio) )), self.rx_symbol.eq(Cat( Part(self.slip.o, 10 * n, 9) for n in range(self.__lane.ratio) )), self.rx_valid.eq(Cat( self.slip.o[10 * n + 9] for n in range(self.__lane.ratio) )), ] return m
def elaborate(self, platform): m = Module() update = Signal() freeze = Signal() # DDRDLLA instance ------------------------------------------------------------------------- _lock = Signal() lock = Signal() lock_d = Signal() m.submodules += Instance("DDRDLLA", i_CLK=ClockSignal("sync2x"), i_RST=ResetSignal("init"), i_UDDCNTLN=~update, i_FREEZE=freeze, o_DDRDEL=self.delay, o_LOCK=_lock) m.submodules += FFSynchronizer(_lock, lock, o_domain="init") m.d.init += lock_d.eq(lock) # DDRDLLA/DDQBUFM/ECLK initialization sequence --------------------------------------------- t = 8 # in cycles tl = Timeline([ (1 * t, [freeze.eq(1)]), # Freeze DDRDLLA (2 * t, [self.stop.eq(1)]), # Stop ECLK domain (3 * t, [self.reset.eq(1)]), # Reset ECLK domain (4 * t, [self.reset.eq(0)]), # Release ECLK domain reset (5 * t, [self.stop.eq(0)]), # Release ECLK domain stop (6 * t, [freeze.eq(0)]), # Release DDRDLLA freeze (7 * t, [self.pause.eq(1)]), # Pause DQSBUFM (8 * t, [update.eq(1)]), # Update DDRDLLA (9 * t, [update.eq(0)]), # Release DDRDMMA update (10 * t, [self.pause.eq(0)]), # Release DQSBUFM pause ]) m.d.comb += tl.trigger.eq( lock & ~lock_d) # Trigger timeline on lock rising edge m.submodules += DomainRenamer("init")(tl) return m
def elaborate(self, platform): m = Module() cart = [] # synchronize the quite asynchronous SNES bus signals to our domain. # this seems to work okay, but the main sd2snes uses more sophisticated # techniques to determine the bus state and we may have to steal some. for fi, field in enumerate(self.cart_signals._fields): cart_signal = self.cart_signals[fi] sync_signal = Signal(len(cart_signal), name=field) m.submodules["ffsync_"+field] = \ FFSynchronizer(cart_signal, sync_signal) cart.append(sync_signal) cart = self.cart_signals._make(cart) # keep track of the SNES clock cycle so the system can time events cycle_counter = Signal(32) last_clock = Signal() m.d.sync += last_clock.eq(cart.clock) with m.If(~last_clock & cart.clock): m.d.sync += cycle_counter.eq(cycle_counter + 1) m.d.comb += self.o_cycle_count.eq(cycle_counter) # for now we just need to monitor reads, so we look for when the read # line is asserted. we assume the address is valid at that point but tbh # we're not 100% sure. last_rd = Signal() snes_read_started = Signal() m.d.sync += last_rd.eq(cart.rd) m.d.comb += snes_read_started.eq(last_rd & ~cart.rd) m.d.comb += [ self.o_valid.eq(snes_read_started), self.o_addr.eq(cart.addr), self.o_data.eq(cart.data), ] return m
def elaborate(self, platform: BetaPlatform): m = Module() m.submodules += self.frame_req platform.ps7.fck_domain(requested_frequency=100e6) sensor = platform.request("sensor") platform.ps7.fck_domain(250e6, "sensor_clk") m.d.comb += sensor.lvds_clk.eq(ClockSignal("sensor_clk")) m.d.comb += sensor.reset.eq(self.sensor_reset) m.d.comb += [ sensor.frame_req.eq(self.frame_req.pulse), sensor.t_exp1.eq(0), sensor.t_exp2.eq(0), ] m.submodules.sensor_spi = Cmv12kSpi(platform.request("sensor_spi")) sensor_rx = m.submodules.sensor_rx = Cmv12kRx(sensor) stats = m.submodules.stats = DomainRenamer("cmv12k_hword")(Stats()) m.d.comb += [ stats.ctrl_lane.eq(sensor_rx.phy.output[-1]), stats.ctrl_valid.eq(sensor_rx.phy.output_valid), ] add_ila(platform, trace_length=2048, domain="cmv12k_hword", after_trigger=2048-768) probe(m, sensor_rx.phy.output_valid, name="output_valid") for lane in range(32): probe(m, sensor_rx.phy.output[lane], name=f"lane_{lane:02d}") probe(m, sensor_rx.phy.output[-1], name="lane_ctrl") capture_pattern_end = Signal() m.submodules += FFSynchronizer(self.capture_pattern_end, capture_pattern_end) trigger(m, Mux(capture_pattern_end, stats.frame_end_trigger, stats.frame_start_trigger)) return m
def elaborate(self, platform): m = Module() memory = m.submodules.memory = self.memory write_port = m.submodules.write_port = memory.write_port(domain="sync") with m.If(~self.packet_done & (self.write_pointer < self.max_packet_size)): m.d.comb += self.input.ready.eq(1) with m.If(self.input.valid): m.d.comb += write_port.en.eq(1) m.d.comb += write_port.addr.eq(self.write_pointer) m.d.comb += write_port.data.eq(self.input.payload) m.d.sync += self.write_pointer.eq(self.write_pointer + 1) with m.If(self.input.last): m.d.sync += self.packet_done.eq(1) reset = Signal() m.submodules += FFSynchronizer(self.reset, reset) with m.If(Changed(m, reset)): m.d.sync += self.write_pointer.eq(0) m.d.sync += self.packet_done.eq(0) return m
def elaborate(self, platform): m = Module() if hasattr(platform, "ila_error"): raise platform.ila_error after_trigger = Signal.like(self.after_trigger) m.submodules += FFSynchronizer(self.after_trigger, after_trigger) assert hasattr(platform, "trigger"), "No trigger in Design" trigger = Signal() m.submodules += FFSynchronizer(platform.trigger, trigger) assert hasattr(platform, "probes"), "No probes in Design" platform_probes = list(platform.probes.items()) probes = [(k, Signal.like(signal)) for k, (signal, decoder) in platform_probes] for (_, (i, _)), (_, o) in zip(platform_probes, probes): m.submodules += FFSynchronizer(i, o) self.probes = [(k, (len(signal), decoder)) for k, (signal, decoder) in platform_probes] probe_bits = sum(length for name, (length, decoder) in self.probes) print(f"ila: using {probe_bits} probe bits") self.mem = m.submodules.mem = SocMemory( width=ceil(probe_bits / 32) * 32, depth=self.trace_length, soc_write=False, attrs=dict(syn_ramstyle="block_ram")) write_port = m.submodules.write_port = self.mem.write_port( domain="sync") since_reset = Signal(range(self.trace_length + 1)) with m.If(self.running): with m.If(self.write_ptr < (self.trace_length - 1)): m.d.sync += self.write_ptr.eq(self.write_ptr + 1) with m.Else(): m.d.sync += self.write_ptr.eq(0) m.d.comb += write_port.addr.eq(self.write_ptr) m.d.comb += write_port.en.eq(1) m.d.comb += write_port.data.eq(Cat([s for _, s in probes])) # we wait trace_length cycles to be sure to overwrite the whole buffer at least once # and avoid confusing results with m.If(since_reset < self.trace_length): m.d.sync += since_reset.eq(since_reset + 1) with m.If(self.trigger_since == 0): with m.If(trigger & (since_reset > self.trace_length - 1)): m.d.sync += self.trigger_since.eq(1) with m.Else(): with m.If(self.trigger_since < (after_trigger - 1)): m.d.sync += self.trigger_since.eq(self.trigger_since + 1) with m.Else(): m.d.sync += self.running.eq(0) m.d.sync += self.initial_.eq(0) with m.Else(): reset = Signal() m.submodules += FFSynchronizer(self.reset, reset) with m.If(Changed(m, reset)): m.d.sync += self.running.eq(1) m.d.sync += self.trigger_since.eq(0) m.d.sync += self.write_ptr.eq(0) m.d.sync += since_reset.eq(0) return m
def elaborate(self, platform): m = Module() if self.depth == 0: m.d.comb += [ self.w_rdy.eq(0), self.r_rdy.eq(0), ] return m # The design of this queue is the "style #2" from Clifford E. Cummings' paper "Simulation # and Synthesis Techniques for Asynchronous FIFO Design": # http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf do_write = self.w_rdy & self.w_en do_read = self.r_rdy & self.r_en # TODO: extract this pattern into lib.cdc.GrayCounter produce_w_bin = Signal(self._ctr_bits) produce_w_nxt = Signal(self._ctr_bits) m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write) m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt) # Note: Both read-domain counters must be reset_less (see comments below) consume_r_bin = Signal(self._ctr_bits, reset_less=True) consume_r_nxt = Signal(self._ctr_bits) m.d.comb += consume_r_nxt.eq(consume_r_bin + do_read) m.d[self._r_domain] += consume_r_bin.eq(consume_r_nxt) produce_w_gry = Signal(self._ctr_bits) produce_r_gry = Signal(self._ctr_bits) produce_enc = m.submodules.produce_enc = \ GrayEncoder(self._ctr_bits) produce_cdc = m.submodules.produce_cdc = \ FFSynchronizer(produce_w_gry, produce_r_gry, o_domain=self._r_domain) m.d.comb += produce_enc.i.eq(produce_w_nxt), m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o) consume_r_gry = Signal(self._ctr_bits, reset_less=True) consume_w_gry = Signal(self._ctr_bits) consume_enc = m.submodules.consume_enc = \ GrayEncoder(self._ctr_bits) consume_cdc = m.submodules.consume_cdc = \ FFSynchronizer(consume_r_gry, consume_w_gry, o_domain=self._w_domain) m.d.comb += consume_enc.i.eq(consume_r_nxt) m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o) consume_w_bin = Signal(self._ctr_bits) consume_dec = m.submodules.consume_dec = \ GrayDecoder(self._ctr_bits) m.d.comb += consume_dec.i.eq(consume_w_gry), m.d[self._w_domain] += consume_w_bin.eq(consume_dec.o) produce_r_bin = Signal(self._ctr_bits) produce_dec = m.submodules.produce_dec = \ GrayDecoder(self._ctr_bits) m.d.comb += produce_dec.i.eq(produce_r_gry), m.d.comb += produce_r_bin.eq(produce_dec.o) w_full = Signal() r_empty = Signal() m.d.comb += [ w_full.eq((produce_w_gry[-1] != consume_w_gry[-1]) & (produce_w_gry[-2] != consume_w_gry[-2]) & (produce_w_gry[:-2] == consume_w_gry[:-2])), r_empty.eq(consume_r_gry == produce_r_gry), ] m.d[self._w_domain] += self.w_level.eq((produce_w_bin - consume_w_bin)) m.d.comb += self.r_level.eq((produce_r_bin - consume_r_bin)) storage = Memory(width=self.width, depth=self.depth) w_port = m.submodules.w_port = storage.write_port( domain=self._w_domain) r_port = m.submodules.r_port = storage.read_port(domain=self._r_domain, transparent=False) m.d.comb += [ w_port.addr.eq(produce_w_bin[:-1]), w_port.data.eq(self.w_data), w_port.en.eq(do_write), self.w_rdy.eq(~w_full), ] m.d.comb += [ r_port.addr.eq(consume_r_nxt[:-1]), self.r_data.eq(r_port.data), r_port.en.eq(1), self.r_rdy.eq(~r_empty), ] # Reset handling to maintain FIFO and CDC invariants in the presence of a write-domain # reset. # There is a CDC hazard associated with resetting an async FIFO - Gray code counters which # are reset to 0 violate their Gray code invariant. One way to handle this is to ensure # that both sides of the FIFO are asynchronously reset by the same signal. We adopt a # slight variation on this approach - reset control rests entirely with the write domain. # The write domain's reset signal is used to asynchronously reset the read domain's # counters and force the FIFO to be empty when the write domain's reset is asserted. # This requires the two read domain counters to be marked as "reset_less", as they are # reset through another mechanism. See https://github.com/nmigen/nmigen/issues/181 for the # full discussion. w_rst = ResetSignal(domain=self._w_domain, allow_reset_less=True) r_rst = Signal() # Async-set-sync-release synchronizer avoids CDC hazards rst_cdc = m.submodules.rst_cdc = \ AsyncFFSynchronizer(w_rst, r_rst, o_domain=self._r_domain) # Decode Gray code counter synchronized from write domain to overwrite binary # counter in read domain. rst_dec = m.submodules.rst_dec = \ GrayDecoder(self._ctr_bits) m.d.comb += rst_dec.i.eq(produce_r_gry) with m.If(r_rst): m.d.comb += r_empty.eq(1) m.d[self._r_domain] += consume_r_gry.eq(produce_r_gry) m.d[self._r_domain] += consume_r_bin.eq(rst_dec.o) m.d[self._r_domain] += self.r_rst.eq(1) with m.Else(): m.d[self._r_domain] += self.r_rst.eq(0) if platform == "formal": with m.If(Initial()): m.d.comb += Assume(produce_w_gry == (produce_w_bin ^ produce_w_bin[1:])) m.d.comb += Assume(consume_r_gry == (consume_r_bin ^ consume_r_bin[1:])) return m
def elaborate(self, platform): m = Module() # # Reference clock selection. # # If we seem to have a raw pin record, we'll assume we're being passed the external REFCLK. # We'll instantiate an instance that captures the reference clock signal. if hasattr(self._refclk, 'p'): refclk = Signal() m.submodules.refclk_input = refclk_in = Instance( "EXTREFB", i_REFCLKP=self._refclk.p, i_REFCLKN=self._refclk.n, o_REFCLKO=refclk, p_REFCK_PWDNB="0b1", p_REFCK_RTERM="0b1", # 100 Ohm ) refclk_in.attrs["LOC"] = f"EXTREF{self._refclk_num}" # Otherwise, we'll accept the reference clock directly. else: refclk = self._refclk # # Raw serdes. # pll_config = ECP5SerDesPLLConfiguration( refclk, refclk_freq=self._refclk_frequency, linerate=5e9) serdes = ECP5SerDes( pll_config=pll_config, tx_pads=self._tx_pads, rx_pads=self._rx_pads, channel=self._channel, ) m.submodules.serdes = serdes m.d.comb += [ serdes.train_equalizer.eq(self.train_equalizer), self.ready.eq(serdes.tx_ready & serdes.rx_ready) ] # # Transmit datapath. # m.submodules.tx_datapath = tx_datapath = TransmitPreprocessing() m.d.comb += [ serdes.tx_idle.eq(self.tx_idle), serdes.tx_enable.eq(self.enable), tx_datapath.sink.stream_eq(self.sink), serdes.sink.stream_eq(tx_datapath.source), serdes.tx_gpio_en.eq(self.use_tx_as_gpio), serdes.tx_gpio.eq(self.tx_gpio) ] # # Receive datapath. # m.submodules.rx_datapath = rx_datapath = ReceivePostprocessing() m.d.comb += [ self.rx_idle.eq(serdes.rx_idle), serdes.rx_enable.eq(self.enable), serdes.rx_align.eq(self.rx_align), rx_datapath.align.eq(self.rx_align), rx_datapath.sink.stream_eq(serdes.source), self.source.stream_eq(rx_datapath.source) ] # Pass through a synchronized version of our SerDes' rx-gpio. m.submodules += FFSynchronizer(serdes.rx_gpio, self.rx_gpio, o_domain="fast") # # LFPS Detection # m.submodules.lfps_detector = lfps_detector = LFPSSquareWaveDetector( self._fast_clock_frequency) m.d.comb += [ lfps_detector.rx_gpio.eq(self.rx_gpio), self.lfps_signaling_detected.eq(lfps_detector.present) ] # debug signals m.d.comb += [ self.raw_rx_data.eq(serdes.source.data), self.raw_rx_ctrl.eq(serdes.source.ctrl), ] return m
def elaborate(self, platform): m = Module() # The ECP5 SerDes uses a simple feedback mechanism to keep its FIFO clocks in sync # with the FPGA's fabric. Accordingly, we'll need to capture the output clocks and then # pass them back to the SerDes; this allows the placer to handle clocking correctly, allows us # to attach clock constraints for analysis, and allows us to use these clocks for -very- simple tasks. txoutclk = Signal() rxoutclk = Signal() # Internal state. rx_los = Signal() rx_lol = Signal() rx_lsm = Signal() rx_align = Signal() rx_bus = Signal(24) tx_lol = Signal() tx_bus = Signal(24) # # Clock domain crossing. # tx_produce_square_wave = Signal() tx_produce_pattern = Signal() tx_pattern = Signal(20) m.submodules += [ # Transmit control synchronization. FFSynchronizer(self.tx_produce_square_wave, tx_produce_square_wave, o_domain="tx"), FFSynchronizer(self.tx_produce_pattern, tx_produce_pattern, o_domain="tx"), FFSynchronizer(self.tx_pattern, tx_pattern, o_domain="tx"), # Receive control synchronization. FFSynchronizer(self.rx_align, rx_align, o_domain="rx"), FFSynchronizer(rx_los, self.rx_idle, o_domain="sync"), ] # # Clocking / reset control. # # The SerDes needs to be brought up gradually; we'll do that here. m.submodules.reset_sequencer = reset = ECP5ResetSequencer() m.d.comb += [ reset.tx_pll_locked.eq(~tx_lol), reset.rx_pll_locked.eq(~rx_lol) ] # Create a local transmit domain, for our transmit-side hardware. m.domains.tx = ClockDomain() m.d.comb += ClockSignal("tx").eq(txoutclk) m.submodules += [ ResetSynchronizer(ResetSignal("sync"), domain="tx"), FFSynchronizer(~ResetSignal("tx"), self.tx_ready) ] # Create the same setup, buf for the receive side. m.domains.rx = ClockDomain() m.d.comb += ClockSignal("rx").eq(rxoutclk) m.submodules += [ ResetSynchronizer(ResetSignal("sync"), domain="rx"), FFSynchronizer(~ResetSignal("rx"), self.rx_ready) ] # # Core SerDes instantiation. # serdes_params = dict( # DCU — power management p_D_MACROPDB="0b1", p_D_IB_PWDNB="0b1", # undocumented (required for RX) p_D_TXPLL_PWDNB="0b1", i_D_FFC_MACROPDB=1, # DCU — reset i_D_FFC_MACRO_RST=ResetSignal("sync"), i_D_FFC_DUAL_RST=ResetSignal("sync"), # DCU — clocking i_D_REFCLKI=self._pll.refclk, o_D_FFS_PLOL=tx_lol, p_D_REFCK_MODE={ 25: "0b100", 20: "0b000", 16: "0b010", 10: "0b001", 8: "0b011" }[self._pll.config["mult"]], p_D_TX_MAX_RATE="5.0", # 5.0 Gbps p_D_TX_VCO_CK_DIV={ 32: "0b111", 16: "0b110", 8: "0b101", 4: "0b100", 2: "0b010", 1: "0b000" }[1], # DIV/1 p_D_BITCLK_LOCAL_EN="0b1", # Use clock from local PLL # Clock multiplier unit configuration p_D_CMUSETBIASI= "0b00", # begin undocumented (10BSER sample code used) p_D_CMUSETI4CPP="0d3", p_D_CMUSETI4CPZ="0d3", p_D_CMUSETI4VCO="0b00", p_D_CMUSETICP4P="0b01", p_D_CMUSETICP4Z="0b101", p_D_CMUSETINITVCT="0b00", p_D_CMUSETISCL4VCO="0b000", p_D_CMUSETP1GM="0b000", p_D_CMUSETP2AGM="0b000", p_D_CMUSETZGM="0b000", p_D_SETIRPOLY_AUX="0b01", p_D_SETICONST_AUX="0b01", p_D_SETIRPOLY_CH="0b01", p_D_SETICONST_CH="0b10", p_D_SETPLLRC="0d1", p_D_RG_EN="0b0", p_D_RG_SET="0b00", p_D_REQ_ISET="0b011", p_D_PD_ISET="0b11", # end undocumented # DCU — FIFOs p_D_LOW_MARK= "0d4", # Clock compensation FIFO low water mark (mean=8) p_D_HIGH_MARK= "0d12", # Clock compensation FIFO high water mark (mean=8) # CHX common --------------------------------------------------------------------------- # CHX — protocol p_CHX_PROTOCOL="10BSER", p_CHX_UC_MODE="0b1", p_CHX_ENC_BYPASS="******", # Use the 8b10b encoder p_CHX_DEC_BYPASS="******", # Use the 8b10b decoder # CHX receive -------------------------------------------------------------------------- # CHX RX — power management p_CHX_RPWDNB="0b1", i_CHX_FFC_RXPWDNB=1, # CHX RX — reset i_CHX_FFC_RRST=~self.rx_enable | reset.serdes_rx_reset, i_CHX_FFC_LANE_RX_RST=~self.rx_enable | reset.pcs_reset, # CHX RX — input i_CHX_HDINP=self._rx_pads.p, i_CHX_HDINN=self._rx_pads.n, p_CHX_REQ_EN="0b1", # Enable equalizer p_CHX_REQ_LVL_SET="0b01", p_CHX_RX_RATE_SEL="0d09", # Equalizer pole position p_CHX_RTERM_RX={ "5k-ohms": "0b00000", "80-ohms": "0b00001", "75-ohms": "0b00100", "70-ohms": "0b00110", "60-ohms": "0b01011", "50-ohms": "0b10011", "46-ohms": "0b11001", "wizard-50-ohms": "0d22" }["wizard-50-ohms"], p_CHX_RXIN_CM="0b11", # CMFB (wizard value used) p_CHX_RXTERM_CM="0b10", # RX Input (wizard value used) # CHX RX — clocking i_CHX_RX_REFCLK=self._pll.refclk, o_CHX_FF_RX_PCLK=rxoutclk, i_CHX_FF_RXI_CLK=ClockSignal("rx"), p_CHX_CDR_MAX_RATE="5.0", # 5.0 Gbps p_CHX_RX_DCO_CK_DIV={ 32: "0b111", 16: "0b110", 8: "0b101", 4: "0b100", 2: "0b010", 1: "0b000" }[1], # DIV/1 p_CHX_RX_GEAR_MODE="0b1", # 1:2 gearbox p_CHX_FF_RX_H_CLK_EN="0b1", # enable DIV/2 output clock p_CHX_FF_RX_F_CLK_DIS="0b1", # disable DIV/1 output clock p_CHX_SEL_SD_RX_CLK="0b1", # FIFO driven by recovered clock p_CHX_AUTO_FACQ_EN="0b1", # undocumented (wizard value used) p_CHX_AUTO_CALIB_EN="0b1", # undocumented (wizard value used) p_CHX_PDEN_SEL="0b0", # phase detector disabled on LOS p_CHX_DCOATDCFG="0b00", # begin undocumented (sample code used) p_CHX_DCOATDDLY="0b00", p_CHX_DCOBYPSATD="0b1", p_CHX_DCOCALDIV="0b000", p_CHX_DCOCTLGI="0b011", p_CHX_DCODISBDAVOID="0b0", p_CHX_DCOFLTDAC="0b00", p_CHX_DCOFTNRG="0b001", p_CHX_DCOIOSTUNE="0b010", p_CHX_DCOITUNE="0b00", p_CHX_DCOITUNE4LSB="0b010", p_CHX_DCOIUPDNX2="0b1", p_CHX_DCONUOFLSB="0b100", p_CHX_DCOSCALEI="0b01", p_CHX_DCOSTARTVAL="0b010", p_CHX_DCOSTEP="0b11", # end undocumented # CHX RX — loss of signal o_CHX_FFS_RLOS=rx_los, p_CHX_RLOS_SEL="0b1", p_CHX_RX_LOS_EN="0b0", p_CHX_RX_LOS_LVL="0b101", # Lattice "TBD" (wizard value used) p_CHX_RX_LOS_CEQ="0b11", # Lattice "TBD" (wizard value used) p_CHX_RX_LOS_HYST_EN="0b1", # CHX RX — loss of lock o_CHX_FFS_RLOL=rx_lol, # CHX RX — link state machine # Note that Lattice Diamond needs these in their provided bases (and string lengths!). # Changing their bases will work with the open toolchain, but will make Diamond mad. i_CHX_FFC_SIGNAL_DETECT=rx_align, o_CHX_FFS_LS_SYNC_STATUS=rx_lsm, p_CHX_ENABLE_CG_ALIGN="0b1", p_CHX_UDF_COMMA_MASK="0x0ff", # compare the 8 lsbs p_CHX_UDF_COMMA_A= "0x003", # "0b0000000011", # K28.1, K28.5 and K28.7 p_CHX_UDF_COMMA_B= "0x07c", # "0b0001111100", # K28.1, K28.5 and K28.7 p_CHX_CTC_BYPASS="******", # bypass CTC FIFO p_CHX_MIN_IPG_CNT="0b11", # minimum interpacket gap of 4 p_CHX_MATCH_2_ENABLE="0b0", # 2 character skip matching p_CHX_MATCH_4_ENABLE="0b0", # 4 character skip matching p_CHX_CC_MATCH_1="0x000", p_CHX_CC_MATCH_2="0x000", p_CHX_CC_MATCH_3="0x000", p_CHX_CC_MATCH_4="0x000", # CHX RX — data **{"o_CHX_FF_RX_D_%d" % n: rx_bus[n] for n in range(len(rx_bus))}, # CHX transmit ------------------------------------------------------------------------- # CHX TX — power management p_CHX_TPWDNB="0b1", i_CHX_FFC_TXPWDNB=1, # CHX TX — reset i_D_FFC_TRST=~self.tx_enable | reset.serdes_tx_reset, i_CHX_FFC_LANE_TX_RST=~self.tx_enable | reset.pcs_reset, # CHX TX — output o_CHX_HDOUTP=self._tx_pads.p, o_CHX_HDOUTN=self._tx_pads.n, p_CHX_TXAMPLITUDE="0d1000", # 1000 mV p_CHX_RTERM_TX={ "5k-ohms": "0b00000", "80-ohms": "0b00001", "75-ohms": "0b00100", "70-ohms": "0b00110", "60-ohms": "0b01011", "50-ohms": "0b10011", "46-ohms": "0b11001", "wizard-50-ohms": "0d19" }["50-ohms"], p_CHX_TDRV_SLICE0_CUR="0b011", # 400 uA p_CHX_TDRV_SLICE0_SEL="0b01", # main data p_CHX_TDRV_SLICE1_CUR="0b000", # 100 uA p_CHX_TDRV_SLICE1_SEL="0b00", # power down p_CHX_TDRV_SLICE2_CUR="0b11", # 3200 uA p_CHX_TDRV_SLICE2_SEL="0b01", # main data p_CHX_TDRV_SLICE3_CUR="0b10", # 2400 uA p_CHX_TDRV_SLICE3_SEL="0b01", # main data p_CHX_TDRV_SLICE4_CUR="0b00", # 800 uA p_CHX_TDRV_SLICE4_SEL="0b00", # power down p_CHX_TDRV_SLICE5_CUR="0b00", # 800 uA p_CHX_TDRV_SLICE5_SEL="0b00", # power down # CHX TX — clocking o_CHX_FF_TX_PCLK=txoutclk, i_CHX_FF_TXI_CLK=ClockSignal("tx"), p_CHX_TX_GEAR_MODE="0b1", # 1:2 gearbox p_CHX_FF_TX_H_CLK_EN="0b1", # enable DIV/2 output clock p_CHX_FF_TX_F_CLK_DIS="0b1", # disable DIV/1 output clock # CHX TX — data **{"i_CHX_FF_TX_D_%d" % n: tx_bus[n] for n in range(len(tx_bus))}, # SCI interface. #**{"i_D_SCIWDATA%d" % n: sci.sci_wdata[n] for n in range(8)}, #**{"i_D_SCIADDR%d" % n: sci.sci_addr[n] for n in range(6)}, #**{"o_D_SCIRDATA%d" % n: sci.sci_rdata[n] for n in range(8)}, #i_D_SCIENAUX = sci.dual_sel, #i_D_SCISELAUX = sci.dual_sel, #i_CHX_SCIEN = sci.chan_sel, #i_CHX_SCISEL = sci.chan_sel, #i_D_SCIRD = sci.sci_rd, #i_D_SCIWSTN = sci.sci_wrn, # Out-of-band signaling Rx support. p_CHX_LDR_RX2CORE_SEL="0b1", # Enables low-speed out-of-band input. o_CHX_LDR_RX2CORE=self.rx_gpio, # Out-of-band signaling Tx support. p_CHX_LDR_CORE2TX_SEL= "0b0", # Uses CORE2TX_EN to enable out-of-band output. i_CHX_LDR_CORE2TX=self.tx_gpio, i_CHX_FFC_LDR_CORE2TX_EN=self.tx_gpio_en) # Translate the 'CHX' string to the correct channel name in each of our SerDes parameters, # and create our SerDes instance. serdes_params = { k.replace("CHX", f"CH{self._channel}"): v for (k, v) in serdes_params.items() } m.submodules.serdes = serdes = Instance("DCUA", **serdes_params) # Bind our SerDes to the correct location inside the FPGA. serdes.attrs["LOC"] = "DCU{}".format(self._dual) serdes.attrs["CHAN"] = "CH{}".format(self._channel) serdes.attrs["BEL"] = "X42/Y71/DCU" # # TX and RX datapaths (SerDes <-> stream conversion) # sink = self.sink source = self.source m.d.comb += [ # Grab our received data directly from our SerDes; modifying things to match the # SerDes Rx bus layout, which squishes status signals between our two geared words. source.data[0:8].eq(rx_bus[0:8]), source.data[8:16].eq(rx_bus[12:20]), source.ctrl[0].eq(rx_bus[8]), source.ctrl[1].eq(rx_bus[20]), source.valid.eq(1), # Stick the data we'd like to transmit into the SerDes; again modifying things to match # the transmit bus layout. tx_bus[0:8].eq(sink.data[0:8]), tx_bus[12:20].eq(sink.data[8:16]), tx_bus[8].eq(sink.ctrl[0]), tx_bus[20].eq(sink.ctrl[1]), sink.ready.eq(1) ] return m
def elaborate(self, platform): m = Module() # register submodules (and make a local reference without self) m.submodules.rdport = rdport = \ self.line_buf.read_port(domain=self.led_domain, transparent=False) # we want each enable to cover one channel so we can write one channel # at a time m.submodules.wrport = wrport = \ self.line_buf.write_port(granularity=self.pd.bpp) m.submodules.col_ctr = col_ctr = self.col_ctr # zeroth, we need to know which row the frame generator is currently # working on so we can work on the next one. because it's in the LED # domain and we're in sync, we have to synchronize it to us. there will # be a few cycles of delay, but it's okay unless the pixel reader is # like 99.99% busy. fg_row = Signal(self.pd.row_bits) fg_row_syncer = FFSynchronizer(i=self.i_row, o=fg_row, o_domain="sync", reset_less=False, stages=3) m.submodules += fg_row_syncer # first, give the frame generator the pixels it wants fg_data = Signal(6 * self.pd.bpp) m.d.comb += [ # they get read directly from the buffer in the LED domain rdport.addr.eq(Cat(self.i_col, self.i_row[0])), fg_data.eq(rdport.data), ] # once the memory is read, we have to select the bits from the pixels # that the frame generator actually wants. with m.Switch(self.i_bit): for bn in range(self.pd.bpp): # which bit from the pixel with m.Case(bn): for cn in range(6): # which pixel channel m.d.comb += self.o_pixel[cn].eq( fg_data[cn * self.pd.bpp + bn]) # second, we need to read pixels to fill the buffer for the next line rd_row = Signal(self.pd.row_bits) # which row we're reading rd_chan = Signal(3) # which channel we're reading. goes 0-2, 4-6. # combine into output physical address. # channel bits, column, row, display half bit m.d.comb += self.o_raddr.eq( Cat(rd_chan[:2], col_ctr.value, rd_row, rd_chan[-1])) should_count = Signal() # should we count to the next pixel? m.d.comb += self.col_ctr.enable.eq(should_count) should_read = Signal() # are we intending to read a pixel? m.d.comb += self.o_re.eq(should_read) # this is probably not logically correct!! it needs to be fixed if we # ever plan to actually stall the pixel reader. done_reading = Signal() # did the pixel read finish? m.d.comb += done_reading.eq(self.i_rack) # once the pixel read finishes, we want to write it to the buffer. # we replicate the read channel data across the 6 memory channels, then # only enable the write for the channel that the data is for. # of course, if gamma is enabled, we pass the data through the gamma # correction table before writing it. # if we are done reading this cycle, use the data channel and address # from last cycle because there is at least a 1 cycle latency through # the read port, and we want to change that stuff this cycle so we can # get another channel ASAP. rd_wchan = Signal(3) rd_waddr = Signal.like(wrport.addr) m.d.sync += [ rd_wchan.eq(rd_chan), rd_waddr.eq(Cat(col_ctr.value, rd_row[0])), ] if self.gp.gamma is None: m.d.comb += wrport.data.eq(Repl(self.i_rdata, 6)) # write data at the address of the current pixel, in the appropriate # buffer half m.d.comb += wrport.addr.eq(rd_waddr) # enable the appropriate channel for writing for ch in range(6): # we index channels from 0 to 5 here, but rd_wchan goes 0-2, 4-6 # because we skip 3 to keep addressing nice. m.d.comb += wrport.en[ch].eq( done_reading & (rd_wchan == (ch if ch < 3 else ch + 1))) else: # the above, but through the gamma table g_rdport = self.gamma_table.read_port() m.submodules += g_rdport # buffer everything else an extra cycle to account for the table g_waddr = Signal.like(rd_waddr) g_wchan = Signal.like(rd_wchan) g_done_reading = Signal.like(done_reading) m.d.comb += [ g_rdport.addr.eq(self.i_rdata), wrport.data.eq(Repl(g_rdport.data, 6)), wrport.addr.eq(g_waddr), ] m.d.sync += [ g_waddr.eq(rd_waddr), g_wchan.eq(rd_wchan), g_done_reading.eq(done_reading), ] for ch in range(6): m.d.comb += wrport.en[ch].eq( g_done_reading & (g_wchan == (ch if ch < 3 else ch + 1))) # FSM generates the channel addresses and manages starting/ending reads with m.FSM("WAIT"): with m.State("WAIT"): # wait for next line to begin # i.e. the frame generator is reading the line we just wrote to with m.If(fg_row[0] == rd_row[0]): # so start working on the next line m.d.sync += rd_row.eq(fg_row + 1) # start at the first pixel m.d.comb += col_ctr.reset.eq(1) # and get back into it m.next = "P0R" with m.State("P0R"): # top physical pixel's red channel m.d.comb += [ rd_chan.eq(0), # which is channel 0 should_read.eq(1), ] with m.If(done_reading): # have we got the data? # go to next pixel. writing is handled outside state machine m.next = "P0G" with m.State("P0G"): # top physical pixel's green channel m.d.comb += [ rd_chan.eq(1), should_read.eq(1), ] with m.If(done_reading): m.next = "P0B" with m.State("P0B"): # top physical pixel's blue channel m.d.comb += [ rd_chan.eq(2), should_read.eq(1), ] with m.If(done_reading): m.next = "P1R" with m.State("P1R"): # bottom physical pixel's red channel m.d.comb += [ rd_chan.eq(4), should_read.eq(1), ] with m.If(done_reading): m.next = "P1G" with m.State("P1G"): # bottom physical pixel's green channel m.d.comb += [ rd_chan.eq(5), should_read.eq(1), ] with m.If(done_reading): m.next = "P1B" with m.State("P1B"): # bottom physical pixel's blue channel m.d.comb += [ rd_chan.eq(6), should_read.eq(1), ] with m.If(done_reading): # address next pixel in the line m.d.comb += should_count.eq(1) with m.If(col_ctr.at_max): # done with this line? m.next = "WAIT" with m.Else(): m.next = "P0R" return m
def rx(): # count out the bits we're receiving (including start and stop) bit_ctr = Signal(range(self.char_bits + 2 - 1)) # shift in the data bits, plus start and stop in_buf = Signal(self.char_bits + 2) # count cycles per baud baud_ctr = Signal(16) # since the rx pin is attached to arbitrary external logic, we # should sync it with our domain first. i_rx = Signal(reset=1) rx_sync = FFSynchronizer(self.i_rx, i_rx, reset=1) m.submodules.rx_sync = rx_sync with m.FSM("IDLE"): with m.State("IDLE"): # has the receive line been asserted? (todo, maybe debounce # this a couple cycles? is that even a problem?) with m.If(~i_rx): m.d.sync += [ # start counting down the bits bit_ctr.eq(self.char_bits + 2 - 1), # and tell the user that we're actively receiving r0_rx_active.eq(1), ] # start the baud counter at half the baud time. this way # we end up halfway through the start bit when we next # sample and can make sure that rx is still asserted. # we're also then lined up to sample the rest of the # bits in the middle. m.d.sync += baud_ctr.eq(r0_baud_divisor >> 1) # then just receive the start bit like any other m.next = "RECV" with m.State("RECV"): m.d.sync += baud_ctr.eq(baud_ctr - 1) with m.If(baud_ctr == 0): # sample the bit once it's time. we shift bits into the # MSB so the first bit ends up at the LSB once we are # done. m.d.sync += in_buf.eq(Cat(in_buf[1:], i_rx)) with m.If(bit_ctr == 0): # this is the stop bit? # yes, sample it (this cycle) and finish up next m.next = "FINISH" with m.Else(): # no, wait to receive another bit m.d.sync += [ baud_ctr.eq(r0_baud_divisor), bit_ctr.eq(bit_ctr - 1), ] with m.State("FINISH"): # make sure that the start bit is 0 and the stop bit is 1, # like the standard prescribes. with m.If((in_buf[0] == 0) & (in_buf[-1] == 1)): # store the data to the "FIFO" if we have space with m.If(r3_rx_empty.value): # minus start and stop bits m.d.sync += r3_rx_data.eq(in_buf[1:-1]) m.d.comb += r3_rx_empty.reset.eq(1) with m.Else(): m.d.comb += r1_rx_overflow.set.eq(1) with m.Else(): # we didn't actually receive a character. let # the user know that something bad happened. m.d.comb += r1_rx_error.set.eq(1) # but we did finish receiving no matter what happened m.d.sync += r0_rx_active.eq(0) m.next = "IDLE"
def elaborate(self, platform): m = Module() # count out the bits we're receiving (including start and stop) bit_ctr = Signal(range(8 + 2 - 1)) # shift in the data bits, plus start and stop in_buf = Signal(8 + 2) # count cycles per baud baud_ctr = Signal(range(self.divisor)) # the data buf is connected directly. it's only valid for the cycle we # say it is though. m.d.comb += self.o_data.eq(in_buf[1:-1]) # since the rx pin is attached to arbitrary external logic, we must sync # it with our domain first. i_rx = Signal(reset=1) rx_sync = FFSynchronizer(self.i_rx, i_rx, reset=1) m.submodules.rx_sync = rx_sync with m.FSM("IDLE"): with m.State("IDLE"): # has the receive line been asserted? with m.If(~i_rx): m.d.sync += [ # start counting down the bits bit_ctr.eq(8 + 2 - 1), # we are now active! self.o_active.eq(1), ] # start the baud counter at half the baud time. this way we # end up halfway through the start bit when we next sample # so we can make sure the start bit is still there. we're # also then lined up to sample the rest of the bits in the # middle. m.d.sync += baud_ctr.eq(self.divisor >> 1) # now we just receive the start bit like any other m.next = "RECV" with m.State("RECV"): m.d.sync += baud_ctr.eq(baud_ctr - 1) with m.If(baud_ctr == 0): # sample the bit once it's time. we shift bits into the MSB # so the first bit ends up at the LSB once we are done. m.d.sync += in_buf.eq(Cat(in_buf[1:], i_rx)) with m.If(bit_ctr == 0): # this is the stop bit? # yes, sample it (this cycle) and finish up next m.next = "FINISH" with m.Else(): # no, wait to receive another bit m.d.sync += [ baud_ctr.eq(self.divisor), bit_ctr.eq(bit_ctr - 1), ] with m.State("FINISH"): # make sure that the start bit is 0 and the stop bit is 1, like # the standard prescribes. with m.If((in_buf[0] == 0) & (in_buf[-1] == 1)): # tell the user we've got something m.d.comb += self.o_we.eq(1) with m.Else(): # we didn't correctly receive the character. let the user # know that something bad happened. m.d.comb += self.o_error.eq(1) # but we did finish receiving, no matter the outcome m.d.sync += self.o_active.eq(0) m.next = "IDLE" # technically, there's still half a bit time until the stop bit # is over, but that's ok. the rx line is deasserted during that # time so we won't accidentally start receiving another bit. return m
def elaborate(self, platform): m = Module() pads = self._pads if hasattr(pads, "ce_t"): m.d.comb += [ pads.ce_t.oe.eq(1), pads.ce_t.o.eq(~self.ce), ] if hasattr(pads, "oe_t"): m.d.comb += [ pads.oe_t.oe.eq(1), pads.oe_t.o.eq(~self.oe), ] if hasattr(pads, "we_t"): m.d.comb += [ pads.we_t.oe.eq(1), pads.we_t.o.eq(~self.we), ] m.d.comb += [ pads.dq_t.oe.eq(~self.oe), pads.dq_t.o.eq(self.d), ] m.submodules += FFSynchronizer(pads.dq_t.i, self.q) m.d.comb += [ pads.a_t.oe.eq(1), pads.a_t.o.eq(self.a), # directly drive low bits ] if hasattr(pads, "a_clk_t") and hasattr(pads, "a_si_t"): a_clk = Signal(reset=1) a_si = Signal() a_lat = Signal(reset=0) if hasattr(pads, "a_lat_t") else None m.d.comb += [ pads.a_clk_t.oe.eq(1), pads.a_clk_t.o.eq(a_clk), pads.a_si_t.oe.eq(1), pads.a_si_t.o.eq(a_si), ] if a_lat is not None: m.d.comb += [pads.a_lat_t.oe.eq(1), pads.a_lat_t.o.eq(a_lat)] # "sa" is the sliced|shifted address, refering to the top-most bits sa_input = self.a[len(pads.a_t.o):] # This represents a buffer of those high address bits, # not to be confused with the latch pin. sa_latch = Signal(self.a_bits - len(pads.a_t.o)) sh_cyc = math.ceil(platform.default_clk_frequency / self._sh_freq) timer = Signal(range(sh_cyc), reset=sh_cyc - 1) count = Signal(range(len(sa_latch) + 1)) first = Signal(reset=1) with m.FSM(): with m.State("READY"): m.d.sync += first.eq(0) with m.If((sa_latch == sa_input) & ~first): m.d.comb += self.rdy.eq(1) with m.Else(): m.d.sync += count.eq(len(sa_latch)) m.d.sync += sa_latch.eq(sa_input) m.next = "SHIFT" with m.State("SHIFT"): with m.If(timer == 0): m.d.sync += timer.eq(timer.reset) m.d.sync += a_clk.eq(~a_clk) with m.If(a_clk): m.d.sync += a_si.eq(sa_latch[-1]) m.d.sync += count.eq(count - 1) with m.Else(): m.d.sync += sa_latch.eq(sa_latch.rotate_left(1)) with m.If(count == 0): if a_lat is None: m.next = "READY" else: m.next = "LATCH-1" with m.Else(): m.d.sync += timer.eq(timer - 1) if a_lat is not None: with m.State("LATCH-1"): m.d.sync += a_lat.eq(1) with m.If(timer == 0): m.d.sync += timer.eq(timer.reset) m.next = "LATCH-2" with m.Else(): m.d.sync += timer.eq(timer - 1) with m.State("LATCH-2"): with m.If(timer == 0): m.d.sync += timer.eq(timer.reset) m.d.sync += a_lat.eq(0) m.next = "READY" with m.Else(): m.d.sync += timer.eq(timer - 1) else: m.d.comb += self.rdy.eq(1) return m
def elaborate(self, platform): m = Module() hispi_6_in = "{}_x6_in".format(self.hispi_domain) m.domains += ClockDomain(hispi_6_in) m.d.comb += ClockSignal(hispi_6_in).eq(self.hispi_clk) m.d[hispi_6_in] += self.hispi_x6_in_domain_counter.eq( self.hispi_x6_in_domain_counter + 1) mul = 3 pll = m.submodules.pll = Mmcm(300e6, mul, 1, input_domain=hispi_6_in.format( self.hispi_domain)) pll.output_domain("{}_x6".format(self.hispi_domain), mul * 1) pll.output_domain("{}_x3".format(self.hispi_domain), mul * 2) pll.output_domain("{}_x2".format(self.hispi_domain), mul * 3) pll.output_domain("{}".format(self.hispi_domain), mul * 6) for lane in range(0, len(self.hispi_lanes)): iserdes = m.submodules["hispi_iserdes_" + str(lane)] = _ISerdes( data_width=6, data_rate="DDR", serdes_mode="master", interface_type="networking", num_ce=1, iobDelay="none", ) m.d.comb += iserdes.d.eq(self.hispi_lanes[lane]) m.d.comb += iserdes.ce[1].eq(1) m.d.comb += iserdes.clk.eq( ClockSignal("{}_x6".format(self.hispi_domain))) m.d.comb += iserdes.clkb.eq( ~ClockSignal("{}_x6".format(self.hispi_domain))) m.d.comb += iserdes.rst.eq( ResetSignal("{}_x6".format(self.hispi_domain))) m.d.comb += iserdes.clkdiv.eq( ClockSignal("{}_x2".format(self.hispi_domain))) data = Signal(12) iserdes_output = Signal(6) m.d.comb += iserdes_output.eq( Cat(iserdes.q[j] for j in range(1, 7))) hispi_x2 = "{}_x2".format(self.hispi_domain) lower_upper_half = Signal() m.d[hispi_x2] += lower_upper_half.eq(~lower_upper_half) with m.If(lower_upper_half): m.d[hispi_x2] += data[6:12].eq(iserdes_output) with m.Else(): m.d[hispi_x2] += data[0:6].eq(iserdes_output) data_in_hispi_domain = Signal(12) m.submodules["data_cdc_{}".format(lane)] = FFSynchronizer( data, data_in_hispi_domain, o_domain=self.hispi_domain) hispi_domain = m.d[self.hispi_domain] bitslip = Signal() was_bitslip = Signal() hispi_domain += was_bitslip.eq(bitslip) with m.If(self.bitslip[lane] & ~was_bitslip & self.enable_bitslip): hispi_domain += bitslip.eq(1) with m.Else(): hispi_domain += bitslip.eq(0) serdes_or_emulated_bitslip = Signal() with m.If(bitslip): hispi_domain += serdes_or_emulated_bitslip.eq( ~serdes_or_emulated_bitslip) m.d.comb += iserdes.bitslip.eq(bitslip & serdes_or_emulated_bitslip) data_order_index = Signal(range(4)) with m.If(bitslip & ~serdes_or_emulated_bitslip): hispi_domain += data_order_index.eq(data_order_index + 1) data_order = StatusSignal(range(16)) setattr(self, "data_order_{}".format(lane), data_order) m.d.comb += data_order.eq(Array((1, 4, 9, 12))[data_order_index]) current = Signal(12) last = Signal(12) m.d.comb += current.eq(data_in_hispi_domain) hispi_domain += last.eq(data_in_hispi_domain) reordered = Signal(12) parts = [current[0:6], current[6:12], last[0:6], last[6:12]] for cond, i in iterator_with_if_elif(range(16), m): with cond(data_order == i): first = parts[i % 4] second = parts[i // 4] m.d.comb += reordered.eq(Cat(first, second)) with m.If(self.word_reverse): m.d.comb += self.out[lane].eq( Cat(reordered[i] for i in range(12))) with m.Else(): m.d.comb += self.out[lane].eq( Cat(reordered[i] for i in reversed(range(12)))) out_status_signal = StatusSignal(12, name="out_{}".format(lane)) setattr(self, "out_{}".format(lane), out_status_signal) m.d.comb += out_status_signal.eq(data_in_hispi_domain) return m
from nmigen import * from nmigen.lib.cdc import FFSynchronizer from nmigen.cli import main i, o = Signal(name="i"), Signal(name="o") m = Module() m.submodules += FFSynchronizer(i, o) if __name__ == "__main__": main(m, ports=[i, o])
def elaborate(self, platform): m = Module() # temp clock setup platform.ps7.fck_domain(200e6, self.domain + "_delay_ref") m.domains += ClockDomain(self.domain + "_ctrl") m.d.comb += ClockSignal(self.domain + "_ctrl").eq( ClockSignal(platform.csr_domain)) phy = m.submodules.phy = self.phy trainer = m.submodules.trainer = self.trainer m.submodules += FFSynchronizer(trainer.lane_pattern, phy.lane_pattern, o_domain=self.domain + "_hword") m.d.comb += [ phy.outclk.eq(self.lvds_outclk), phy.lanes.eq(Cat(self.lanes, self.lane_ctrl)), phy.lane_delay_reset.eq(trainer.lane_delay_reset), phy.lane_delay_inc.eq(trainer.lane_delay_inc), phy.lane_bitslip.eq(trainer.lane_bitslip), phy.outclk_delay_reset.eq(trainer.outclk_delay_reset), phy.outclk_delay_inc.eq(trainer.outclk_delay_inc), phy.halfslip.eq(trainer.halfslip), trainer.lane_match.eq(phy.lane_match), trainer.lane_mismatch.eq(phy.lane_mismatch), ] # synchronize valid signal to output domain lanes_valid = Signal() valid_sync = m.submodules.valid_sync = PulseSynchronizer( self.domain + "_hword", self.output_domain) trained = Signal() m.submodules += FFSynchronizer(trainer.trained, trained, o_domain=self.domain + "_hword") m.d.comb += [ valid_sync.i.eq(phy.output_valid & trained), lanes_valid.eq(valid_sync.o) ] # information about the "current" pixel data_lanes = Signal(len(self.lanes) * self.bits) pixel_valid = Signal() line_valid = Signal() frame_valid = Signal() # information about the next pixel, i.e. what we grab from the PHY next_data_lanes = Signal(len(self.lanes) * self.bits) next_pixel_valid = Signal() next_line_valid = Signal() next_frame_valid = Signal() # on the rising edge of the valid signal, grab the next pixel from the # PHY. this is a clock domain crossing, but since the source clock is in # phase with the output clock, we will be okay without synchronization. # note that the valid signal rises only once every `self.bits/2` clocks. with m.If(lanes_valid): m.d[self.output_domain] += [ next_data_lanes.eq(Cat(*phy.output[:-1])), Cat(next_pixel_valid, next_line_valid, next_frame_valid).eq(phy.output[-1]), # move the next pixel to the current pixel data_lanes.eq(next_data_lanes), Cat(pixel_valid, line_valid, frame_valid).eq( Cat(next_pixel_valid, next_line_valid, next_frame_valid)) ] # once we've got that, generate the output stream information output = ImageStream(len(self.lanes) * self.bits) m.d.comb += output.payload.eq(data_lanes) with m.If(Fell(m, lanes_valid, self.output_domain)): m.d.comb += [ output.valid.eq(pixel_valid), # if the next pixel isn't a valid line or frame, this pixel is # the last one of the current line/frame output.line_last.eq(line_valid & ~next_line_valid), output.frame_last.eq(frame_valid & ~next_frame_valid), ] # we are naughty and do not respect the ready signal, so add a buffer # to handle that part of the contract for us buffer = m.submodules.buffer = DomainRenamer(self.output_domain)( StreamBuffer(output)) m.d.comb += self.output.connect_upstream(buffer.output) return m