def find_bit_timings(self, m: Module, sync_counter: DividingCounter, got_edge: Signal): """Waits for the ten zero bits of the SYNC section to determine the length of an ADAT bit""" sync = m.d.sync bit_time_44100 = math.ceil(110 * (self.clk_freq/self.adat_freq(44100) / 100)) # as long as the input does not change, count up # else reset with m.If(got_edge): # if the sync counter is 10% over the sync time @44100Hz, then # the signal just woke up from the dead. Start counting again. with m.If(sync_counter.counter_out > 10 * bit_time_44100): sync += sync_counter.reset_in.eq(1) # if we are in the middle of the signal, # and got an edge, then we reset the counter on each edge with m.Else(): # when the counter is bigger than 3/4 of the old max, then we have a sync frame with m.If(sync_counter.counter_out > 7 * bit_time_44100): sync += sync_counter.active_in.eq(0) # stop counting, we found it m.next = "DECODE" with m.Else(): sync += sync_counter.reset_in.eq(1) # when we have no edge, count... with m.Else(): sync += [ sync_counter.reset_in.eq(0), sync_counter.active_in.eq(1) ]
def decode_nrzi(self, m: Module, bit_time: Signal, got_edge: Signal, sync_counter: DividingCounter): """Do the actual decoding of the NRZI bitstream""" sync = m.d.sync bit_counter = Signal(7) # this counter is used to detect a dead signal # to determine when to go back to SYNC state dead_counter = Signal(8) output = Signal(reset=1) # recover ADAT clock with m.If(bit_counter <= (bit_time >> 1)): m.d.comb += self.recovered_clock_out.eq(1) with m.Else(): m.d.comb += self.recovered_clock_out.eq(0) # when the frame decoder got garbage # then we need to go back to SYNC state with m.If(self.invalid_frame_in): sync += [ sync_counter.reset_in.eq(1), bit_counter.eq(0), dead_counter.eq(0) ] m.next = "SYNC" sync += bit_counter.eq(bit_counter + 1) with m.If(got_edge): sync += [ # latch 1 until we read it in the middle of the bit output.eq(1), # resynchronize at each bit edge, 1 to compensate # for sync delay bit_counter.eq(1), # when we get an edge, the signal is alive, reset counter dead_counter.eq(0) ] with m.Else(): sync += dead_counter.eq(dead_counter + 1) # wrap the counter with m.If(bit_counter == bit_time): sync += bit_counter.eq(0) # output at the middle of the bit with m.Elif(bit_counter == (bit_time >> 1)): sync += [ self.data_out.eq(output), self.data_out_en.eq(1), # pulse out_en output.eq(0) # edge has been output, wait for new edge ] with m.Else(): sync += self.data_out_en.eq(0) # when we had no edge for 16 bits worth of time # then we go back to sync state with m.If(dead_counter >= bit_time << 4): sync += dead_counter.eq(0) m.next = "SYNC"
def elaborate(self, platform): m = Module() # This state machine recognizes sequences of 6 bits and drops the 7th # bit. The fsm implements a counter in a series of several states. # This is intentional to help absolutely minimize the levels of logic # used. drop_bit = Signal(1) with m.FSM(domain="usb_io"): for i in range(6): with m.State(f"D{i}"): with m.If(self.i_valid): with m.If(self.i_data): # Receiving '1' increments the bitstuff counter. m.next = (f"D{i + 1}") with m.Else(): # Receiving '0' resets the bitstuff counter. m.next = "D0" with m.State("D6"): with m.If(self.i_valid): m.d.comb += drop_bit.eq(1) # Reset the bitstuff counter, drop the data. m.next = "D0" m.d.usb_io += [ self.o_data.eq(self.i_data), self.o_stall.eq(drop_bit | ~self.i_valid), self.o_error.eq(drop_bit & self.i_data & self.i_valid), ] return m
def elab(self, m: Module): buffering = Signal() # True if there is a value being buffered buffered_value = Signal.like(Value.cast(self.input.payload)) # Pipe valid and ready back and forth m.d.comb += [ self.input.ready.eq(~buffering | self.output.ready), self.output.valid.eq(buffering | self.input.valid), self.output.payload.eq( Mux(buffering, buffered_value, self.input.payload)) ] # Buffer when have incoming value but cannot output just now with m.If(~buffering & ~self.output.ready & self.input.valid): m.d.sync += buffering.eq(True) m.d.sync += buffered_value.eq(self.input.payload) # Handle cases when transfering out from buffer with m.If(buffering & self.output.ready): with m.If(self.input.valid): m.d.sync += buffered_value.eq(self.input.payload) with m.Else(): m.d.sync += buffering.eq(False) # Reset all state with m.If(self.reset): m.d.sync += buffering.eq(False) m.d.sync += buffered_value.eq(0)
def elaborate(self, platform): m = Module() led0 = platform.request("led", 0) with m.If(self.c > 10): m.d.sync += self.a.eq(self.a+1) with m.Else(): m.d.sync += self.a.eq(1) m.d.sync += self.b.eq(2) m.d.sync += self.c.eq(self.a * self.b) with m.If(self.c > 10): m.d.comb += led0.eq(0) with m.Else(): m.d.comb += led0.eq(1) return m
def elaborate(self, platform): m = Module() led0 = platform.request("led", 0) led1 = platform.request("led", 1) m.submodules.rdport = rdport = self.mem.read_port() with m.If(rdport.data == 255): m.d.comb += led0.eq(0) with m.Else(): m.d.comb += led0.eq(1) timer = Signal(range(round(100E6))) with m.If(timer > 100): m.d.comb += rdport.addr.eq(100) with m.Else(): m.d.comb += rdport.addr.eq(0) m.d.sync += timer.eq(timer + 1) m.d.comb += led1.o.eq(timer[-1]) return m
def elaborate(self, platform): m = Module() with m.If(self.op): m.d.comb += self.y.eq(self.a + self.b) with m.Else(): m.d.comb += self.y.eq(self.a - self.b) return m
def elaborate(self, platform): m = Module() beta = self.beta temp = Signal(signed(self.totalbits * 2)) n = len(self.coeff) j = Signal(range(1, n)) k = Signal.like(j) with m.FSM(reset='INIT') as algo: with m.State('INIT'): m.d.sync += self.done.eq(0) for i in range(n): m.d.sync += beta[i].eq(self.coeff[i]) m.d.sync += [k.eq(0), j.eq(1)] m.next = 'UPDATE' with m.FSM('UPDATE'): m.d.sync += temp.eq(beta[k] * (1 - self.t) + beta[k + 1] * self.t) m.next = 'MULTIPLICATIONFIX' # Fixed point arithmetic need fix # see multiplication as https://vha3.github.io/FixedPoint/FixedPoint.html with m.FSM('MULTIPLICATIONFIX'): m.d.sync += beta[k].eq( temp[self.fractionalbits:self.fractionalbits + self.totalbits]) with m.If(k != n - j): m.d.sync += k.eq(k + 1) m.next = 'UPDATE' with m.Else(): with m.If(j != n): m.d.sync += j.eq(j + 1) m.d.sync += k.eq(0) m.next = 'UPDATE' with m.Else(): m.next = 'FINISH' with m.FSM('FINISH'): m.d.sync += self.done.eq(1) m.next = 'FINISH' return m
def elaborate(self, platform): m = Module() stuff_bit = Signal() with m.FSM(domain="usb"): for i in range(5): with m.State(f"D{i}"): # Receiving '1' increments the bitstuff counter. with m.If(self.i_data): m.next = f"D{i+1}" # Receiving '0' resets the bitstuff counter. with m.Else(): m.next = "D0" with m.State("D5"): with m.If(self.i_data): # There's a '1', so indicate we might stall on the next loop. m.d.comb += self.o_will_stall.eq(1), m.next = "D6" with m.Else(): m.next = "D0" with m.State("D6"): m.d.comb += stuff_bit.eq(1) m.next = "D0" m.d.comb += [self.o_stall.eq(stuff_bit)] # flop outputs with m.If(stuff_bit): m.d.usb += self.o_data.eq(0), with m.Else(): m.d.usb += self.o_data.eq(self.i_data) return m
def elaborate(self, platform): m = Module() if self.own_register_window: m.submodules.reg_window = self.register_window # Add the registers that represent each of our signals. self.populate_ulpi_registers(m) # Generate logic to handle changes on each of our registers. first_element = True for address, signals in self._register_signals.items(): conditional = m.If if first_element else m.Elif first_element = False # If we're requesting a write on the given register, pass that to our # register window. with conditional(signals['write_requested']): # Keep track of when we'll be okay to start a write: # it's when there's a write request, we're not complete. # and the bus is idle. We'll use this below. request_write = \ signals['write_requested'] & \ ~self.register_window.done & \ self.bus_idle m.d.comb += [ # Control signals. signals['write_done'] .eq(self.register_window.done), # Register window signals. self.register_window.address .eq(address), self.register_window.write_data .eq(signals['write_value']), self.register_window.write_request .eq(request_write), # Status signals ] m.d.usb += self.busy.eq(request_write | self.register_window.busy) # If no register accesses are active, provide default signal values. with m.Else(): m.d.comb += self.register_window.write_request.eq(0) m.d.usb += self.busy.eq(self.register_window.busy) # Ensure our register window is never performing a read. m.d.comb += self.register_window.read_request.eq(0) return m
def elaborate(self, platform): m = Module() ac = Signal(self.width+1) ac_next = Signal.like(ac) temp = Signal.like(ac) q1 = Signal(self.width) q1_next = Signal.like(q1) i = Signal(range(self.width)) # combinatorial with m.If(ac >= self.y): m.d.comb += [temp.eq(ac-self.y), Cat(q1_next, ac_next).eq( Cat(1, q1, temp[0:self.width-1]))] with m.Else(): m.d.comb += [Cat(q1_next, ac_next).eq(Cat(q1, ac) << 1)] # synchronized with m.If(self.start): m.d.sync += [self.valid.eq(0), i.eq(0)] with m.If(self.y == 0): m.d.sync += [self.busy.eq(0), self.dbz.eq(1)] with m.Else(): m.d.sync += [self.busy.eq(1), self.dbz.eq(0), Cat(q1, ac).eq(Cat(Const(0, 1), self.x, Const(0, self.width)))] with m.Elif(self.busy): with m.If(i == self.width-1): m.d.sync += [self.busy.eq(0), self.valid.eq(1), i.eq(0), self.q.eq(q1_next), self.r.eq(ac_next >> 1)] with m.Else(): m.d.sync += [i.eq(i+1), ac.eq(ac_next), q1.eq(q1_next)] return m
def elaborate(self, platform): m = Module() m.submodules.ila = self.ila transaction_start = Rose(self.spi.cs) # Connect up our SPI transciever to our public interface. interface = SPIDeviceInterface(word_size=self.bits_per_word, clock_polarity=self.clock_polarity, clock_phase=self.clock_phase) m.submodules.spi = interface m.d.comb += [ interface.spi.connect(self.spi), # Always output the captured sample. interface.word_out.eq(self.ila.captured_sample) ] # Count where we are in the current transmission. current_sample_number = Signal(range(0, self.ila.sample_depth)) # Our first piece of data is latched in when the transaction # starts, so we'll move on to sample #1. with m.If(self.spi.cs): with m.If(transaction_start): m.d.sync += current_sample_number.eq(1) # From then on, we'll move to the next sample whenever we're finished # scanning out a word (and thus our current samples are latched in). # This only works correctly because the SPI interface will accept a word # more than one clock cycle after the edge which latches the new address # to read, allowing the backing memory to have a clock to latch in the # correct value. with m.Elif(interface.word_accepted): m.d.sync += current_sample_number.eq(current_sample_number + 1) # Whenever CS is low, we should be providing the very first sample, # so reset our sample counter to 0. with m.Else(): m.d.sync += current_sample_number.eq(0) # Ensure our ILA module outputs the right sample. m.d.sync += [self.ila.captured_sample_number.eq(current_sample_number)] # Convert our sync domain to the domain requested by the user, if necessary. if self.domain != "sync": m = DomainRenamer(self.domain)(m) return m
def elab(self, m: Module): # One signal for each input stream, indicating whether # there is a value being buffered: buffering = {name: Signal(name=f'buffering_{name}') for name in self.field_names} # A buffer for each input stream: buffered_values = \ {name: Signal(self.field_shapes[name], name=f'buffered_{name}') for name in self.field_names} # For each field of the concatenated output, present either the # buffered value if we have one, or else plumb through the input. for name in self.field_names: m.d.comb += self.output.payload[name].eq( Mux(buffering[name], buffered_values[name], self.inputs[name].payload)) # The output is valid if we have either a buffered value or a valid # input for every slice in the output. valid_or_buffering = (Cat(*[ep.valid for ep in self.inputs.values()]) | Cat(*buffering.values())) m.d.comb += self.output.valid.eq(valid_or_buffering.all()) for name, input in self.inputs.items(): # We can accept an input if the buffer is not occupied, # or if we can output this cycle. m.d.comb += input.ready.eq(~buffering[name] | self.output.is_transferring()) with m.If(input.valid): # Buffer it if the buffer is not occupied and we can't output. with m.If(~buffering[name] & ~self.output.is_transferring()): m.d.sync += buffering[name].eq(True) m.d.sync += buffered_values[name].eq(input.payload) # Buffer it if the buffer is occupied but we are outputting. with m.If(self.output.is_transferring()): m.d.sync += buffered_values[name].eq(input.payload) with m.Else(): with m.If(self.output.is_transferring()): m.d.sync += buffering[name].eq(False) # Reset all state with m.If(self.reset): for b in buffering.values(): m.d.sync += b.eq(False) for bv in buffered_values.values(): m.d.sync += bv.eq(0)
def elaborate(self, platform): m = Module() # Create a set of registers, and expose them over SPI. board_spi = platform.request("debug_spi") spi_registers = SPIRegisterInterface(default_read_value=-1) m.submodules.spi_registers = spi_registers # Identify ourselves as the SPI flash bridge. spi_registers.add_read_only_register(REGISTER_ID, read=0x53504946) # # SPI flash passthrough connections. # flash_sdo = Signal() spi_flash_bus = platform.request('spi_flash') spi_flash_passthrough = ECP5ConfigurationFlashInterface( bus=spi_flash_bus) m.submodules += spi_flash_passthrough m.d.comb += [ spi_flash_passthrough.sck.eq(board_spi.sck), spi_flash_passthrough.sdi.eq(board_spi.sdi), flash_sdo.eq(spi_flash_passthrough.sdo), ] # # Structural connections. # spi = synchronize(m, board_spi) # Select the passthrough or gateware SPI based on our chip-select values. gateware_sdo = Signal() with m.If(board_spi.cs): m.d.comb += board_spi.sdo.eq(gateware_sdo) with m.Else(): m.d.comb += board_spi.sdo.eq(flash_sdo) # Connect our register interface to our board SPI. m.d.comb += [ spi_registers.spi.sck.eq(spi.sck), spi_registers.spi.sdi.eq(spi.sdi), gateware_sdo.eq(spi_registers.spi.sdo), spi_registers.spi.cs.eq(spi.cs) ] return m
def elaborate(self, platform): m = Module() # Make the output `rollover` always equal to this comparison, # which will only be 1 for a single cycle every counter period. m.d.comb += self.rollover.eq(self.counter == self.limit - 1) # Conditionally reset the counter to 0 on rollover, otherwise # increment it. We could write the comparison out again here # to the same effect. with m.If(self.rollover): m.d.sync += self.counter.eq(0) with m.Else(): m.d.sync += self.counter.eq(self.counter + 1) return m
def elaborate(self, platform): m = Module() pkt_start = Signal() pkt_active = Signal() pkt_end = Signal() with m.FSM(domain="usb_io"): for i in range(5): with m.State(f"D{i}"): with m.If(self.i_valid): with m.If(self.i_data | self.i_se0): # Receiving '1' or SE0 early resets the packet start counter. m.next = "D0" with m.Else(): # Receiving '0' increments the packet start counter. m.next = f"D{i + 1}" with m.State("D5"): with m.If(self.i_valid): with m.If(self.i_se0): m.next = "D0" # once we get a '1', the packet is active with m.Elif(self.i_data): m.d.comb += pkt_start.eq(1) m.next = "PKT_ACTIVE" with m.State("PKT_ACTIVE"): m.d.comb += pkt_active.eq(1) with m.If(self.i_valid & self.i_se0): m.d.comb += [pkt_active.eq(0), pkt_end.eq(1)] m.next = "D0" # pass all of the outputs through a pipe stage m.d.comb += [ self.o_pkt_start.eq(pkt_start), self.o_pkt_active.eq(pkt_active), self.o_pkt_end.eq(pkt_end), ] return m
def elaborate(self, platform): m = Module() uart = platform.request("uart") clock_freq = int(60e6) char_freq = int(6e6) # Create our UART transmitter. transmitter = UARTTransmitter(divisor=int(clock_freq // 115200)) m.submodules.transmitter = transmitter stream = transmitter.stream # Create a counter that will let us transmit ten times per second. counter = Signal(range(0, char_freq)) with m.If(counter == (char_freq - 1)): m.d.sync += counter.eq(0) with m.Else(): m.d.sync += counter.eq(counter + 1) # Create a simple ROM with a message for ourselves... letters = Array(ord(i) for i in "Hello, world! \r\n") # ... and count through it whenever we send a letter. current_letter = Signal(range(0, len(letters))) with m.If(stream.ready): m.d.sync += current_letter.eq(current_letter + 1) # Hook everything up. m.d.comb += [ stream.payload.eq(letters[current_letter]), stream.valid.eq(counter == 0), uart.tx.o.eq(transmitter.tx), ] # If this platform has an output-enable control on its UART, drive it iff # we're actively driving a transmission. if hasattr(uart.tx, 'oe'): m.d.comb += uart.tx.oe.eq(transmitter.driving), # Turn on a single LED, just to show something's running. led = Cat(platform.request('led', i) for i in range(6)) m.d.comb += led.eq(~transmitter.tx) return m
def elaborate(self, platform): m = Module() # Attach our SPI interface. m.submodules.interface = self.interface self._connect_interface(m) # Split the command into our "write" and "address" signals. m.d.comb += [ self._is_write.eq(self.interface.command[-1]), self._address.eq(self.interface.command[0:-1]) ] # Create the control/write logic for each of our registers. for address, connections in self.registers.items(): self._elaborate_register(m, address, connections) # Build the logic to select the 'to_send' value, which is selected # from all of our registers according to the selected register address. first_item = True for address, connections in self.registers.items(): statement = m.If if first_item else m.Elif with statement(self._address == address): # Hook up the word-to-send signal either to the read value for the relevant # register, or to the default read value. if connections['read'] is not None: m.d.comb += self.interface.word_to_send.eq( connections['read']) else: m.d.comb += self.interface.word_to_send.eq( self.default_read_value) # We've already created the m.If; from now on, use m.Elif first_item = False # Finally, tie all non-handled register values to always respond with the default. with m.Else(): m.d.comb += self.interface.word_to_send.eq(self.default_read_value) return m
def elaborate(self, platform): m = Module() # neat way of setting carry flag res_and_carry = Cat(self.res, self.carry) m.d.comb += res_and_carry.eq( Mux(self.sub, self.src1 - self.src2, self.src1 + self.src2)) with m.If(self.sub): with m.If((self.src1[-1] != self.src2[-1]) & (self.src1[-1] != self.res[-1])): m.d.comb += self.overflow.eq(1) with m.Else(): # add with m.If((self.src1[-1] == self.src2[-1]) & (self.src1[-1] != self.res[-1])): m.d.comb += self.overflow.eq(1) return m
def elaborate(self, platform): m = Module() width = self._width # Instead of using a counter, we will use a sentinel bit in the shift # register to indicate when it is full. shift_reg = Signal(width + 1, reset=0b1) m.d.comb += self.o_data.eq(shift_reg[0:width]) m.d.usb_io += self.o_put.eq(shift_reg[width - 1] & ~shift_reg[width] & self.i_valid), with m.If(self.reset): m.d.usb_io += shift_reg.eq(1) with m.If(self.i_valid): with m.If(shift_reg[width]): m.d.usb_io += shift_reg.eq(Cat(self.i_data, Const(1))) with m.Else(): m.d.usb_io += shift_reg.eq(Cat(self.i_data, shift_reg[0:width])), return m
def elaborate(self, platform): m = Module() # Synchronize the USB signals at our I/O boundary. # Despite the assumptions made in ValentyUSB, this line rate recovery FSM # isn't enough to properly synchronize these inputs. We'll explicitly synchronize. sync_dp = synchronize(m, self._usbp, o_domain="usb_io") sync_dn = synchronize(m, self._usbn, o_domain="usb_io") ####################################################################### # Line State Recovery State Machine # # The receive path doesn't use a differential receiver. Because of # this there is a chance that one of the differential pairs will appear # to have changed to the new state while the other is still in the old # state. The following state machine detects transitions and waits an # extra sampling clock before decoding the state on the differential # pair. This transition period will only ever last for one clock as # long as there is no noise on the line. If there is enough noise on # the line then the data may be corrupted and the packet will fail the # data integrity checks. # dpair = Cat(sync_dp, sync_dn) # output signals for use by the clock recovery stage line_state_in_transition = Signal() with m.FSM(domain="usb_io") as fsm: m.d.usb_io += [ self.line_state_se0.eq(fsm.ongoing("SE0")), self.line_state_se1.eq(fsm.ongoing("SE1")), self.line_state_dj.eq(fsm.ongoing("DJ")), self.line_state_dk.eq(fsm.ongoing("DK")), ] # If we are in a transition state, then we can sample the pair and # move to the next corresponding line state. with m.State("DT"): m.d.comb += line_state_in_transition.eq(1) with m.Switch(dpair): with m.Case(0b10): m.next = "DJ" with m.Case(0b01): m.next = "DK" with m.Case(0b00): m.next = "SE0" with m.Case(0b11): m.next = "SE1" # If we are in a valid line state and the value of the pair changes, # then we need to move to the transition state. with m.State("DJ"): with m.If(dpair != 0b10): m.next = "DT" with m.State("DK"): with m.If(dpair != 0b01): m.next = "DT" with m.State("SE0"): with m.If(dpair != 0b00): m.next = "DT" with m.State("SE1"): with m.If(dpair != 0b11): m.next = "DT" ####################################################################### # Clock and Data Recovery # # The DT state from the line state recovery state machine is used to align to # transmit clock. The line state is sampled in the middle of the bit time. # # Example of signal relationships # ------------------------------- # line_state DT DJ DJ DJ DT DK DK DK DK DK DK DT DJ DJ DJ # line_state_valid ________----____________----____________----________----____ # bit_phase 0 0 1 2 3 0 1 2 3 0 1 2 0 1 2 # # We 4x oversample, so make the line_state_phase have # 4 possible values. line_state_phase = Signal(2) m.d.usb_io += self.line_state_valid.eq(line_state_phase == 1) with m.If(line_state_in_transition): m.d.usb_io += [ # re-align the phase with the incoming transition line_state_phase.eq(0), # make sure we never assert valid on a transition self.line_state_valid.eq(0), ] with m.Else(): # keep tracking the clock by incrementing the phase m.d.usb_io += line_state_phase.eq(line_state_phase + 1) return m
def elaborate(self, platform): m = Module() # Memory read and write ports. m.submodules.read = mem_read_port = self.mem.read_port(domain="usb") m.submodules.write = mem_write_port = self.mem.write_port(domain="usb") # Store the memory address of our active packet header, which will store # packet metadata like the packet size. header_location = Signal.like(mem_write_port.addr) write_location = Signal.like(mem_write_port.addr) # Read FIFO status. read_location = Signal.like(mem_read_port.addr) fifo_count = Signal.like(mem_read_port.addr, reset=0) fifo_new_data = Signal() # Current receive status. packet_size = Signal(16) # # Read FIFO logic. # m.d.comb += [ # We have data ready whenever there's data in the FIFO. self.stream.valid.eq((fifo_count != 0) & self.idle), # Our data_out is always the output of our read port... self.stream.payload.eq(mem_read_port.data), self.sampling.eq(mem_write_port.en) ] # Once our consumer has accepted our current data, move to the next address. with m.If(self.stream.ready & self.stream.valid): m.d.usb += read_location.eq(read_location + 1) m.d.comb += mem_read_port.addr.eq(read_location + 1) with m.Else(): m.d.comb += mem_read_port.addr.eq(read_location), # # FIFO count handling. # fifo_full = (fifo_count == self.mem_size) data_pop = Signal() data_push = Signal() m.d.comb += [ data_pop.eq(self.stream.ready & self.stream.valid), data_push.eq(fifo_new_data & ~fifo_full) ] # If we have both a read and a write, don't update the count, # as we've both added one and subtracted one. with m.If(data_push & data_pop): pass # Otherwise, add when data's added, and subtract when data's removed. with m.Elif(data_push): m.d.usb += fifo_count.eq(fifo_count + 1) with m.Elif(data_pop): m.d.usb += fifo_count.eq(fifo_count - 1) # # Core analysis FSM. # with m.FSM(domain="usb") as f: m.d.comb += [ self.idle.eq(f.ongoing("IDLE")), self.overrun.eq(f.ongoing("OVERRUN")), self.capturing.eq(f.ongoing("CAPTURE")), ] # IDLE: wait for an active receive. with m.State("IDLE"): # Wait until a transmission is active. # TODO: add triggering logic? with m.If(self.utmi.rx_active): m.next = "CAPTURE" m.d.usb += [ header_location.eq(write_location), write_location.eq(write_location + self.HEADER_SIZE_BYTES), packet_size.eq(0), ] # Capture data until the packet is complete. with m.State("CAPTURE"): byte_received = self.utmi.rx_valid & self.utmi.rx_active # Capture data whenever RxValid is asserted. m.d.comb += [ mem_write_port.addr.eq(write_location), mem_write_port.data.eq(self.utmi.rx_data), mem_write_port.en.eq(byte_received), fifo_new_data.eq(byte_received), ] # Advance the write pointer each time we receive a bit. with m.If(byte_received): m.d.usb += [ write_location.eq(write_location + 1), packet_size.eq(packet_size + 1) ] # If this would be filling up our data memory, # move to the OVERRUN state. with m.If(fifo_count == self.mem_size - 1 - self.HEADER_SIZE_BYTES): m.next = "OVERRUN" # If we've stopped receiving, move to the "finalize" state. with m.If(~self.utmi.rx_active): m.next = "EOP_1" # Optimization: if we didn't receive any data, there's no need # to create a packet. Clear our header from the FIFO and disarm. with m.If(packet_size == 0): m.next = "IDLE" m.d.usb += [write_location.eq(header_location)] with m.Else(): m.next = "EOP_1" # EOP: handle the end of the relevant packet. with m.State("EOP_1"): # Now that we're done, add the header to the start of our packet. # This will take two cycles, currently, as we're using a 2-byte header, # but we only have an 8-bit write port. m.d.comb += [ mem_write_port.addr.eq(header_location), mem_write_port.data.eq(packet_size[8:16]), mem_write_port.en.eq(1), fifo_new_data.eq(1) ] m.next = "EOP_2" with m.State("EOP_2"): # Add the second byte of our header. # Note that, if this is an adjacent read, we should have # just captured our packet header _during_ the stop turnaround. m.d.comb += [ mem_write_port.addr.eq(header_location + 1), mem_write_port.data.eq(packet_size[0:8]), mem_write_port.en.eq(1), fifo_new_data.eq(1) ] m.next = "IDLE" # BABBLE -- handles the case in which we've received a packet beyond # the allowable size in the USB spec with m.State("BABBLE"): # Trap here, for now. pass with m.State("OVERRUN"): # TODO: we should probably set an overrun flag and then emit an EOP, here? pass return m
def elaborate(self, platform): m = Module() current_address = Signal(6) current_write = Signal(8) # Keep our control signals low unless explicitly asserted. m.d.usb += [ self.ulpi_out_req.eq(0), self.ulpi_stop .eq(0), self.done .eq(0) ] with m.FSM(domain="usb") as fsm: # We're busy whenever we're not IDLE; indicate so. m.d.comb += self.busy.eq(~fsm.ongoing('IDLE')) # IDLE: wait for a request to be made with m.State('IDLE'): # Apply a NOP whenever we're idle. # # This doesn't technically help for normal ULPI # operation, as the controller should handle this, # but it cleans up the output in our tests and allows # this unit to be used standalone. m.d.usb += self.ulpi_data_out.eq(0) # Constantly latch in our arguments while IDLE. # We'll stop latching these in as soon as we're busy. m.d.usb += [ current_address .eq(self.address), current_write .eq(self.write_data) ] with m.If(self.read_request): m.next = 'START_READ' with m.If(self.write_request): m.next = 'START_WRITE' # # Read handling. # # START_READ: wait for the bus to be idle, so we can transmit. with m.State('START_READ'): # Wait for the bus to be idle. with m.If(~self.ulpi_dir): m.next = 'SEND_READ_ADDRESS' # Once it is, start sending our command. m.d.usb += [ self.ulpi_data_out .eq(self.COMMAND_REG_READ | self.address), self.ulpi_out_req .eq(1) ] # SEND_READ_ADDRESS: Request sending the read address, which we # start sending on the next clock cycle. Note that we don't want # to come into this state writing, as we need to lead with a # bus-turnaround cycle. with m.State('SEND_READ_ADDRESS'): m.d.usb += self.ulpi_out_req.eq(1) # If DIR has become asserted, we're being interrupted. # We'll have to restart the read after the interruption is over. with m.If(self.ulpi_dir): m.next = 'START_READ' m.d.usb += self.ulpi_out_req.eq(0) # If NXT becomes asserted without us being interrupted by # DIR, then the PHY has accepted the read. Release our write # request, so the next cycle can properly act as a bus turnaround. with m.Elif(self.ulpi_next): m.d.usb += [ self.ulpi_out_req .eq(0), self.ulpi_data_out .eq(0), ] m.next = 'READ_TURNAROUND' # READ_TURNAROUND: wait for the PHY to take control of the ULPI bus. with m.State('READ_TURNAROUND'): # After one cycle, we should have a data byte ready. m.next = 'READ_COMPLETE' # READ_COMPLETE: the ULPI read exchange is complete, and the read data is ready. with m.State('READ_COMPLETE'): m.next = 'IDLE' # Latch in the data, and indicate that we have new, valid data. m.d.usb += [ self.read_data .eq(self.ulpi_data_in), self.done .eq(1) ] # # Write handling. # # START_WRITE: wait for the bus to be idle, so we can transmit. with m.State('START_WRITE'): # Wait for the bus to be idle. with m.If(~self.ulpi_dir): m.next = 'SEND_WRITE_ADDRESS' # Once it is, start sending our command. m.d.usb += [ self.ulpi_data_out .eq(self.COMMAND_REG_WRITE | self.address), self.ulpi_out_req .eq(1) ] # SEND_WRITE_ADDRESS: Continue sending the write address until the # target device accepts it. with m.State('SEND_WRITE_ADDRESS'): m.d.usb += self.ulpi_out_req.eq(1) # If DIR has become asserted, we're being interrupted. # We'll have to restart the write after the interruption is over. with m.If(self.ulpi_dir): m.next = 'START_WRITE' m.d.usb += self.ulpi_out_req.eq(0) # Hold our address until the PHY has accepted the command; # and then move to presenting the PHY with the value to be written. with m.Elif(self.ulpi_next): m.d.usb += self.ulpi_data_out.eq(self.write_data) m.next = 'HOLD_WRITE' # Hold the write data on the bus until the device acknowledges it. with m.State('HOLD_WRITE'): m.d.usb += self.ulpi_out_req.eq(1) # Handle interruption. with m.If(self.ulpi_dir): m.next = 'START_WRITE' m.d.usb += self.ulpi_out_req.eq(0) # Hold the data present until the device has accepted it. # Once it has, pulse STP for a cycle to complete the transaction. with m.Elif(self.ulpi_next): m.d.usb += [ self.ulpi_data_out.eq(0), self.ulpi_stop.eq(1), ] m.next = 'STOPPING' with m.State('STOPPING'): m.d.usb += self.ulpi_stop.eq(0) # Check again for interruption since DIR may have # been asserted during the previous cycle. with m.If(self.ulpi_dir): m.next = 'START_WRITE' m.d.usb += self.ulpi_out_req.eq(0) with m.Else(): m.d.usb += [ self.ulpi_out_req.eq(0), self.done.eq(1) ] m.next = 'IDLE' return m
def elaborate(self, platform): m = Module() bit_stuffing_disabled = (self.op_mode == self.OP_MODE_NO_BIT_STUFFING) with m.FSM(domain="usb") as fsm: # Mark ourselves as busy whenever we're not in idle. m.d.comb += self.busy.eq(~fsm.ongoing('IDLE')) # IDLE: our transmitter is ready and with m.State('IDLE'): m.d.comb += self.ulpi_stp.eq(0) # Start once a transmit is started, and we can access the bus. with m.If(self.tx_valid & self.bus_idle): # If bit-stuffing is disabled, we'll need to prefix our transmission with a NOPID command. # In this case, we'll never accept the first byte (as we're not ready to transmit it, yet), # and thus TxReady will always be 0. with m.If(bit_stuffing_disabled): m.d.usb += self.ulpi_out_req.eq(1), m.d.comb += [ self.ulpi_data_out .eq(self.TRANSMIT_COMMAND), self.tx_ready .eq(0) ] # Otherwise, this transmission starts with a PID. Extract the PID from the first data byte # and present it as part of the Transmit Command. In this case, the NXT signal is # has the same meaning as the UTMI TxReady signal; and can be passed along directly. with m.Else(): m.d.usb += self.ulpi_out_req.eq(1), m.d.comb += [ self.ulpi_data_out .eq(self.TRANSMIT_COMMAND | self.tx_data[0:4]), self.tx_ready .eq(self.ulpi_nxt) ] # Once the PHY has accepted the command byte, we're ready to move into our main transmit state. with m.If(self.ulpi_nxt): m.next = 'TRANSMIT' # TRANSMIT: we're in the body of a transmit; the UTMI and ULPI interface signals # are roughly equivalent; we'll just pass them through. with m.State('TRANSMIT'): m.d.comb += [ self.ulpi_data_out .eq(self.tx_data), self.tx_ready .eq(self.ulpi_nxt), self.ulpi_stp .eq(0), ] # Once the transmission has ended, we'll need to issue a ULPI stop. with m.If(~self.tx_valid): m.d.usb += self.ulpi_out_req.eq(0), m.next = 'IDLE' # STOP: our packet has just terminated; we'll generate a ULPI stop event for a single cycle. # [ULPI: 3.8.2.2] m.d.comb += self.ulpi_stp.eq(1) # If we've disabled bit stuffing, we'll want to termainate by generating a bit-stuff error. with m.If(bit_stuffing_disabled): # Drive 0xFF as we stop, to generate a bit-stuff error. [ULPI: 3.8.2.3] m.d.comb += self.ulpi_data_out .eq(0xFF) # Otherwise, we'll generate a normal stop. with m.Else(): m.d.comb += self.ulpi_data_out .eq(0) return m
def elaborate(self, platform): m = Module() # Create the component parts of our ULPI interfacing hardware. m.submodules.register_window = register_window = ULPIRegisterWindow() m.submodules.control_translator = control_translator = ULPIControlTranslator(register_window=register_window) m.submodules.rxevent_decoder = rxevent_decoder = ULPIRxEventDecoder(ulpi_bus=self.ulpi) m.submodules.transmit_translator = transmit_translator = ULPITransmitTranslator() # If we're choosing to honor any registers defined in the platform file, apply those # before continuing with elaboration. if self.use_platform_registers and hasattr(platform, 'ulpi_extra_registers'): for address, value in platform.ulpi_extra_registers.items(): self.add_extra_register(address, value) # Some platforms may need to have a raw clock domain for their I/O; e.g. if they need # to do some simple processing for their internal clock domain. if self.handle_clocking and hasattr(platform, 'ulpi_raw_clock_domain'): raw_clock_domain = platform.ulpi_raw_clock_domain else: raw_clock_domain = 'usb' # Add any extra registers provided by the user to our control translator. for address, values in self._extra_registers.items(): control_translator.add_composite_register(m, address, values['value'], reset_value=values['default']) # Keep track of when any of our components are busy any_busy = \ register_window.busy | \ transmit_translator.busy | \ control_translator.busy | \ self.ulpi.dir # If we're handling ULPI clocking, do so. if self.handle_clocking: # We can't currently handle bidirectional clock lines, as we don't know if they # should be used in input or output modes. if hasattr(self.ulpi.clk, 'oe'): raise TypeError("ULPI records with bidirectional clock lines require manual handling.") # Just Input (TM) and Just Output (TM) clocks are simpler: we know how to drive them. elif hasattr(self.ulpi.clk, 'o'): m.d.comb += self.ulpi.clk.eq(ClockSignal(raw_clock_domain)) elif hasattr(self.ulpi.clk, 'i'): m.d.comb += ClockSignal(raw_clock_domain).eq(self.ulpi.clk) # Clocks that don't seem to be I/O pins aren't what we're expecting; fail out. else: raise TypeError(f"ULPI `clk` was an unexpected type {type(self.ulpi.clk)}." \ " You may need to handle clocking manually.") # Hook up our reset signal iff our ULPI bus has one. if hasattr(self.ulpi, 'rst'): m.d.comb += self.ulpi.rst .eq(ResetSignal(raw_clock_domain)), # Connect our ULPI control signals to each of our subcomponents. m.d.comb += [ # Drive the bus whenever the target PHY isn't. self.ulpi.data.oe .eq(~self.ulpi.dir), # Generate our busy signal. self.busy .eq(any_busy), # Connect our data inputs to the event decoder. # Note that the event decoder is purely passive. rxevent_decoder.register_operation_in_progress.eq(register_window.busy), self.last_rx_command .eq(rxevent_decoder.last_rx_command), # Connect our inputs to our transmit translator. transmit_translator.ulpi_nxt .eq(self.ulpi.nxt), transmit_translator.op_mode .eq(self.op_mode), transmit_translator.bus_idle .eq(~control_translator.busy & ~self.ulpi.dir), transmit_translator.tx_data .eq(self.tx_data), transmit_translator.tx_valid .eq(self.tx_valid), self.tx_ready .eq(transmit_translator.tx_ready), # Connect our inputs to our control translator / register window. control_translator.bus_idle .eq(~transmit_translator.busy), register_window.ulpi_data_in .eq(self.ulpi.data.i), register_window.ulpi_dir .eq(self.ulpi.dir), register_window.ulpi_next .eq(self.ulpi.nxt), ] # Control our the source of our ULPI data output. # If a transmit request is active, prioritize that over # any register reads/writes. with m.If(transmit_translator.ulpi_out_req): m.d.comb += [ self.ulpi.data.o .eq(transmit_translator.ulpi_data_out), self.ulpi.stp .eq(transmit_translator.ulpi_stp) ] # Otherwise, yield control to the register handler. # This is a slight optimization: since it properly generates NOPs # while not in use, we can let it handle idle, as well, saving a mux. with m.Else(): m.d.comb += [ self.ulpi.data.o .eq(register_window.ulpi_data_out), self.ulpi.stp .eq(register_window.ulpi_stop) ] # Connect our RxEvent status signals from our RxEvent decoder. for signal_name, _ in self.RXEVENT_STATUS_SIGNALS: signal = getattr(rxevent_decoder, signal_name) m.d.comb += self.__dict__[signal_name].eq(signal) # Connect our control signals through the control translator. for signal_name, _ in self.CONTROL_SIGNALS: signal = getattr(control_translator, signal_name) m.d.comb += signal.eq(self.__dict__[signal_name]) # RxActive handler: # A transmission starts when DIR goes high with NXT, or when an RxEvent indicates # a switch from RxActive = 0 to RxActive = 1. A transmission stops when DIR drops low, # or when the RxEvent RxActive bit drops from 1 to 0, or an error occurs.A dir_rising_edge = Rose(self.ulpi.dir.i, domain="usb") dir_based_start = dir_rising_edge & self.ulpi.nxt with m.If(~self.ulpi.dir | rxevent_decoder.rx_stop): # TODO: this should probably also trigger if RxError m.d.usb += self.rx_active.eq(0) with m.Elif(dir_based_start | rxevent_decoder.rx_start): m.d.usb += self.rx_active.eq(1) # Data-out: we'll connect this almost direct through from our ULPI # interface, as it's essentially the same as in the UTMI spec. We'll # add a one cycle processing delay so it matches the rest of our signals. # RxValid: equivalent to NXT whenever a Rx is active. m.d.usb += [ self.rx_data .eq(self.ulpi.data.i), self.rx_valid .eq(self.ulpi.nxt & self.rx_active) ] return m
def elaborate(self, platform) -> Module: """build the module""" m = Module() sync = m.d.sync comb = m.d.comb nrzidecoder = NRZIDecoder(self.clk_freq) m.submodules.nrzi_decoder = nrzidecoder framedata_shifter = InputShiftRegister(24) m.submodules.framedata_shifter = framedata_shifter output_pulser = EdgeToPulse() m.submodules.output_pulser = output_pulser active_channel = Signal(3) # counts the number of bits output bit_counter = Signal(8) # counts the bit position inside a nibble nibble_counter = Signal(3) # counts, how many 0 bits it got in a row sync_bit_counter = Signal(4) comb += [ nrzidecoder.nrzi_in.eq(self.adat_in), self.synced_out.eq(nrzidecoder.running), self.recovered_clock_out.eq(nrzidecoder.recovered_clock_out), ] with m.FSM(): # wait for SYNC with m.State("WAIT_SYNC"): # reset invalid frame bit to be able to start again with m.If(nrzidecoder.invalid_frame_in): sync += nrzidecoder.invalid_frame_in.eq(0) with m.If(nrzidecoder.running): sync += [ bit_counter.eq(0), nibble_counter.eq(0), active_channel.eq(0), output_pulser.edge_in.eq(0) ] with m.If(nrzidecoder.data_out_en): m.d.sync += sync_bit_counter.eq(Mux(nrzidecoder.data_out, 0, sync_bit_counter + 1)) with m.If(sync_bit_counter == 9): m.d.sync += sync_bit_counter.eq(0) m.next = "READ_FRAME" with m.State("READ_FRAME"): # at which bit of bit_counter to output sample data at output_at = Signal(8) # user bits have been read with m.If(bit_counter == 5): sync += [ # output user bits self.user_data_out.eq(framedata_shifter.value_out[0:4]), # at bit 35 the first channel has been read output_at.eq(35) ] # when each channel has been read, output the channel's sample with m.If((bit_counter > 5) & (bit_counter == output_at)): sync += [ self.output_enable.eq(1), self.addr_out.eq(active_channel), self.sample_out.eq(framedata_shifter.value_out), output_at.eq(output_at + 30), active_channel.eq(active_channel + 1) ] with m.Else(): sync += self.output_enable.eq(0) # we work and count only when we get # a new bit fron the NRZI decoder with m.If(nrzidecoder.data_out_en): comb += [ framedata_shifter.bit_in.eq(nrzidecoder.data_out), # skip sync bit, which is first framedata_shifter.enable_in.eq(~(nibble_counter == 0)) ] sync += [ nibble_counter.eq(nibble_counter + 1), bit_counter.eq(bit_counter + 1), ] # check 4b/5b sync bit with m.If((nibble_counter == 0) & ~nrzidecoder.data_out): sync += nrzidecoder.invalid_frame_in.eq(1) m.next = "WAIT_SYNC" with m.Else(): sync += nrzidecoder.invalid_frame_in.eq(0) with m.If(nibble_counter >= 4): sync += nibble_counter.eq(0) # 239 channel bits and 5 user bits (including sync bits) with m.If(bit_counter >= (239 + 5)): sync += [ bit_counter.eq(0), output_pulser.edge_in.eq(1) ] m.next = "READ_SYNC" with m.Else(): comb += framedata_shifter.enable_in.eq(0) with m.If(~nrzidecoder.running): m.next = "WAIT_SYNC" # read the sync bits with m.State("READ_SYNC"): sync += [ self.output_enable.eq(output_pulser.pulse_out), self.addr_out.eq(active_channel), self.sample_out.eq(framedata_shifter.value_out), ] with m.If(nrzidecoder.data_out_en): sync += [ nibble_counter.eq(0), bit_counter.eq(bit_counter + 1), ] with m.If(bit_counter == 9): comb += [ framedata_shifter.enable_in.eq(0), framedata_shifter.clear_in.eq(1), ] #check last sync bit before sync trough with m.If((bit_counter == 0) & ~nrzidecoder.data_out): sync += nrzidecoder.invalid_frame_in.eq(1) m.next = "WAIT_SYNC" #check all the null bits in the sync trough with m.Elif((bit_counter > 0) & nrzidecoder.data_out): sync += nrzidecoder.invalid_frame_in.eq(1) m.next = "WAIT_SYNC" with m.Elif((bit_counter == 10) & ~nrzidecoder.data_out): sync += [ bit_counter.eq(0), nibble_counter.eq(0), active_channel.eq(0), output_pulser.edge_in.eq(0), nrzidecoder.invalid_frame_in.eq(0) ] m.next = "READ_FRAME" with m.Else(): sync += nrzidecoder.invalid_frame_in.eq(0) with m.If(~nrzidecoder.running): m.next = "WAIT_SYNC" return m
def elaborate(self, platform): m = Module() # Generate our clock domains. clocking = LunaECP5DomainGenerator(clock_frequencies=CLOCK_FREQUENCIES) m.submodules.clocking = clocking registers = JTAGRegisterInterface(default_read_value=0xDEADBEEF) m.submodules.registers = registers # Simple applet ID register. registers.add_read_only_register(REGISTER_ID, read=0x54455354) # LED test register. led_reg = registers.add_register(REGISTER_LEDS, size=6, name="leds", reset=0b111111) led_out = Cat( [platform.request("led", i, dir="o") for i in range(0, 6)]) m.d.comb += led_out.eq(led_reg) # # Target power test register. # Note: these values assume you've populated the correct AP22814 for # your revision (AP22814As for rev0.2+, and AP22814Bs for rev0.1). # bits [1:0]: 0 = power off # 1 = provide A-port VBUS # 2 = pass through target VBUS # power_test_reg = Signal(3) power_test_write_strobe = Signal() power_test_write_value = Signal(2) registers.add_sfr(REGISTER_TARGET_POWER, read=power_test_reg, write_strobe=power_test_write_strobe, write_signal=power_test_write_value) # Store the values for our enable bits. with m.If(power_test_write_strobe): m.d.sync += power_test_reg[0:2].eq(power_test_write_value) # Decode the enable bits and control the two power supplies. power_a_port = platform.request("power_a_port") power_passthrough = platform.request("pass_through_vbus") with m.If(power_test_reg[0:2] == 1): m.d.comb += [power_a_port.eq(1), power_passthrough.eq(0)] with m.Elif(power_test_reg[0:2] == 2): m.d.comb += [power_a_port.eq(0), power_passthrough.eq(1)] with m.Else(): m.d.comb += [power_a_port.eq(0), power_passthrough.eq(0)] # # User IO GPIO registers. # # Data direction register. user_io_dir = registers.add_register(REGISTER_USER_IO_DIR, size=2) # Pin (input) state register. user_io_in = Signal(2) registers.add_sfr(REGISTER_USER_IO_IN, read=user_io_in) # Output value register. user_io_out = registers.add_register(REGISTER_USER_IO_OUT, size=2) # Grab and connect each of our user-I/O ports our GPIO registers. for i in range(2): pin = platform.request("user_io", i) m.d.comb += [ pin.oe.eq(user_io_dir[i]), user_io_in[i].eq(pin.i), pin.o.eq(user_io_out[i]) ] # # ULPI PHY windows # self.add_ulpi_registers(m, platform, ulpi_bus="target_phy", register_base=REGISTER_TARGET_ADDR) self.add_ulpi_registers(m, platform, ulpi_bus="host_phy", register_base=REGISTER_HOST_ADDR) self.add_ulpi_registers(m, platform, ulpi_bus="sideband_phy", register_base=REGISTER_SIDEBAND_ADDR) # # HyperRAM test connections. # ram_bus = platform.request('ram') psram = HyperRAMInterface(bus=ram_bus, **platform.ram_timings) m.submodules += psram psram_address_changed = Signal() psram_address = registers.add_register( REGISTER_RAM_REG_ADDR, write_strobe=psram_address_changed) registers.add_sfr(REGISTER_RAM_VALUE, read=psram.read_data) # Hook up our PSRAM. m.d.comb += [ ram_bus.reset.eq(0), psram.single_page.eq(0), psram.perform_write.eq(0), psram.register_space.eq(1), psram.final_word.eq(1), psram.start_transfer.eq(psram_address_changed), psram.address.eq(psram_address), ] return m
def elaborate(self, platform): m = Module() # # Transciever state. # # Handle our PID-sequence reset. # Note that we store the _inverse_ of our data PID, as we'll toggle our DATA PID # before sending. with m.If(self.reset_sequence): m.d.usb += self.data_pid.eq(~self.start_with_data1) # # Transmit buffer. # # Our USB connection imposed a few requirements on our stream: # 1) we must be able to transmit packets at a full rate; i.e. # must be asserted from the start to the end of our transfer; and # 2) we must be able to re-transmit data if a given packet is not ACK'd. # # Accordingly, we'll buffer a full USB packet of data, and then transmit # it once either a) our buffer is full, or 2) the transfer ends (last=1). # # This implementation is double buffered; so a buffer fill can be pipelined # with a transmit. # # We'll create two buffers; so we can fill one as we empty the other. buffer = Array(Memory(width=8, depth=self._max_packet_size, name=f"transmit_buffer_{i}") for i in range(2)) buffer_write_ports = Array(buffer[i].write_port(domain="usb") for i in range(2)) buffer_read_ports = Array(buffer[i].read_port(domain="usb") for i in range(2)) m.submodules.read_port_0, m.submodules.read_port_1 = buffer_read_ports m.submodules.write_port_0, m.submodules.write_port_1 = buffer_write_ports # Create values equivalent to the buffer numbers for our read and write buffer; which switch # whenever we swap our two buffers. write_buffer_number = self.buffer_toggle read_buffer_number = ~self.buffer_toggle # Create a shorthand that refers to the buffer to be filled; and the buffer to send from. # We'll call these the Read and Write buffers. buffer_write = buffer_write_ports[write_buffer_number] buffer_read = buffer_read_ports[read_buffer_number] # Buffer state tracking: # - Our ``fill_count`` keeps track of how much data is stored in a given buffer. # - Our ``stream_ended`` bit keeps track of whether the stream ended while filling up # the given buffer. This indicates that the buffer cannot be filled further; and, when # ``generate_zlps`` is enabled, is used to determine if the given buffer should end in # a short packet; which determines whether ZLPs are emitted. buffer_fill_count = Array(Signal(range(0, self._max_packet_size + 1)) for _ in range(2)) buffer_stream_ended = Array(Signal(name=f"stream_ended_in_buffer{i}") for i in range(2)) # Create shortcuts to active fill_count / stream_ended signals for the buffer being written. write_fill_count = buffer_fill_count[write_buffer_number] write_stream_ended = buffer_stream_ended[write_buffer_number] # Create shortcuts to the fill_count / stream_ended signals for the packet being sent. read_fill_count = buffer_fill_count[read_buffer_number] read_stream_ended = buffer_stream_ended[read_buffer_number] # Keep track of our current send position; which determines where we are in the packet. send_position = Signal(range(0, self._max_packet_size + 1)) # Shortcut names. in_stream = self.transfer_stream out_stream = self.packet_stream # Use our memory's two ports to capture data from our transfer stream; and two emit packets # into our packet stream. Since we'll never receive to anywhere else, or transmit to anywhere else, # we can just unconditionally connect these. m.d.comb += [ # We'll only ever -write- data from our input stream... buffer_write_ports[0].data .eq(in_stream.payload), buffer_write_ports[0].addr .eq(write_fill_count), buffer_write_ports[1].data .eq(in_stream.payload), buffer_write_ports[1].addr .eq(write_fill_count), # ... and we'll only ever -send- data from the Read buffer. buffer_read.addr .eq(send_position), out_stream.payload .eq(buffer_read.data), # We're ready to receive data iff we have space in the buffer we're currently filling. in_stream.ready .eq((write_fill_count != self._max_packet_size) & ~write_stream_ended), buffer_write.en .eq(in_stream.valid & in_stream.ready) ] # Increment our fill count whenever we accept new data. with m.If(buffer_write.en): m.d.usb += write_fill_count.eq(write_fill_count + 1) # If the stream ends while we're adding data to the buffer, mark this as an ended stream. with m.If(in_stream.last & buffer_write.en): m.d.usb += write_stream_ended.eq(1) # Shortcut for when we need to deal with an in token. # Pulses high an interpacket delay after receiving an IN token. in_token_received = self.active & self.tokenizer.is_in & self.tokenizer.ready_for_response with m.FSM(domain='usb'): # WAIT_FOR_DATA -- We don't yet have a full packet to transmit, so we'll capture data # to fill the our buffer. At full throughput, this state will never be reached after # the initial post-reset fill. with m.State("WAIT_FOR_DATA"): # We can't yet send data; so NAK any packet requests. m.d.comb += self.handshakes_out.nak.eq(in_token_received) # If we have valid data that will end our packet, we're no longer waiting for data. # We'll now wait for the host to request data from us. packet_complete = (write_fill_count + 1 == self._max_packet_size) will_end_packet = packet_complete | in_stream.last with m.If(in_stream.valid & will_end_packet): # If we've just finished a packet, we now have data we can send! with m.If(packet_complete | in_stream.last): m.next = "WAIT_TO_SEND" m.d.usb += [ # We're now ready to take the data we've captured and _transmit_ it. # We'll swap our read and write buffers, and toggle our data PID. self.buffer_toggle .eq(~self.buffer_toggle), self.data_pid[0] .eq(~self.data_pid[0]), # Mark our current stream as no longer having ended. read_stream_ended .eq(0) ] # WAIT_TO_SEND -- we now have at least a buffer full of data to send; we'll # need to wait for an IN token to send it. with m.State("WAIT_TO_SEND"): m.d.usb += send_position .eq(0), # Once we get an IN token, move to sending a packet. with m.If(in_token_received): # If we have a packet to send, send it. with m.If(read_fill_count): m.next = "SEND_PACKET" m.d.usb += out_stream.first .eq(1) # Otherwise, we entered a transmit path without any data in the buffer. with m.Else(): m.d.comb += [ # Send a ZLP... out_stream.valid .eq(1), out_stream.last .eq(1), ] # ... and clear the need to follow up with one, since we've just sent a short packet. m.d.usb += read_stream_ended.eq(0) m.next = "WAIT_FOR_ACK" with m.State("SEND_PACKET"): last_packet = (send_position + 1 == read_fill_count) m.d.comb += [ # We're always going to be sending valid data, since data is always # available from our memory. out_stream.valid .eq(1), # Let our transmitter know when we've reached our last packet. out_stream.last .eq(last_packet) ] # Once our transmitter accepts our data... with m.If(out_stream.ready): m.d.usb += [ # ... move to the next byte in our packet ... send_position .eq(send_position + 1), # ... and mark our packet as no longer the first. out_stream.first .eq(0) ] # Move our memory pointer to its next position. m.d.comb += buffer_read.addr .eq(send_position + 1), # If we've just sent our last packet, we're now ready to wait for a # response from our host. with m.If(last_packet): m.next = 'WAIT_FOR_ACK' # WAIT_FOR_ACK -- We've just sent a packet; but don't know if the host has # received it correctly. We'll wait to see if the host ACKs. with m.State("WAIT_FOR_ACK"): # If the host does ACK... with m.If(self.handshakes_in.ack): # ... clear the data we've sent from our buffer. m.d.usb += read_fill_count.eq(0) # Figure out if we'll need to follow up with a ZLP. If we have ZLP generation enabled, # we'll make sure we end on a short packet. If this is max-packet-size packet _and_ our # transfer ended with this packet; we'll need to inject a ZLP. follow_up_with_zlp = \ self.generate_zlps & (read_fill_count == self._max_packet_size) & read_stream_ended # If we're following up with a ZLP, move back to our "wait to send" state. # Since we've now cleared our fill count; this next go-around will emit a ZLP. with m.If(follow_up_with_zlp): m.d.usb += self.data_pid[0].eq(~self.data_pid[0]), m.next = "WAIT_TO_SEND" # Otherwise, there's a possibility we already have a packet-worth of data waiting # for us in our "write buffer", which we've been filling in the background. # If this is the case, we'll flip which buffer we're working with, toggle our data pid, # and then ready ourselves for transmit. packet_completing = in_stream.valid & ((write_fill_count + 1 == self._max_packet_size) | in_stream.last) with m.Elif(~in_stream.ready | packet_completing): m.next = "WAIT_TO_SEND" m.d.usb += [ self.buffer_toggle .eq(~self.buffer_toggle), self.data_pid[0] .eq(~self.data_pid[0]), read_stream_ended .eq(0) ] # If neither of the above conditions are true; we now don't have enough data to send. # We'll wait for enough data to transmit. with m.Else(): m.next = "WAIT_FOR_DATA" # If the host starts a new packet without ACK'ing, we'll need to retransmit. # We'll move back to our "wait for token" state without clearing our buffer. with m.If(self.tokenizer.new_token): m.next = 'WAIT_TO_SEND' return m
def elaborate(self, platform): m = Module() # Grab signals that detect when we should shift in and out. sample_edge, output_edge = self.spi_edge_detectors(m) # We'll use separate buffers for transmit and receive, # as this makes the code a little more readable. bit_count = Signal(range(0, self.word_size), reset=0) current_tx = Signal.like(self.word_out) current_rx = Signal.like(self.word_in) # Signal that tracks if this is our first bit of the word. is_first_bit = Signal() # A word is ready one cycle after we complete a transaction # (and latch in the next word to be sent). with m.If(self.word_accepted): m.d.sync += [self.word_in.eq(current_rx), self.word_complete.eq(1)] with m.Else(): m.d.sync += self.word_complete.eq(0) # De-assert our control signals unless explicitly asserted. m.d.sync += [ self.word_accepted.eq(0), ] # If the chip is selected, process our I/O: chip_selected = self.spi.cs if not self.cs_idles_high else ~self.spi.cs with m.If(chip_selected): # Shift in data on each sample edge. with m.If(sample_edge): m.d.sync += [bit_count.eq(bit_count + 1), is_first_bit.eq(0)] if self.msb_first: m.d.sync += current_rx.eq( Cat(self.spi.sdi, current_rx[:-1])) else: m.d.sync += current_rx.eq(Cat(current_rx[1:], self.spi.sdi)) # If we're just completing a word, handle I/O. with m.If(bit_count + 1 == self.word_size): m.d.sync += [ self.word_accepted.eq(1), current_tx.eq(self.word_out) ] # Shift out data on each output edge. with m.If(output_edge): if self.msb_first: m.d.sync += Cat(current_tx[1:], self.spi.sdo).eq(current_tx) else: m.d.sync += Cat(self.spi.sdo, current_tx[:-1]).eq(current_tx) with m.Else(): m.d.sync += [ current_tx.eq(self.word_out), bit_count.eq(0), is_first_bit.eq(1) ] return m
def elaborate(self, platform): m = Module() spi = self.spi sample_edge = Fell(spi.sck, domain="sync") # Bit counter: counts the number of bits received. max_bit_count = max(self.word_size, self.command_size) bit_count = Signal(range(0, max_bit_count + 1)) # Shift registers for our command and data. current_command = Signal.like(self.command) current_word = Signal.like(self.word_received) # De-assert our control signals unless explicitly asserted. m.d.sync += [self.command_ready.eq(0), self.word_complete.eq(0)] with m.FSM() as fsm: m.d.comb += [ self.idle.eq(fsm.ongoing('IDLE')), self.stalled.eq(fsm.ongoing('STALL')), ] # STALL: entered when we can't accept new bits -- either when # CS starts asserted, or when we've received more data than expected. with m.State("STALL"): # Wait for CS to clear. with m.If(~spi.cs): m.next = 'IDLE' # We ignore all data until chip select is asserted, as that data Isn't For Us (TM). # We'll spin and do nothing until the bus-master addresses us. with m.State('IDLE'): m.d.sync += bit_count.eq(0) with m.If(spi.cs): m.next = 'RECEIVE_COMMAND' # Once CS is low, we'll shift in our command. with m.State('RECEIVE_COMMAND'): # If CS is de-asserted early; our transaction is being aborted. with m.If(~spi.cs): m.next = 'IDLE' # Continue shifting in data until we have a full command. with m.If(bit_count < self.command_size): with m.If(sample_edge): m.d.sync += [ bit_count.eq(bit_count + 1), current_command.eq( Cat(spi.sdi, current_command[:-1])) ] # ... and then pass that command out to our controller. with m.Else(): m.d.sync += [ bit_count.eq(0), self.command_ready.eq(1), self.command.eq(current_command) ] m.next = 'PROCESSING' # Give our controller a wait state to prepare any response they might want to... with m.State('PROCESSING'): m.next = 'LATCH_OUTPUT' # ... and then latch in the response to transmit. with m.State('LATCH_OUTPUT'): m.d.sync += current_word.eq(self.word_to_send) m.next = 'SHIFT_DATA' # Finally, exchange data. with m.State('SHIFT_DATA'): # If CS is de-asserted early; our transaction is being aborted. with m.If(~spi.cs): m.next = 'IDLE' m.d.sync += spi.sdo.eq(current_word[-1]) # Continue shifting data until we have a full word. with m.If(bit_count < self.word_size): with m.If(sample_edge): m.d.sync += [ bit_count.eq(bit_count + 1), current_word.eq(Cat(spi.sdi, current_word[:-1])) ] # ... and then output that word on our bus. with m.Else(): m.d.sync += [ bit_count.eq(0), self.word_complete.eq(1), self.word_received.eq(current_word) ] # Stay in the stall state until CS is de-asserted. m.next = 'STALL' return m