示例#1
0
 def __init__(self):
     self.start_run = Signal()
     self.all_output_finished = Signal()
     self.in_store_ready = Signal()
     self.fifo_has_space = Signal()
     self.gate = Signal()
示例#2
0
    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
示例#3
0
    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
示例#4
0
    def __init__(self,
                 *,
                 bus,
                 strobe_length=2,
                 in_skew=None,
                 out_skew=None,
                 clock_skew=None):
        """
        Parmeters:
            bus           -- The RAM record that should be connected to this RAM chip.
            strobe_length -- The number of fast-clock cycles any strobe should be asserted for.
            data_skews    -- If provided, adds an input delay to each line of the data input.
                             Can be provided as a single delay number, or an interable of eight
                             delays to separately delay each of the input lines.
        """

        self.in_skew = in_skew
        self.out_skew = out_skew
        self.clock_skew = clock_skew

        #
        # I/O port.
        #
        self.bus = bus
        self.reset = Signal()

        # Control signals.
        self.address = Signal(32)
        self.register_space = Signal()
        self.perform_write = Signal()
        self.single_page = Signal()
        self.start_transfer = Signal()
        self.final_word = Signal()

        # Status signals.
        self.idle = Signal()
        self.new_data_ready = Signal()

        # Data signals.
        self.read_data = Signal(16)
        self.write_data = Signal(16)
示例#5
0
class SPICommandInterface(Elaboratable):
    """ Variant of an SPIDeviceInterface that accepts command-prefixed data.

    I/O signals:
        I: sck           -- SPI clock, from the SPI master
        I: sdi           -- SPI data in
        O: sdo           -- SPI data out
        I: cs            -- chip select, active high (as we assume your I/O will use PinsN)

        O: command       -- the command read from the SPI bus
        O: command_ready -- a new command is ready

        O: word_received -- the most recent word received
        O: word_complete -- strobe indicating a new word is present on word_in
        I: word_to_send  -- the word to be loaded; latched in on next word_complete and while cs is low

        O: idle          -- true iff the register interface is currently doing nothing
        O: stalled       -- true iff the register interface cannot accept data until this transaction ends
    """
    def __init__(self, command_size=8, word_size=32):

        self.command_size = command_size
        self.word_size = word_size

        #
        # I/O port.
        #

        # SPI
        self.spi = SPIBus()

        # Command I/O.
        self.command = Signal(self.command_size)
        self.command_ready = Signal()

        # Data I/O
        self.word_received = Signal(self.word_size)
        self.word_to_send = Signal.like(self.word_received)
        self.word_complete = Signal()

        # Status
        self.idle = Signal()
        self.stalled = Signal()

    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
示例#6
0
    def __init__(self):

        #
        # I/O port.
        #

        self.tx_data = Signal(8)
        self.tx_valid = Signal()
        self.tx_ready = Signal()

        self.op_mode = Signal(2)
        self.bus_idle = Signal()

        self.ulpi_out_req = Signal()
        self.ulpi_data_out = Signal.like(self.tx_data)
        self.ulpi_nxt = Signal()
        self.ulpi_stp = Signal()

        self.busy = Signal()
示例#7
0
    def elaborate(self, platform):
        m = Module()

        #
        # Delayed input and output.
        #

        if self.in_skew is not None:
            data_in = delay(m, self.bus.dq.i, self.in_skew)
        else:
            data_in = self.bus.dq.i

        if self.out_skew is not None:
            data_out = Signal.like(self.bus.dq.o)
            delay(m, data_out, self.out_skew, out=self.bus.dq.o)
        else:
            data_out = self.bus.dq.o

        #
        # Transaction clock generator.
        #
        advance_clock = Signal()
        reset_clock = Signal()

        if self.clock_skew is not None:
            out_clock = Signal()
            delay(m, out_clock, self.clock_skew, out=self.bus.clk)
        else:
            out_clock = self.bus.clk

        with m.If(reset_clock):
            m.d.fast += out_clock.eq(0)
        with m.Elif(advance_clock):
            m.d.fast += out_clock.eq(~out_clock)

        #
        # Latched control/addressing signals.
        #
        is_read = Signal()
        is_register = Signal()
        current_address = Signal(32)
        is_multipage = Signal()

        #
        # FSM datapath signals.
        #

        # Tracks whether we need to add an extra latency period between our
        # command and the data body.
        extra_latency = Signal()

        # Tracks how many cycles of latency we have remaining between a command
        # and the relevant data stages.
        latency_edges_remaining = Signal(range(0, self.HIGH_LATENCY_EDGES + 1))

        # One cycle delayed version of RWDS.
        # This is used to detect edges in RWDS during reads, which semantically mean
        # we should accept new data.
        last_rwds = Signal.like(self.bus.rwds.i)
        m.d.fast += last_rwds.eq(self.bus.rwds.i)

        # Create a fast-domain version of our 'new data ready' signal.
        new_data_ready = Signal()

        # We need to stretch our internal strobes to two cycles before passing them
        # into the main clock domain.
        stretch_strobe_signal(m,
                              strobe=new_data_ready,
                              output=self.new_data_ready,
                              to_cycles=2,
                              domain=m.d.fast)

        #
        # Core operation FSM.
        #

        # Provide defaults for our control/status signals.
        m.d.fast += [
            advance_clock.eq(1),
            reset_clock.eq(0),
            new_data_ready.eq(0),
            self.bus.cs.eq(1),
            self.bus.rwds.oe.eq(0),
            self.bus.dq.oe.eq(0),
        ]

        with m.FSM(domain='fast') as fsm:
            m.d.comb += self.idle.eq(fsm.ongoing('IDLE'))

            # IDLE state: waits for a transaction request
            with m.State('IDLE'):
                m.d.fast += reset_clock.eq(1)

                # Once we have a transaction request, latch in our control
                # signals, and assert our chip-select.
                with m.If(self.start_transfer):
                    m.next = 'LATCH_RWDS'

                    m.d.fast += [
                        is_read.eq(~self.perform_write),
                        is_register.eq(self.register_space),
                        is_multipage.eq(~self.single_page),
                        current_address.eq(self.address),
                    ]

                with m.Else():
                    m.d.fast += self.bus.cs.eq(0)

            # LATCH_RWDS -- latch in the value of the RWDS signal, which determines
            # our read/write latency. Note that we advance the clock in this state,
            # as our out-of-phase clock signal will output the relevant data before
            # the next edge can occur.
            with m.State("LATCH_RWDS"):
                m.d.fast += extra_latency.eq(self.bus.rwds.i),
                m.next = "SHIFT_COMMAND0"

            # Commands, in order of bytes sent:
            #   - WRBAAAAA
            #     W         => selects read or write; 1 = read, 0 = write
            #      R        => selects register or memory; 1 = register, 0 = memory
            #       B       => selects burst behavior; 0 = wrapped, 1 = linear
            #        AAAAA  => address bits [27:32]
            #
            #   - AAAAAAAA  => address bits [19:27]
            #   - AAAAAAAA  => address bits [11:19]
            #   - AAAAAAAA  => address bits [ 3:16]
            #   - 00000000  => [reserved]
            #   - 00000AAA  => address bits [ 0: 3]

            # SHIFT_COMMANDx -- shift each of our command bytes out
            with m.State('SHIFT_COMMAND0'):
                m.next = 'SHIFT_COMMAND1'

                # Build our composite command byte.
                command_byte = Cat(current_address[27:32], is_multipage,
                                   is_register, is_read)

                # Output our first byte of our command.
                m.d.fast += [data_out.eq(command_byte), self.bus.dq.oe.eq(1)]

            # Note: it's felt that this is more readable with each of these
            # states defined explicitly. If you strongly disagree, feel free
            # to PR a for-loop, here.~

            with m.State('SHIFT_COMMAND1'):
                m.d.fast += [
                    data_out.eq(current_address[19:27]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND2'

            with m.State('SHIFT_COMMAND2'):
                m.d.fast += [
                    data_out.eq(current_address[11:19]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND3'

            with m.State('SHIFT_COMMAND3'):
                m.d.fast += [
                    data_out.eq(current_address[3:16]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND4'

            with m.State('SHIFT_COMMAND4'):
                m.d.fast += [data_out.eq(0), self.bus.dq.oe.eq(1)]
                m.next = 'SHIFT_COMMAND5'

            with m.State('SHIFT_COMMAND5'):
                m.d.fast += [
                    data_out.eq(current_address[0:3]),
                    self.bus.dq.oe.eq(1)
                ]

                # If we have a register write, we don't need to handle
                # any latency. Move directly to our SHIFT_DATA state.
                with m.If(is_register & ~is_read):
                    m.next = 'WRITE_DATA_MSB'

                # Otherwise, react with either a short period of latency
                # or a longer one, depending on what the RAM requested via
                # RWDS.
                with m.Else():
                    m.next = "HANDLE_LATENCY"

                    with m.If(extra_latency):
                        m.d.fast += latency_edges_remaining.eq(
                            self.HIGH_LATENCY_EDGES)
                    with m.Else():
                        m.d.fast += latency_edges_remaining.eq(
                            self.LOW_LATENCY_EDGES)

            # HANDLE_LATENCY -- applies clock edges until our latency period is over.
            with m.State('HANDLE_LATENCY'):
                m.d.fast += latency_edges_remaining.eq(
                    latency_edges_remaining - 1)

                with m.If(latency_edges_remaining == 0):
                    with m.If(is_read):
                        m.next = 'READ_DATA_MSB'
                    with m.Else():
                        m.next = 'WRITE_DATA_MSB'

            # STREAM_DATA_MSB -- scans in or out the first byte of data
            with m.State('READ_DATA_MSB'):

                # If RWDS has changed, the host has just sent us new data.
                with m.If(self.bus.rwds.i != last_rwds):
                    m.d.fast += [self.read_data[8:16].eq(data_in)]
                    m.next = 'READ_DATA_LSB'

            # STREAM_DATA_LSB -- scans in or out the second byte of data
            with m.State('READ_DATA_LSB'):

                # If RWDS has changed, the host has just sent us new data.
                # Sample it, and indicate that we now have a valid piece of new data.
                with m.If(self.bus.rwds.i != last_rwds):
                    m.d.fast += [
                        self.read_data[0:8].eq(data_in),
                        new_data_ready.eq(1)
                    ]

                    # If our controller is done with the transcation, end it.
                    with m.If(self.final_word):
                        m.next = 'RECOVERY'
                        m.d.fast += advance_clock.eq(0)

                    with m.Else():
                        m.next = 'READ_DATA_MSB'

            # RECOVERY state: wait for the required period of time before a new transaction
            with m.State('RECOVERY'):
                m.d.fast += [self.bus.cs.eq(0), advance_clock.eq(0)]

                # TODO: implement recovery
                m.next = 'IDLE'

            # TODO: implement write shift states

            with m.State("WRITE_DATA_MSB"):
                pass

        return m
示例#8
0
文件: analyzer.py 项目: ktemkin/luna
class USBAnalyzer(Elaboratable):
    """ Core USB analyzer; backed by a small ringbuffer in FPGA block RAM.

    If you're looking to instantiate a full analyzer, you'll probably want to grab
    one of the DRAM-based ringbuffer variants (which are currently forthcoming).

    If you're looking to use this with a ULPI PHY, rather than the FPGA-convenient UMTI interface,
    grab the UMTITranslator from `luna.gateware.interface.ulpi`.

    I/O port:
        O: data_available -- indicates that new data is available in the analysis stream
        O: data_out[8]    -- the next byte in the captured stream; valid when data_available is asserted
        I: next           -- strobe that indicates when the data_out byte has been accepted; and can be
                             discarded from the local memory
    """

    # Current, we'll provide a packet header of 16 bits.
    HEADER_SIZE_BITS = 16
    HEADER_SIZE_BYTES = HEADER_SIZE_BITS // 8

    # Support a maximum payload size of 1024B, plus a 1-byte PID and a 2-byte CRC16.
    MAX_PACKET_SIZE_BYTES = 1024 + 1 + 2

    def __init__(self, *, umti_interface, mem_depth=8192):
        """
        Parameters:
            umti_interface -- A record or elaboratable that presents a UMTI interface.
        """

        self.umti = umti_interface

        # Internal storage memory.
        self.mem = Memory(width=8, depth=mem_depth, name="analysis_ringbuffer")
        self.mem_size = mem_depth

        #
        # I/O port
        #
        self.data_available = Signal()
        self.data_out = Signal(8)
        self.next = Signal()

        self.overrun = Signal()
        self.capturing = Signal()

        # Diagnostic I/O.
        self.sampling = Signal()

    def elaborate(self, platform):
        m = Module()

        # Memory read and write ports.
        m.submodules.read = mem_read_port = self.mem.read_port(domain="ulpi")
        m.submodules.write = mem_write_port = self.mem.write_port(
            domain="ulpi")

        # 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 not data in the FIFO.
            self.data_available.eq(fifo_count != 0),

            # Our data_out is always the output of our read port...
            self.data_out.eq(mem_read_port.data),

            # ... and our read port always reads from our read pointer.
            mem_read_port.addr.eq(read_location),
            self.sampling.eq(mem_write_port.en)
        ]

        # Once our consumer has accepted our current data, move to the next address.
        with m.If(self.next & self.data_available):
            m.d.ulpi += read_location.eq(read_location + 1)

        #
        # FIFO count handling.
        #
        fifo_full = (fifo_count == self.mem_size)

        data_pop = Signal()
        data_push = Signal()
        m.d.comb += [
            data_pop.eq(self.next & self.data_available),
            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.ulpi += fifo_count.eq(fifo_count + 1)
        with m.Elif(data_pop):
            m.d.ulpi += fifo_count.eq(fifo_count - 1)

        #
        # Core analysis FSM.
        #
        with m.FSM(domain="ulpi") as f:
            m.d.comb += [
                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.umti.rx_active):
                    m.next = "CAPTURE"
                    m.d.ulpi += [
                        header_location.eq(write_location),
                        write_location.eq(write_location +
                                          self.HEADER_SIZE_BYTES),
                        packet_size.eq(0),
                    ]

                    #with m.If(self.umti.rx_valid):
                    #    m.d.ulpi += [
                    #        fifo_count   .eq(fifo_count + 1),
                    #    write_location   .eq(write_location + self.HEADER_SIZE_BYTES + 1),
                    #        packet_size  .eq(1)
                    #    ]
                    #    m.d.comb += [
                    #        mem_write_port.addr  .eq(write_location + self.HEADER_SIZE_BYTES),
                    #        mem_write_port.data  .eq(self.umti.data_out),
                    #        mem_write_port.en    .eq(1)
                    #    ]

            # Capture data until the packet is complete.
            with m.State("CAPTURE"):

                # Capture data whenever RxValid is asserted.
                m.d.comb += [
                    mem_write_port.addr.eq(write_location),
                    mem_write_port.data.eq(self.umti.data_out),
                    mem_write_port.en.eq(self.umti.rx_valid
                                         & self.umti.rx_active),
                    fifo_new_data.eq(self.umti.rx_valid & self.umti.rx_active)
                ]

                # Advance the write pointer each time we receive a bit.
                with m.If(self.umti.rx_valid & self.umti.rx_active):
                    m.d.ulpi += [
                        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.umti.rx_active):

                    # 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.ulpi += [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[7:16]),
                    #mem_write_port.data  .eq(0xAA),
                    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)
                ]

                # Move to the next state, which will either be another capture,
                # or our idle state, depending on whether we have another rx.
                with m.If(self.umti.rx_active):
                    m.next = "CAPTURE"
                    m.d.ulpi += [
                        header_location.eq(write_location),
                        write_location.eq(write_location +
                                          self.HEADER_SIZE_BYTES),
                        packet_size.eq(0),
                    ]

                    # FIXME: capture if rx_valid

                with m.Else():
                    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
示例#9
0
文件: analyzer.py 项目: ktemkin/luna
    def __init__(self, *, umti_interface, mem_depth=8192):
        """
        Parameters:
            umti_interface -- A record or elaboratable that presents a UMTI interface.
        """

        self.umti = umti_interface

        # Internal storage memory.
        self.mem = Memory(width=8, depth=mem_depth, name="analysis_ringbuffer")
        self.mem_size = mem_depth

        #
        # I/O port
        #
        self.data_available = Signal()
        self.data_out = Signal(8)
        self.next = Signal()

        self.overrun = Signal()
        self.capturing = Signal()

        # Diagnostic I/O.
        self.sampling = Signal()
示例#10
0
 def elab(self, m):
     state = Signal(1)
     m.d.sync += state.eq(~state)
示例#11
0
    def elaborate(self, platform):
        phase = Signal(self.phase_depth)
        note = Signal.like(self.note_in.i_data.note)
        mod = Signal.like(self.mod_in)
        pw = Signal.like(self.pw_in)
        octave = Signal(range(OCTAVES))
        step = Signal(range(STEPS))
        base_inc = Signal(self.inc_depth)
        step_incs = Array(
            [Signal.like(base_inc, reset=inc) for inc in self._base_incs])
        inc = Signal.like(phase)
        pulse_sample = Signal.like(self.pulse_out.o_data)
        saw_sample = Signal.like(self.saw_out.o_data)

        m = Module()
        with m.If(self.sync_in):
            m.d.sync += [
                phase.eq(0),
                # self.rdy_out.eq(False),
            ]

        m.d.comb += [
            self.note_in.o_ready.eq(True),
        ]
        with m.If(self.note_in.received()):
            m.d.sync += [
                note.eq(self.note_in.i_data.note),
                octave.eq(div12(self.note_in.i_data.note)),
            ]
        # Calculate pulse wave edges.  The pulse must rise and fall
        # exactly once per cycle.
        prev_msb = Signal()
        new_cycle = Signal()
        pulse_up = Signal()
        up_latch = Signal()
        pw8 = Cat(pw, Const(0, unsigned(1)))
        m.d.sync += [
            prev_msb.eq(phase[-1]),
        ]
        m.d.comb += [
            new_cycle.eq(~phase[-1] & prev_msb),
            # Widen pulse to one sample period minimum.
            pulse_up.eq(new_cycle | (up_latch & (phase[-8:] <= pw8))),
        ]
        m.d.sync += [
            up_latch.eq((new_cycle | up_latch) & pulse_up),
        ]

        with m.FSM():

            with m.State(FSM.START):
                m.d.sync += [
                    # note.eq(self.note_in),
                    mod.eq(self.mod_in),
                    pw.eq(self.pw_in),
                    # octave.eq(div12(self.note_in)),
                ]
                m.next = FSM.MODULUS

            with m.State(FSM.MODULUS):
                m.d.sync += [
                    step.eq(note - mul12(octave)),
                ]
                m.next = FSM.LOOKUP

            with m.State(FSM.LOOKUP):
                m.d.sync += [
                    base_inc.eq(step_incs[step]),
                ]
                # m.next = FSM.MODULATE
                m.next = FSM.SHIFT

            # with m.State(FSM.MODULATE):
            #     ...
            #     m.next = FSM.SHIFT

            with m.State(FSM.SHIFT):
                m.d.sync += [
                    inc.eq((base_inc << octave)[-self.shift:]),
                ]
                m.next = FSM.ADD

            with m.State(FSM.ADD):
                m.d.sync += [
                    phase.eq(phase + inc),
                ]
                m.next = FSM.SAMPLE

            with m.State(FSM.SAMPLE):
                samp_depth = self.saw_out.o_data.shape()[0]
                samp_max = 2**(samp_depth - 1) - 1
                m.d.sync += [
                    pulse_sample.eq(Mux(pulse_up, samp_max, -samp_max)),
                    saw_sample.eq(samp_max - phase[-samp_depth:]),
                ]
                m.next = FSM.EMIT

            with m.State(FSM.EMIT):
                with m.If(self.saw_out.i_ready & self.pulse_out.i_ready):
                    m.d.sync += [
                        self.pulse_out.o_valid.eq(True),
                        self.pulse_out.o_data.eq(pulse_sample),
                        self.saw_out.o_valid.eq(True),
                        self.saw_out.o_data.eq(saw_sample),
                    ]
                    m.next = FSM.START

        with m.If(self.pulse_out.sent()):
            m.d.sync += [
                self.pulse_out.o_valid.eq(False),
            ]
        with m.If(self.saw_out.sent()):
            m.d.sync += [
                self.saw_out.o_valid.eq(False),
            ]
        return m
示例#12
0
 def elab(self, m):
     captured = Signal.like(self.input)
     with m.If(self.capture):
         m.d.sync += captured.eq(self.input)
     m.d.comb += self.output.eq(Mux(self.capture, self.input, captured))
示例#13
0
 def __init__(self, inp, capture):
     self.input = inp
     self.capture = capture
     self.output = Signal.like(inp)
示例#14
0
    def elaborate(self, platform):
        m = Module()

        cpu = Core()
        m.submodules += cpu
        m.domains.ph1 = ph1 = ClockDomain("ph1")
        m.domains.ph2 = ph2 = ClockDomain("ph2", clk_edge="neg")

        # Hook up clocks and reset to pins

        if not SLOWCLK:
            clk1 = platform.request("clk1")
            clk2 = platform.request("clk2")
            rst = platform.request("rst")
            m.d.comb += [
                ph1.rst.eq(rst.i),
                ph2.rst.eq(rst.i),
                ph1.clk.eq(clk1.i),
                ph2.clk.eq(clk2.i),
            ]

        if SLOWCLK:
            clk_freq = platform.default_clk_frequency
            timer = Signal(range(0, int(clk_freq // 2)),
                           reset=int(clk_freq // 2) - 1)
            tick = Signal()
            sync = ClockDomain()

            with m.If(timer == 0):
                m.d.sync += timer.eq(timer.reset)
                m.d.sync += tick.eq(~tick)
            with m.Else():
                m.d.sync += timer.eq(timer - 1)
            m.d.comb += [
                ph1.rst.eq(sync.rst),
                ph2.rst.eq(sync.rst),
                ph1.clk.eq(tick),
                ph2.clk.eq(~tick),
            ]

        # Hook up address lines to pins
        addr = []
        for i in range(16):
            pin = platform.request("addr", i)
            m.d.comb += pin.o.eq(cpu.Addr[i])
            addr.append(pin)

        data = []
        if not FAKEMEM:
            # Hook up data in/out + direction to pins
            for i in range(8):
                pin = platform.request("data", i)
                m.d.comb += pin.o.eq(cpu.Dout[i])
                m.d.ph2 += cpu.Din[i].eq(pin.i)
                data.append(pin)

        if FAKEMEM:
            with m.Switch(cpu.Addr):
                for a, d in mem.items():
                    with m.Case(a):
                        m.d.comb += cpu.Din.eq(d)
                with m.Default():
                    m.d.comb += cpu.Din.eq(0x00)
            for i in range(8):
                pin = platform.request("led", i)
                m.d.comb += pin.o.eq(cpu.Addr[i])

        rw = platform.request("rw")
        m.d.comb += rw.o.eq(cpu.RW)

        nIRQ = platform.request("n_irq")
        nNMI = platform.request("n_nmi")
        m.d.ph2 += cpu.IRQ.eq(~nIRQ)
        m.d.ph2 += cpu.NMI.eq(~nNMI)

        ba = platform.request("ba")
        m.d.comb += ba.o.eq(cpu.BA)
        m.d.comb += rw.oe.eq(~cpu.BA)
        for i in range(len(addr)):
            m.d.comb += addr[i].oe.eq(~cpu.BA)
        for i in range(len(data)):
            m.d.comb += data[i].oe.eq(~cpu.BA & ~cpu.RW)

        return m
示例#15
0
class ULPITransmitTranslator(Elaboratable):
    """ Accepts UTMI transmit signals, and converts them into ULPI equivalents.

    I/O port:
        I: tx_data[8]      -- The data to be transmitted.
        I: tx_valid        -- Driven high to indicate we're trying to transmit.
        O: tx_ready        -- Driven high when a given byte will be accepted on tx_data on the next clock edge.

        I: op_mode[2]      -- The UTMI operating mode. Used to determine when NOPID commands should be issued;
                              and when to force transmit errors.

        I: bus_idle        -- Should be asserted when the transmitter is able to control the bus.

        O: ulpi_data_out   -- The data to be driven onto the ULPI transmit lines.
        O: ulpi_out_req    -- Asserted when we're trying to drive the ULPI data lines.
        I: ulpi_nxt        -- The NXT signal for the relevant ULPI bus.
        O: ulpi_stp        -- The STP signal for the relevant ULPI bus.

        O: busy            -- True iff this module is using the bus.
    """

    # Prefix for ULPI transmit commands.
    TRANSMIT_COMMAND = 0b01000000

    # UTMI operating mode for "bit stuffing disabled".
    OP_MODE_NO_BIT_STUFFING = 0b10

    def __init__(self):

        #
        # I/O port.
        #

        self.tx_data = Signal(8)
        self.tx_valid = Signal()
        self.tx_ready = Signal()

        self.op_mode = Signal(2)
        self.bus_idle = Signal()

        self.ulpi_out_req = Signal()
        self.ulpi_data_out = Signal.like(self.tx_data)
        self.ulpi_nxt = Signal()
        self.ulpi_stp = Signal()

        self.busy = Signal()

    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
示例#16
0
文件: analyzer.py 项目: ktemkin/luna
    def elaborate(self, platform):
        m = Module()

        # Memory read and write ports.
        m.submodules.read = mem_read_port = self.mem.read_port(domain="ulpi")
        m.submodules.write = mem_write_port = self.mem.write_port(
            domain="ulpi")

        # 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 not data in the FIFO.
            self.data_available.eq(fifo_count != 0),

            # Our data_out is always the output of our read port...
            self.data_out.eq(mem_read_port.data),

            # ... and our read port always reads from our read pointer.
            mem_read_port.addr.eq(read_location),
            self.sampling.eq(mem_write_port.en)
        ]

        # Once our consumer has accepted our current data, move to the next address.
        with m.If(self.next & self.data_available):
            m.d.ulpi += read_location.eq(read_location + 1)

        #
        # FIFO count handling.
        #
        fifo_full = (fifo_count == self.mem_size)

        data_pop = Signal()
        data_push = Signal()
        m.d.comb += [
            data_pop.eq(self.next & self.data_available),
            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.ulpi += fifo_count.eq(fifo_count + 1)
        with m.Elif(data_pop):
            m.d.ulpi += fifo_count.eq(fifo_count - 1)

        #
        # Core analysis FSM.
        #
        with m.FSM(domain="ulpi") as f:
            m.d.comb += [
                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.umti.rx_active):
                    m.next = "CAPTURE"
                    m.d.ulpi += [
                        header_location.eq(write_location),
                        write_location.eq(write_location +
                                          self.HEADER_SIZE_BYTES),
                        packet_size.eq(0),
                    ]

                    #with m.If(self.umti.rx_valid):
                    #    m.d.ulpi += [
                    #        fifo_count   .eq(fifo_count + 1),
                    #    write_location   .eq(write_location + self.HEADER_SIZE_BYTES + 1),
                    #        packet_size  .eq(1)
                    #    ]
                    #    m.d.comb += [
                    #        mem_write_port.addr  .eq(write_location + self.HEADER_SIZE_BYTES),
                    #        mem_write_port.data  .eq(self.umti.data_out),
                    #        mem_write_port.en    .eq(1)
                    #    ]

            # Capture data until the packet is complete.
            with m.State("CAPTURE"):

                # Capture data whenever RxValid is asserted.
                m.d.comb += [
                    mem_write_port.addr.eq(write_location),
                    mem_write_port.data.eq(self.umti.data_out),
                    mem_write_port.en.eq(self.umti.rx_valid
                                         & self.umti.rx_active),
                    fifo_new_data.eq(self.umti.rx_valid & self.umti.rx_active)
                ]

                # Advance the write pointer each time we receive a bit.
                with m.If(self.umti.rx_valid & self.umti.rx_active):
                    m.d.ulpi += [
                        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.umti.rx_active):

                    # 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.ulpi += [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[7:16]),
                    #mem_write_port.data  .eq(0xAA),
                    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)
                ]

                # Move to the next state, which will either be another capture,
                # or our idle state, depending on whether we have another rx.
                with m.If(self.umti.rx_active):
                    m.next = "CAPTURE"
                    m.d.ulpi += [
                        header_location.eq(write_location),
                        write_location.eq(write_location +
                                          self.HEADER_SIZE_BYTES),
                        packet_size.eq(0),
                    ]

                    # FIXME: capture if rx_valid

                with m.Else():
                    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
示例#17
0
    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_out_req.eq(0),
                        self.ulpi_stop.eq(1),
                        self.done.eq(1)
                    ]
                    m.next = 'IDLE'

        return m
示例#18
0
    def __init__(self,
                 *,
                 ulpi,
                 use_platform_registers=True,
                 handle_clocking=True):
        """ Params:

            ulpi                   -- The ULPI bus to communicate with.
            use_platform_registers -- If True (or not provided), any extra registers writes provided in
                                      the platform definition will be applied automatically.
            handle_clocking        -- True iff we should attempt to automatically handle ULPI clocking. If
                                      the `clk` ULPI signal is an input, it will be used to provide the 'usb'
                                      domain clock. If the ULPI signal is an output, it will driven with our
                                      'usb' domain clock. If False, it will be the user's responsibility to
                                      handle clocking.

            Note that it's recommended that multi-PHY systems either use a single clock for all PHYs
            (assuming the PHYs support clock input), or that individual clock domains be created for each
            PHY using a DomainRenamer.
        """

        self.use_platform_registers = use_platform_registers
        self.handle_clocking = handle_clocking

        #
        # I/O port
        #
        self.ulpi = ulpi
        self.busy = Signal()

        # Data signals.
        self.rx_data = Signal(8)
        self.rx_valid = Signal()

        self.tx_data = Signal(8)
        self.tx_valid = Signal()
        self.tx_ready = Signal()

        # Status signals.
        self.rx_active = Signal()

        # RxEvent-based flags.
        for signal_name, size in self.RXEVENT_STATUS_SIGNALS:
            self.__dict__[signal_name] = Signal(size, name=signal_name)

        # Control signals.
        for signal_name, size in self.CONTROL_SIGNALS:
            self.__dict__[signal_name] = Signal(size, name=signal_name)

        # Diagnostic I/O.
        self.last_rx_command = Signal(8)

        #
        # Internal
        #

        #  Create a list of extra registers to be set.
        self._extra_registers = {}
示例#19
0
class UTMITranslator(Elaboratable):
    """ Gateware that translates a ULPI interface into a simpler UTMI one.

    I/O port:

        O: busy          -- signal that's true iff the ULPI interface is being used
                            for a register or transmit command

        # See the UTMI specification for most signals.

        # Data signals:
        I: tx_data[8]  -- data to be transmitted; valid when tx_valid is asserted
        I: tx_valid    -- set to true when data is to be transmitted; indicates the data_in
                          byte is valid; de-asserting this line terminates the transmission
        O: tx_ready    -- indicates the the PHY is ready to accept a new byte of data, and that the
                          transmitter should move on to the next byte after the given cycle

        O: rx_data[8]  -- data received from the PHY; valid when rx_valid is asserted
        O: rx_valid    -- indicates that the data present on rx_data is new and valid data;
                          goes high for a single ULPI clock cycle to indicate new data is ready

        O: rx_active   -- indicates that the PHY is actively receiving data from the host; data is
                          slewed on rx_data by rx_valid
        O: rx_error    -- indicates that an error has occurred in the current transmission

        # Extra signals:
        O: rx_complete -- strobe that goes high for one cycle when a packet rx is complete

        # Signals for diagnostic use:
        O: last_rxcmd    -- The byte content of the last RxCmd.

        I: address       -- The ULPI register address to work with.
        O: read_data[8]  -- The contents of the most recently read ULPI command.
        I: write_data[8] -- The data to be written on the next write request.
        I: manual_read   -- Strobe that triggers a diagnostic read.
        I: manual_write  -- Strobe that triggers a diagnostic write.

    """

    # UTMI status signals translated from the ULPI bus.
    RXEVENT_STATUS_SIGNALS = [('line_state', 2), ('vbus_valid', 1),
                              ('session_valid', 1), ('session_end', 1),
                              ('rx_error', 1), ('host_disconnect', 1),
                              ('id_digital', 1)]

    # Control signals that we control through our control translator.
    CONTROL_SIGNALS = [('xcvr_select', 2), ('term_select', 1), ('op_mode', 2),
                       ('suspend', 1), ('id_pullup', 1), ('dm_pulldown', 1),
                       ('dp_pulldown', 1), ('chrg_vbus', 1),
                       ('dischrg_vbus', 1), ('use_external_vbus_indicator', 1)]

    def __dir__(self):
        """ Extend our properties list of contain all of the above fields, for proper autocomplete. """

        properties = list(super().__dir__())

        properties.extend(name for name, _ in self.RXEVENT_STATUS_SIGNALS)
        properties.extend(name for name, _ in self.CONTROL_SIGNALS)

        return properties

    def __init__(self,
                 *,
                 ulpi,
                 use_platform_registers=True,
                 handle_clocking=True):
        """ Params:

            ulpi                   -- The ULPI bus to communicate with.
            use_platform_registers -- If True (or not provided), any extra registers writes provided in
                                      the platform definition will be applied automatically.
            handle_clocking        -- True iff we should attempt to automatically handle ULPI clocking. If
                                      the `clk` ULPI signal is an input, it will be used to provide the 'usb'
                                      domain clock. If the ULPI signal is an output, it will driven with our
                                      'usb' domain clock. If False, it will be the user's responsibility to
                                      handle clocking.

            Note that it's recommended that multi-PHY systems either use a single clock for all PHYs
            (assuming the PHYs support clock input), or that individual clock domains be created for each
            PHY using a DomainRenamer.
        """

        self.use_platform_registers = use_platform_registers
        self.handle_clocking = handle_clocking

        #
        # I/O port
        #
        self.ulpi = ulpi
        self.busy = Signal()

        # Data signals.
        self.rx_data = Signal(8)
        self.rx_valid = Signal()

        self.tx_data = Signal(8)
        self.tx_valid = Signal()
        self.tx_ready = Signal()

        # Status signals.
        self.rx_active = Signal()

        # RxEvent-based flags.
        for signal_name, size in self.RXEVENT_STATUS_SIGNALS:
            self.__dict__[signal_name] = Signal(size, name=signal_name)

        # Control signals.
        for signal_name, size in self.CONTROL_SIGNALS:
            self.__dict__[signal_name] = Signal(size, name=signal_name)

        # Diagnostic I/O.
        self.last_rx_command = Signal(8)

        #
        # Internal
        #

        #  Create a list of extra registers to be set.
        self._extra_registers = {}

    def add_extra_register(self,
                           write_address,
                           write_value,
                           *,
                           default_value=None):
        """ Adds logic to configure an extra ULPI register. Useful for configuring vendor registers.

        Params:
            write_address -- The write address of the target ULPI register.
            write_value   -- The value to be written. If a Signal is provided; the given register will be
                             set post-reset, if necessary; and then dynamically updated each time the signal changes.
                             If an integer constant is provided, this value will be written once upon startup.
            default_value -- The default value the register is expected to have post-reset; used to determine
                             if the value needs to be updated post-reset. If a Signal is provided for write_value,
                             this must be provided; if an integer is provided for write_value, this is optional.
        """

        # Ensure we have a default_value if we have a Signal(); as this will determine
        # whether we need to update the register post-reset.
        if (default_value is None) and isinstance(write_value, Signal):
            raise ValueError(
                "if write_value is a signal, default_value must be provided")

        # Otherwise, we'll pick a value that ensures the write always occurs.
        elif default_value is None:
            default_value = 0xff ^ write_value

        self._extra_registers[write_address] = {
            'value': write_value,
            'default': default_value
        }

    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)

        # 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('usb'))
            elif hasattr(self.ulpi.clk, 'i'):
                m.d.comb += ClockSignal('usb').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("usb")),

        # 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
示例#20
0
class ULPIRxEventDecoder(Elaboratable):
    """ Simple piece of gateware that tracks receive events.

    I/O port:

        I: ulpi_data_in[8] -- The current input state of the ULPI data lines.
        I: ulpi_dir        -- The ULPI bus-direction signal.
        I: ulpi_nxt        -- The ULPI 'next' throttle signal.
        I: register_operation_in_progress
            Signal that should be true iff we're performing a register operation.

        O: last_rx_command -- The full byte value of the last RxCmd.

        O: line_state[2]   -- The states of the two USB lines.
        O: rx_active       -- True when a packet receipt is active.
        O: rx_error        -- True when a packet receive error has occurred.
        O: host_disconnect -- True if the host has just disconnected.
        O: id_digital      -- Digital value of the ID pin.
        O: vbus_valid      -- True iff a valid VBUS voltage is present
        O: session_end     -- True iff a session has just ended.

        # Strobes indicating signal changes.
        O: rx_start        -- True iff an RxEvent has changed the value of RxActive from 0 -> 1.
        O: rx_stop         -- True iff an RxEvent has changed the value of RxActive from 1 -> 0.
    """
    def __init__(self, *, ulpi_bus):

        #
        # I/O port.
        #
        self.ulpi = ulpi_bus
        self.register_operation_in_progress = Signal()

        # Optional: signal that allows access to the last RxCmd byte.
        self.last_rx_command = Signal(8)

        self.line_state = Signal(2)
        self.rx_active = Signal()
        self.rx_error = Signal()
        self.host_disconnect = Signal()
        self.id_digital = Signal()
        self.vbus_valid = Signal()
        self.session_valid = Signal()
        self.session_end = Signal()

        # RxActive strobes.
        self.rx_start = Signal()
        self.rx_stop = Signal()

    def elaborate(self, platform):
        m = Module()

        # An RxCmd is present when three conditions are met:
        # - We're not actively undergoing a register read.
        # - Direction has been high for more than one cycle.
        # - NXT is low.

        # To implement the first condition, we'll first create a delayed
        # version of DIR, and then logically AND it with the current value.
        direction_delayed = Signal()
        m.d.usb += direction_delayed.eq(self.ulpi.dir)

        receiving = Signal()
        m.d.comb += receiving.eq(direction_delayed & self.ulpi.dir)

        # Default our strobes to 0, unless asserted.
        m.d.usb += [self.rx_start.eq(0), self.rx_stop.eq(0)]

        # Sample the DATA lines whenever these conditions are met.
        with m.If(receiving & ~self.ulpi.nxt
                  & ~self.register_operation_in_progress):
            m.d.usb += self.last_rx_command.eq(self.ulpi.data.i)

            # If RxActive has just changed, strobe the start or stop signals,
            rx_active = self.ulpi.data.i[4]
            with m.If(~self.rx_active & rx_active):
                m.d.usb += self.rx_start.eq(1)
            with m.If(self.rx_active & ~rx_active):
                m.d.usb += self.rx_stop.eq(1)

        # Break the most recent RxCmd into its UTMI-equivalent signals.
        # From table 3.8.1.2 in the ULPI spec; rev 1.1/Oct-20-2004.
        m.d.comb += [
            self.line_state.eq(self.last_rx_command[0:2]),
            self.vbus_valid.eq(self.last_rx_command[2:4] == 0b11),
            self.session_valid.eq(self.last_rx_command[2:4] == 0b10),
            self.session_end.eq(self.last_rx_command[2:4] == 0b00),
            self.rx_active.eq(self.last_rx_command[4]),
            self.rx_error.eq(self.last_rx_command[4:6] == 0b11),
            self.host_disconnect.eq(self.last_rx_command[4:6] == 0b10),
            self.id_digital.eq(self.last_rx_command[6]),
        ]

        return m
示例#21
0
class HyperRAMInterface(Elaboratable):
    """ Gateware interface to HyperRAM series self-refreshing DRAM chips.

    Intended to run at twice the frequency of the interfacing hardware -- e.g. to interface with
    something from LUNA's sync domain, while existing itself in the fast domain.

    I/O port:
        B: bus              -- The primary physical connection to the DRAM chip.
        I: reset            -- An active-high signal used to provide a prolonged reset upon configuration.

        I: address[32]      -- The address to be targeted by the given operation.
        I: register_space   -- When set to 1, read and write requests target registers instead of normal RAM.
        I: perform_write    -- When set to 1, a transfer request is viewed as a write, rather than a read.
        I: single_page      -- If set, data accesses will wrap around to the start of the current page when done.
        I: start_transfer   -- Strobe that goes high for 1-8 cycles to request a read operation.
                            [This added duration allows other clock domains to easily perform requests.]
        I: final_word       -- Flag that indicates the current word is the last word of the transaction.

        O: read_data[16]    -- word that holds the 16 bits most recently read from the PSRAM
        I: write_data[16]   -- word that accepts the data to output during this transaction

        O: idle             -- High whenever the transmitter is idle (and thus we can start a new piece of data.)
        O: new_data_ready   -- Strobe that indicates when new data is ready for reading
    """

    LOW_LATENCY_EDGES = 6
    HIGH_LATENCY_EDGES = 14

    def __init__(self,
                 *,
                 bus,
                 strobe_length=2,
                 in_skew=None,
                 out_skew=None,
                 clock_skew=None):
        """
        Parmeters:
            bus           -- The RAM record that should be connected to this RAM chip.
            strobe_length -- The number of fast-clock cycles any strobe should be asserted for.
            data_skews    -- If provided, adds an input delay to each line of the data input.
                             Can be provided as a single delay number, or an interable of eight
                             delays to separately delay each of the input lines.
        """

        self.in_skew = in_skew
        self.out_skew = out_skew
        self.clock_skew = clock_skew

        #
        # I/O port.
        #
        self.bus = bus
        self.reset = Signal()

        # Control signals.
        self.address = Signal(32)
        self.register_space = Signal()
        self.perform_write = Signal()
        self.single_page = Signal()
        self.start_transfer = Signal()
        self.final_word = Signal()

        # Status signals.
        self.idle = Signal()
        self.new_data_ready = Signal()

        # Data signals.
        self.read_data = Signal(16)
        self.write_data = Signal(16)

    def elaborate(self, platform):
        m = Module()

        #
        # Delayed input and output.
        #

        if self.in_skew is not None:
            data_in = delay(m, self.bus.dq.i, self.in_skew)
        else:
            data_in = self.bus.dq.i

        if self.out_skew is not None:
            data_out = Signal.like(self.bus.dq.o)
            delay(m, data_out, self.out_skew, out=self.bus.dq.o)
        else:
            data_out = self.bus.dq.o

        #
        # Transaction clock generator.
        #
        advance_clock = Signal()
        reset_clock = Signal()

        if self.clock_skew is not None:
            out_clock = Signal()
            delay(m, out_clock, self.clock_skew, out=self.bus.clk)
        else:
            out_clock = self.bus.clk

        with m.If(reset_clock):
            m.d.fast += out_clock.eq(0)
        with m.Elif(advance_clock):
            m.d.fast += out_clock.eq(~out_clock)

        #
        # Latched control/addressing signals.
        #
        is_read = Signal()
        is_register = Signal()
        current_address = Signal(32)
        is_multipage = Signal()

        #
        # FSM datapath signals.
        #

        # Tracks whether we need to add an extra latency period between our
        # command and the data body.
        extra_latency = Signal()

        # Tracks how many cycles of latency we have remaining between a command
        # and the relevant data stages.
        latency_edges_remaining = Signal(range(0, self.HIGH_LATENCY_EDGES + 1))

        # One cycle delayed version of RWDS.
        # This is used to detect edges in RWDS during reads, which semantically mean
        # we should accept new data.
        last_rwds = Signal.like(self.bus.rwds.i)
        m.d.fast += last_rwds.eq(self.bus.rwds.i)

        # Create a fast-domain version of our 'new data ready' signal.
        new_data_ready = Signal()

        # We need to stretch our internal strobes to two cycles before passing them
        # into the main clock domain.
        stretch_strobe_signal(m,
                              strobe=new_data_ready,
                              output=self.new_data_ready,
                              to_cycles=2,
                              domain=m.d.fast)

        #
        # Core operation FSM.
        #

        # Provide defaults for our control/status signals.
        m.d.fast += [
            advance_clock.eq(1),
            reset_clock.eq(0),
            new_data_ready.eq(0),
            self.bus.cs.eq(1),
            self.bus.rwds.oe.eq(0),
            self.bus.dq.oe.eq(0),
        ]

        with m.FSM(domain='fast') as fsm:
            m.d.comb += self.idle.eq(fsm.ongoing('IDLE'))

            # IDLE state: waits for a transaction request
            with m.State('IDLE'):
                m.d.fast += reset_clock.eq(1)

                # Once we have a transaction request, latch in our control
                # signals, and assert our chip-select.
                with m.If(self.start_transfer):
                    m.next = 'LATCH_RWDS'

                    m.d.fast += [
                        is_read.eq(~self.perform_write),
                        is_register.eq(self.register_space),
                        is_multipage.eq(~self.single_page),
                        current_address.eq(self.address),
                    ]

                with m.Else():
                    m.d.fast += self.bus.cs.eq(0)

            # LATCH_RWDS -- latch in the value of the RWDS signal, which determines
            # our read/write latency. Note that we advance the clock in this state,
            # as our out-of-phase clock signal will output the relevant data before
            # the next edge can occur.
            with m.State("LATCH_RWDS"):
                m.d.fast += extra_latency.eq(self.bus.rwds.i),
                m.next = "SHIFT_COMMAND0"

            # Commands, in order of bytes sent:
            #   - WRBAAAAA
            #     W         => selects read or write; 1 = read, 0 = write
            #      R        => selects register or memory; 1 = register, 0 = memory
            #       B       => selects burst behavior; 0 = wrapped, 1 = linear
            #        AAAAA  => address bits [27:32]
            #
            #   - AAAAAAAA  => address bits [19:27]
            #   - AAAAAAAA  => address bits [11:19]
            #   - AAAAAAAA  => address bits [ 3:16]
            #   - 00000000  => [reserved]
            #   - 00000AAA  => address bits [ 0: 3]

            # SHIFT_COMMANDx -- shift each of our command bytes out
            with m.State('SHIFT_COMMAND0'):
                m.next = 'SHIFT_COMMAND1'

                # Build our composite command byte.
                command_byte = Cat(current_address[27:32], is_multipage,
                                   is_register, is_read)

                # Output our first byte of our command.
                m.d.fast += [data_out.eq(command_byte), self.bus.dq.oe.eq(1)]

            # Note: it's felt that this is more readable with each of these
            # states defined explicitly. If you strongly disagree, feel free
            # to PR a for-loop, here.~

            with m.State('SHIFT_COMMAND1'):
                m.d.fast += [
                    data_out.eq(current_address[19:27]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND2'

            with m.State('SHIFT_COMMAND2'):
                m.d.fast += [
                    data_out.eq(current_address[11:19]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND3'

            with m.State('SHIFT_COMMAND3'):
                m.d.fast += [
                    data_out.eq(current_address[3:16]),
                    self.bus.dq.oe.eq(1)
                ]
                m.next = 'SHIFT_COMMAND4'

            with m.State('SHIFT_COMMAND4'):
                m.d.fast += [data_out.eq(0), self.bus.dq.oe.eq(1)]
                m.next = 'SHIFT_COMMAND5'

            with m.State('SHIFT_COMMAND5'):
                m.d.fast += [
                    data_out.eq(current_address[0:3]),
                    self.bus.dq.oe.eq(1)
                ]

                # If we have a register write, we don't need to handle
                # any latency. Move directly to our SHIFT_DATA state.
                with m.If(is_register & ~is_read):
                    m.next = 'WRITE_DATA_MSB'

                # Otherwise, react with either a short period of latency
                # or a longer one, depending on what the RAM requested via
                # RWDS.
                with m.Else():
                    m.next = "HANDLE_LATENCY"

                    with m.If(extra_latency):
                        m.d.fast += latency_edges_remaining.eq(
                            self.HIGH_LATENCY_EDGES)
                    with m.Else():
                        m.d.fast += latency_edges_remaining.eq(
                            self.LOW_LATENCY_EDGES)

            # HANDLE_LATENCY -- applies clock edges until our latency period is over.
            with m.State('HANDLE_LATENCY'):
                m.d.fast += latency_edges_remaining.eq(
                    latency_edges_remaining - 1)

                with m.If(latency_edges_remaining == 0):
                    with m.If(is_read):
                        m.next = 'READ_DATA_MSB'
                    with m.Else():
                        m.next = 'WRITE_DATA_MSB'

            # STREAM_DATA_MSB -- scans in or out the first byte of data
            with m.State('READ_DATA_MSB'):

                # If RWDS has changed, the host has just sent us new data.
                with m.If(self.bus.rwds.i != last_rwds):
                    m.d.fast += [self.read_data[8:16].eq(data_in)]
                    m.next = 'READ_DATA_LSB'

            # STREAM_DATA_LSB -- scans in or out the second byte of data
            with m.State('READ_DATA_LSB'):

                # If RWDS has changed, the host has just sent us new data.
                # Sample it, and indicate that we now have a valid piece of new data.
                with m.If(self.bus.rwds.i != last_rwds):
                    m.d.fast += [
                        self.read_data[0:8].eq(data_in),
                        new_data_ready.eq(1)
                    ]

                    # If our controller is done with the transcation, end it.
                    with m.If(self.final_word):
                        m.next = 'RECOVERY'
                        m.d.fast += advance_clock.eq(0)

                    with m.Else():
                        m.next = 'READ_DATA_MSB'

            # RECOVERY state: wait for the required period of time before a new transaction
            with m.State('RECOVERY'):
                m.d.fast += [self.bus.cs.eq(0), advance_clock.eq(0)]

                # TODO: implement recovery
                m.next = 'IDLE'

            # TODO: implement write shift states

            with m.State("WRITE_DATA_MSB"):
                pass

        return m
示例#22
0
    def __init__(self, *, ulpi_bus):

        #
        # I/O port.
        #
        self.ulpi = ulpi_bus
        self.register_operation_in_progress = Signal()

        # Optional: signal that allows access to the last RxCmd byte.
        self.last_rx_command = Signal(8)

        self.line_state = Signal(2)
        self.rx_active = Signal()
        self.rx_error = Signal()
        self.host_disconnect = Signal()
        self.id_digital = Signal()
        self.vbus_valid = Signal()
        self.session_valid = Signal()
        self.session_end = Signal()

        # RxActive strobes.
        self.rx_start = Signal()
        self.rx_stop = Signal()
示例#23
0
class SPIDeviceInterface(Elaboratable):
    """ Simple word-oriented SPI interface.

    I/O signals:
        B: spi           -- the SPI bus to work with

        O: word_in       -- the most recent word received
        O: word_complete -- strobe indicating a new word is present on word_in
        I: word_out      -- the word to be loaded; latched in on next word_complete and while cs is low
    """
    def __init__(self,
                 *,
                 word_size=8,
                 clock_polarity=0,
                 clock_phase=0,
                 msb_first=True,
                 cs_idles_high=False):
        """
        Parameters:
            word_size      -- The size of each transmitted word, in bits.
            clock_polarity -- The SPI-standard clock polarity. 0 for idle low, 1 for idle high.
            clock_phase    -- The SPI-standard clock phase. 1 to capture on the leading edge, or 0 for on the trailing
            msb_first      -- If true, or not provided, data will be transmitted MSB first (standard).
            cs_idles_high  -- If provided, data will be captured when CS goes _low_, rather than high.
        """

        self.word_size = word_size
        self.clock_polarity = clock_polarity
        self.clock_phase = clock_phase
        self.msb_first = msb_first
        self.cs_idles_high = cs_idles_high

        #
        # I/O port.
        #

        # SPI
        self.spi = SPIBus()

        # Data I/O
        self.word_in = Signal(self.word_size)
        self.word_out = Signal(self.word_size)
        self.word_accepted = Signal()
        self.word_complete = Signal()

    def spi_edge_detectors(self, m):
        """ Generates edge detectors for the sample and output clocks, based on the current SPI mode.

        Returns:
            sample_edge, output_edge -- signals that pulse high for a single cycle when we should
                                        sample and change our outputs, respectively
        """

        # Select whether we're working with an inverted or un-inverted serial clock.
        serial_clock = Signal()
        if self.clock_polarity:
            m.d.comb += serial_clock.eq(~self.spi.sck)
        else:
            m.d.comb += serial_clock.eq(self.spi.sck)

        # Generate the leading and trailing edge detectors.
        # Note that we use rising and falling edge detectors, but call these leading and
        # trailing edges, as our clock here may have been inverted.
        leading_edge = Rose(serial_clock, domain="sync")
        trailing_edge = Fell(serial_clock, domain="sync")

        # Determine the sample and output edges based on the SPI clock phase.
        sample_edge = trailing_edge if self.clock_phase else leading_edge
        output_edge = leading_edge if self.clock_phase else trailing_edge

        return sample_edge, output_edge

    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
示例#24
0
class ULPIControlTranslator(Elaboratable):
    """ Gateware that translates ULPI control signals to their UTMI equivalents.

    I/O port:
        I: bus_idle       -- Indicates that the ULPI bus is idle, and thus capable of
                             performing register writes.

        I: xcvr_select[2] -- selects the operating speed of the transciever;
                             00 = HS, 01 = FS, 10 = LS, 11 = LS on FS bus
        I: term_select    -- enables termination for the given operating mode; see spec
        I: op_mode        -- selects the operating mode of the transciever;
                             00 = normal, 01 = non-driving, 10 = disable bit-stuff/NRZI
        I: suspend        -- places the transceiver into suspend mode; active high

        I: id_pullup      -- when set, places a 100kR pull-up on the ID pin
        I: dp_pulldown    -- when set, enables a 15kR pull-down on D+; intended for host mode
        I: dm_pulldown    -- when set, enables a 15kR pull-down on D+; intended for host mode

        I: chrg_vbus      -- when set, connects a resistor from VBUS to GND to discharge VBUS
        I: dischrg_vbus   -- when set, connects a resistor from VBUS to 3V3 to charge VBUS above SessValid

        O: busy           -- true iff the control translator is actively performing an operation
    """
    def __init__(self, *, register_window, own_register_window=False):
        """
        Parmaeters:
            register_window     -- The ULPI register window to work with.
            own_register_window -- True iff we're the owner of this register window.
                                   Typically, we'll use the register window for a broader controller;
                                   but this can be set to True to indicate that we need to consider this
                                   register window our own, and thus a submodule.
        """

        self.register_window = register_window
        self.own_register_window = own_register_window

        #
        # I/O port
        #
        self.bus_idle = Signal()
        self.xcvr_select = Signal(2, reset=0b01)
        self.term_select = Signal()
        self.op_mode = Signal(2)
        self.suspend = Signal()

        self.id_pullup = Signal()
        self.dp_pulldown = Signal(reset=1)
        self.dm_pulldown = Signal(reset=1)

        self.chrg_vbus = Signal()
        self.dischrg_vbus = Signal()

        self.busy = Signal()

        # Extra/non-UTMI properties.
        self.use_external_vbus_indicator = Signal(reset=1)

        #
        # Internal variables.
        #
        self._register_signals = {}

    def add_composite_register(self, m, address, value, *, reset_value=0):
        """ Adds a ULPI register that's composed of multiple control signals.

        Params:
            address      -- The register number in the ULPI register space.
            value       -- An 8-bit signal composing the bits that should be placed in
                           the given register.

            reset_value -- If provided, the given value will be assumed as the reset value
                        -- of the given register; allowing us to avoid an initial write.
        """

        current_register_value = Signal(
            8, reset=reset_value, name=f"current_register_value_{address:02x}")

        # Create internal signals that request register updates.
        write_requested = Signal(name=f"write_requested_{address:02x}")
        write_value = Signal(8, name=f"write_value_{address:02x}")
        write_done = Signal(name=f"write_done_{address:02x}")

        self._register_signals[address] = {
            'write_requested': write_requested,
            'write_value': write_value,
            'write_done': write_done
        }

        # If we've just finished a write, update our current register value.
        with m.If(write_done):
            m.d.usb += current_register_value.eq(write_value),

        # If we have a mismatch between the requested and actual register value,
        # request a write of the new value.
        m.d.comb += write_requested.eq(current_register_value != value)
        with m.If(current_register_value != value):
            m.d.usb += write_value.eq(value)

    def populate_ulpi_registers(self, m):
        """ Creates translator objects that map our control signals to ULPI registers. """

        # Function control.
        function_control = Cat(self.xcvr_select, self.term_select,
                               self.op_mode, Const(0), ~self.suspend, Const(0))
        self.add_composite_register(m,
                                    0x04,
                                    function_control,
                                    reset_value=0b01000001)

        # OTG control.
        otg_control = Cat(self.id_pullup, self.dp_pulldown,
                          self.dm_pulldown, self.dischrg_vbus, self.chrg_vbus,
                          Const(0), Const(0), self.use_external_vbus_indicator)
        self.add_composite_register(m,
                                    0x0A,
                                    otg_control,
                                    reset_value=0b00000110)

    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
示例#25
0
    def __init__(self, command_size=8, word_size=32):

        self.command_size = command_size
        self.word_size = word_size

        #
        # I/O port.
        #

        # SPI
        self.spi = SPIBus()

        # Command I/O.
        self.command = Signal(self.command_size)
        self.command_ready = Signal()

        # Data I/O
        self.word_received = Signal(self.word_size)
        self.word_to_send = Signal.like(self.word_received)
        self.word_complete = Signal()

        # Status
        self.idle = Signal()
        self.stalled = Signal()
示例#26
0
    def __init__(self, *, register_window, own_register_window=False):
        """
        Parmaeters:
            register_window     -- The ULPI register window to work with.
            own_register_window -- True iff we're the owner of this register window.
                                   Typically, we'll use the register window for a broader controller;
                                   but this can be set to True to indicate that we need to consider this
                                   register window our own, and thus a submodule.
        """

        self.register_window = register_window
        self.own_register_window = own_register_window

        #
        # I/O port
        #
        self.bus_idle = Signal()
        self.xcvr_select = Signal(2, reset=0b01)
        self.term_select = Signal()
        self.op_mode = Signal(2)
        self.suspend = Signal()

        self.id_pullup = Signal()
        self.dp_pulldown = Signal(reset=1)
        self.dm_pulldown = Signal(reset=1)

        self.chrg_vbus = Signal()
        self.dischrg_vbus = Signal()

        self.busy = Signal()

        # Extra/non-UTMI properties.
        self.use_external_vbus_indicator = Signal(reset=1)

        #
        # Internal variables.
        #
        self._register_signals = {}
示例#27
0
class SPIRegisterInterface(Elaboratable):
    """ SPI device interface that allows for register reads and writes via SPI.
    The SPI transaction format matches:

        in:  WAAAAAAA[...] VVVVVVVV[...]
        out: XXXXXXXX[...] RRRRRRRR[...]

    Where:
        W = write bit; a '1' indicates that the provided value is a write request
        A = all bits of the address
        V = value to be written into the register, if W is set
        R = value to be read from the register

    I/O signals:
        I: sck           -- SPI clock, from the SPI master
        I: sdi           -- SPI data in
        O: sdo           -- SPI data out
        I: cs            -- chip select, active high (as we assume your I/O will use PinsN)

        O: idle          -- true iff the register interface is currently doing nothing
        O: stalled       -- true iff the register interface cannot accept data until this transaction ends

    Other I/O ports are added dynamically with add_register().
    """
    def __init__(self,
                 address_size=15,
                 register_size=32,
                 default_read_value=0,
                 support_size_autonegotiation=True):
        """
        Parameters:
            address_size       -- the size of an address, in bits; recommended to be one bit
                                  less than a binary number, as the write command is formed by adding a one-bit
                                  write flag to the start of every address
            register_size      -- The size of any given register, in bits.
            default_read_value -- The read value read from a non-existent or write-only register.

            support_size_autonegotiation --
                If set, register 0 is used as a size auto-negotiation register. Functionally equivalent to
                calling .support_size_autonegotiation(); see its documentation for details on autonegotiation.
        """

        self.address_size = address_size
        self.register_size = register_size
        self.default_read_value = default_read_value

        #
        # I/O port
        #
        self.idle = Signal()
        self.stalled = Signal()

        # Create our SPI I/O.
        self.spi = SPIBus()

        #
        # Internal details.
        #

        # Instantiate an SPI command transciever submodule.
        self.interface = SPICommandInterface(command_size=address_size + 1,
                                             word_size=register_size)

        # Create a new, empty dictionary mapping registers to their signals.
        self.registers = {}

        # Create signals for each of our register control signals.
        self._is_write = Signal()
        self._address = Signal(self.address_size)

        if support_size_autonegotiation:
            self.support_size_autonegotiation()

    def _ensure_register_is_unused(self, address):
        """ Checks to make sure a register address isn't in use before issuing it. """

        if address in self.registers:
            raise ValueError(
                "can't add more than one register with address 0x{:x}!".format(
                    address))

    def support_size_autonegotiation(self):
        """ Support autonegotiation of register and address size. Consumes address 0.

        Auto-negotiation of size is relatively simple: the host sends a string of zeroes over
        the SPI bus, and we respond with:

            -- as many zeroes as there are address bits
            -- as many ones as there are data bits
            -- zeroes for any bits after

        In practice, this is functionally identical to setting register zero to a constant of all 1's.
        """
        self.add_read_only_register(0, read=-1)

    def add_sfr(self,
                address,
                *,
                read=None,
                write_signal=None,
                write_strobe=None,
                read_strobe=None):
        """ Adds a special function register to the given command interface.

        Parameters:
            address       -- the register's address, as a big-endian integer
            read          -- a Signal or integer constant representing the
                             value to be read at the given address; if not provided, the default
                             value will be read
            read_strobe   -- a Signal that is asserted when a read is completed; if not provided,
                             the relevant strobe will be left unconnected
            write_signal  -- a Signal set to the value to be written when a write is requested;
                             if not provided, writes will be ignored
            wrote_strobe  -- a Signal that goes high when a value is available for a write request
         """

        assert address < (2**self.address_size)
        self._ensure_register_is_unused(address)

        # Add the register to our collection.
        self.registers[address] = {
            'read': read,
            'write_signal': write_signal,
            'write_strobe': write_strobe,
            'read_strobe': read_strobe,
            'elaborate': None,
        }

    def add_read_only_register(self, address, *, read, read_strobe=None):
        """ Adds a read-only register.

        Parameters:
            address       -- the register's address, as a big-endian integer
            read          -- a Signal or integer constant representing the
                             value to be read at the given address; if not provided, the default
                             value will be read
            read_strobe   -- a Signal that is asserted when a read is completed; if not provided,
                             the relevant strobe will be left unconnected
        """
        self.add_sfr(address, read=read, read_strobe=read_strobe)

    def add_register(self,
                     address,
                     *,
                     value_signal=None,
                     size=None,
                     name=None,
                     read_strobe=None,
                     write_strobe=None,
                     reset=0):
        """ Adds a standard, memory-backed register.

            Parameters:
                address       -- the register's address, as a big-endian integer
                value_signal  -- the signal that will store the register's value; if omitted
                                 a storage register will be created automatically
                size          -- if value_signal isn't provided, this sets the size of the created register
                reset         -- if value_signal isn't provided, this sets the reset value of the created register
                read_strobe   -- a Signal to be asserted when the register is read; ignored if not provided
                write_strobe  -- a Signal to be asserted when the register is written; ignored if not provided

            Returns:
                value_signal  -- a signal that stores the register's value; which may be the value_signal arg,
                                 or may be a signal created during execution
        """
        self._ensure_register_is_unused(address)

        # Generate a name for the register, if we don't already have one.
        name = name if name else "register_{:x}".format(address)

        # Generate a backing store for the register, if we don't already have one.
        if value_signal is None:
            size = self.register_size if (size is None) else size
            value_signal = Signal(size, name=name, reset=reset)

        # If we don't have a write strobe signal, create an internal one.
        if write_strobe is None:
            write_strobe = Signal(name=name + "_write_strobe")

        # Create our register-value-input and our write strobe.
        write_value = Signal.like(value_signal, name=name + "_write_value")

        # Create a generator for a the fragments that will manage the register's memory.
        def _elaborate_memory_register(m):
            with m.If(write_strobe):
                m.d.sync += value_signal.eq(write_value)

        # Add the register to our collection.
        self.registers[address] = {
            'read': value_signal,
            'write_signal': write_value,
            'write_strobe': write_strobe,
            'read_strobe': read_strobe,
            'elaborate': _elaborate_memory_register,
        }

        return value_signal

    def _elaborate_register(self, m, register_address, connections):
        """ Generates the hardware connections that handle a given register. """

        #
        # Elaborate our register hardware.
        #

        # Create a signal that goes high iff the given register is selected.
        register_selected = Signal(
            name="register_address_matches_{:x}".format(register_address))
        m.d.comb += register_selected.eq(self._address == register_address)

        # Our write signal is always connected to word_received; but it's only meaningful
        # when write_strobe is asserted.
        if connections['write_signal'] is not None:
            m.d.comb += connections['write_signal'].eq(
                self.interface.word_received)

        # If we have a write strobe, assert it iff:
        #  - this register is selected
        #  - the relevant command is a write command
        #  - we've just finished receiving the command's argument
        if connections['write_strobe'] is not None:
            m.d.comb += [
                connections['write_strobe'].eq(self._is_write
                                               & self.interface.word_complete
                                               & register_selected)
            ]

        # Create essentially the same connection with the read strobe.
        if connections['read_strobe'] is not None:
            m.d.comb += [
                connections['read_strobe'].eq(~self._is_write
                                              & self.interface.word_complete
                                              & register_selected)
            ]

        # If we have any additional code that assists in elaborating this register, run it.
        if connections['elaborate']:
            connections['elaborate'](m)

    def elaborate(self, platform):
        m = Module()

        # Connect up our SPI transceiver submodule.
        m.submodules.interface = self.interface
        m.d.comb += [
            self.interface.spi.connect(self.spi),
            self.idle.eq(self.interface.idle),
            self.stalled.eq(self.interface.stalled)
        ]

        # 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.
        for address, connections in self.registers.items():

            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
示例#28
0
    def __init__(self):

        #
        # I/O port.
        #

        self.ulpi_data_in = Signal(8)
        self.ulpi_data_out = Signal(8)
        self.ulpi_out_req = Signal()
        self.ulpi_dir = Signal()
        self.ulpi_next = Signal()
        self.ulpi_stop = Signal()

        self.busy = Signal()
        self.address = Signal(6)
        self.done = Signal()

        self.read_request = Signal()
        self.read_data = Signal(8)

        self.write_request = Signal()
        self.write_data = Signal(8)
示例#29
0
class Cache(Elaboratable):
    def __init__(
        self,
        nlines: int,  # number of lines
        nwords: int,  # number of words x line x way
        nways: int,  # number of ways
        start_addr: int = 0,  # start of cacheable region
        end_addr: int = 2**32,  # end of cacheable region
        enable_write: bool = True  # enable writes to cache
    ) -> None:
        # enable write -> data cache
        if nlines == 0 or (nlines & (nlines - 1)):
            raise ValueError(f'nlines must be a power of 2: {nlines}')
        if nwords not in (4, 8, 16):
            raise ValueError(f'nwords must be 4, 8 or 16: {nwords}')
        if nways not in (1, 2):
            raise ValueError(f'nways must be 1 or 2: {nways}')

        self.enable_write = enable_write
        self.nlines = nlines
        self.nwords = nwords
        self.nways = nways
        self.start_addr = start_addr
        self.end_addr = end_addr
        offset_bits = log2_int(nwords)
        line_bits = log2_int(nlines)
        addr_bits = log2_int(end_addr - start_addr, need_pow2=False)
        tag_bits = addr_bits - line_bits - offset_bits - 2  # -2 because word line.
        extra_bits = 32 - tag_bits - line_bits - offset_bits - 2

        self.pc_layout = [('byte', 2), ('offset', offset_bits),
                          ('line', line_bits), ('tag', tag_bits)]
        if extra_bits != 0:
            self.pc_layout.append(('unused', extra_bits))

        # -------------------------------------------------------------------------
        # IO
        self.s1_address = Record(self.pc_layout)
        self.s1_flush = Signal()
        self.s1_valid = Signal()
        self.s1_stall = Signal()
        self.s1_access = Signal()
        self.s2_address = Record(self.pc_layout)
        self.s2_evict = Signal()
        self.s2_valid = Signal()
        self.s2_stall = Signal()
        self.s2_access = Signal()
        self.s2_miss = Signal()
        self.s2_rdata = Signal(32)
        self.s2_re = Signal()
        if enable_write:
            self.s2_wdata = Signal(32)
            self.s2_sel = Signal(4)
            self.s2_we = Signal()

        self.bus_addr = Record(self.pc_layout)
        self.bus_valid = Signal()
        self.bus_last = Signal()
        self.bus_data = Signal(32)
        self.bus_ack = Signal()
        self.bus_err = Signal()

        self.access_cnt = Signal(40)
        self.miss_cnt = Signal(40)
        # snoop bus
        if not enable_write:
            self.dcache_snoop = InternalSnoopPort(
                name='cache_snoop'
            )  # RO cache. Implement the Internal snooping port

    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        snoop_addr = Record(self.pc_layout)
        snoop_valid = Signal()

        # -------------------------------------------------------------------------
        # Performance counter
        # TODO: connect to CSR's performance counter
        with m.If(~self.s1_stall & self.s1_valid & self.s1_access):
            m.d.sync += self.access_cnt.eq(self.access_cnt + 1)
        with m.If(self.s2_valid & self.s2_miss & ~self.bus_valid
                  & self.s2_access):
            m.d.sync += self.miss_cnt.eq(self.miss_cnt + 1)
        # -------------------------------------------------------------------------

        way_layout = [('data', 32 * self.nwords),
                      ('tag', self.s1_address.tag.shape()), ('valid', 1),
                      ('sel_lru', 1), ('snoop_hit', 1)]
        if self.enable_write:
            way_layout.append(('sel_we', 1))

        ways = Array(
            Record(way_layout, name='way_idx{}'.format(_way))
            for _way in range(self.nways))
        fill_cnt = Signal.like(self.s1_address.offset)

        # Check hit/miss
        way_hit = m.submodules.way_hit = Encoder(self.nways)
        for idx, way in enumerate(ways):
            m.d.comb += way_hit.i[idx].eq((way.tag == self.s2_address.tag)
                                          & way.valid)

        m.d.comb += self.s2_miss.eq(way_hit.n)
        if self.enable_write:
            # Asumiendo que hay un HIT, indicar que la vía que dió hit es en la cual se va a escribir
            m.d.comb += ways[way_hit.o].sel_we.eq(self.s2_we & self.s2_valid)

        # set the LRU
        if self.nways == 1:
            # One way: LRU is useless
            lru = Const(0)  # self.nlines
        else:
            # LRU es un vector de N bits, cada uno indicado el set a reemplazar
            # como NWAY es máximo 2, cada LRU es de un bit
            lru = Signal(self.nlines)
            _lru = lru.bit_select(self.s2_address.line, 1)
            write_ended = self.bus_valid & self.bus_ack & self.bus_last  # err ^ ack = = 1
            access_hit = ~self.s2_miss & self.s2_valid & (way_hit.o == _lru)
            with m.If(write_ended | access_hit):
                m.d.sync += _lru.eq(~_lru)

        # read data from the cache
        m.d.comb += self.s2_rdata.eq(ways[way_hit.o].data.word_select(
            self.s2_address.offset, 32))

        # Internal Snoop
        snoop_use_cache = Signal()
        snoop_tag_match = Signal()
        snoop_line_match = Signal()
        snoop_cancel_refill = Signal()
        if not self.enable_write:
            bits_range = log2_int(self.end_addr - self.start_addr,
                                  need_pow2=False)

            m.d.comb += [
                snoop_addr.eq(self.dcache_snoop.addr),  # aux
                snoop_valid.eq(self.dcache_snoop.we & self.dcache_snoop.valid
                               & self.dcache_snoop.ack),
                snoop_use_cache.eq(snoop_addr[bits_range:] == (
                    self.start_addr >> bits_range)),
                snoop_tag_match.eq(snoop_addr.tag == self.s2_address.tag),
                snoop_line_match.eq(snoop_addr.line == self.s2_address.line),
                snoop_cancel_refill.eq(snoop_use_cache & snoop_valid
                                       & snoop_line_match & snoop_tag_match),
            ]
        else:
            m.d.comb += snoop_cancel_refill.eq(0)

        with m.FSM():
            with m.State('READ'):
                with m.If(self.s2_re & self.s2_miss & self.s2_valid):
                    m.d.sync += [
                        self.bus_addr.eq(self.s2_address),
                        self.bus_valid.eq(1),
                        fill_cnt.eq(self.s2_address.offset - 1)
                    ]
                    m.next = 'REFILL'
            with m.State('REFILL'):
                m.d.comb += self.bus_last.eq(fill_cnt == self.bus_addr.offset)
                with m.If(self.bus_ack):
                    m.d.sync += self.bus_addr.offset.eq(self.bus_addr.offset +
                                                        1)
                with m.If(self.bus_ack & self.bus_last | self.bus_err):
                    m.d.sync += self.bus_valid.eq(0)
                with m.If(~self.bus_valid | self.s1_flush
                          | snoop_cancel_refill):
                    m.next = 'READ'
                    m.d.sync += self.bus_valid.eq(0)

        # mark the way to use (replace)
        m.d.comb += ways[lru.bit_select(self.s2_address.line,
                                        1)].sel_lru.eq(self.bus_valid)

        # generate for N ways
        for way in ways:
            # create the memory structures for valid, tag and data.
            valid = Signal(self.nlines)  # Valid bits

            tag_m = Memory(width=len(way.tag), depth=self.nlines)  # tag memory
            tag_rp = tag_m.read_port()
            snoop_rp = tag_m.read_port()
            tag_wp = tag_m.write_port()
            m.submodules += tag_rp, tag_wp, snoop_rp

            data_m = Memory(width=len(way.data),
                            depth=self.nlines)  # data memory
            data_rp = data_m.read_port()
            data_wp = data_m.write_port(
                granularity=32
            )  # implica que solo puedo escribir palabras de 32 bits.
            m.submodules += data_rp, data_wp

            # handle valid
            with m.If(self.s1_flush & self.s1_valid):  # flush
                m.d.sync += valid.eq(0)
            with m.Elif(way.sel_lru & self.bus_last
                        & self.bus_ack):  # refill ok
                m.d.sync += valid.bit_select(self.bus_addr.line, 1).eq(1)
            with m.Elif(way.sel_lru & self.bus_err):  # refill error
                m.d.sync += valid.bit_select(self.bus_addr.line, 1).eq(0)
            with m.Elif(self.s2_evict & self.s2_valid
                        & (way.tag == self.s2_address.tag)):  # evict
                m.d.sync += valid.bit_select(self.s2_address.line, 1).eq(0)

            # assignments
            m.d.comb += [
                tag_rp.addr.eq(
                    Mux(self.s1_stall, self.s2_address.line,
                        self.s1_address.line)),
                tag_wp.addr.eq(self.bus_addr.line),
                tag_wp.data.eq(self.bus_addr.tag),
                tag_wp.en.eq(way.sel_lru & self.bus_ack & self.bus_last),
                data_rp.addr.eq(
                    Mux(self.s1_stall, self.s2_address.line,
                        self.s1_address.line)),
                way.data.eq(data_rp.data),
                way.tag.eq(tag_rp.data),
                way.valid.eq(valid.bit_select(self.s2_address.line, 1))
            ]

            # update cache: CPU or Refill
            # El puerto de escritura se multiplexa debido a que la memoria solo puede tener un
            # puerto de escritura.
            if self.enable_write:
                update_addr = Signal(len(data_wp.addr))
                update_data = Signal(len(data_wp.data))
                update_we = Signal(len(data_wp.en))
                aux_wdata = Signal(32)

                with m.If(self.bus_valid):
                    m.d.comb += [
                        update_addr.eq(self.bus_addr.line),
                        update_data.eq(Repl(self.bus_data, self.nwords)),
                        update_we.bit_select(self.bus_addr.offset,
                                             1).eq(way.sel_lru & self.bus_ack),
                    ]
                with m.Else():
                    m.d.comb += [
                        update_addr.eq(self.s2_address.line),
                        update_data.eq(Repl(aux_wdata, self.nwords)),
                        update_we.bit_select(self.s2_address.offset,
                                             1).eq(way.sel_we & ~self.s2_miss)
                    ]
                m.d.comb += [
                    # Aux data: no tengo granularidad de byte en el puerto de escritura. Así que para el
                    # caso en el cual el CPU tiene que escribir, hay que construir el dato (wrord) a reemplazar
                    aux_wdata.eq(
                        Cat(
                            Mux(self.s2_sel[0],
                                self.s2_wdata.word_select(0, 8),
                                self.s2_rdata.word_select(0, 8)),
                            Mux(self.s2_sel[1],
                                self.s2_wdata.word_select(1, 8),
                                self.s2_rdata.word_select(1, 8)),
                            Mux(self.s2_sel[2],
                                self.s2_wdata.word_select(2, 8),
                                self.s2_rdata.word_select(2, 8)),
                            Mux(self.s2_sel[3],
                                self.s2_wdata.word_select(3, 8),
                                self.s2_rdata.word_select(3, 8)))),
                    #
                    data_wp.addr.eq(update_addr),
                    data_wp.data.eq(update_data),
                    data_wp.en.eq(update_we),
                ]
            else:
                m.d.comb += [
                    data_wp.addr.eq(self.bus_addr.line),
                    data_wp.data.eq(Repl(self.bus_data, self.nwords)),
                    data_wp.en.bit_select(self.bus_addr.offset,
                                          1).eq(way.sel_lru & self.bus_ack),
                ]

                # --------------------------------------------------------------
                # intenal snoop
                # for FENCE.i instruction
                _match_snoop = Signal()

                m.d.comb += [
                    snoop_rp.addr.eq(snoop_addr.line),  # read tag memory
                    _match_snoop.eq(snoop_rp.data == snoop_addr.tag),
                    way.snoop_hit.eq(snoop_use_cache & snoop_valid
                                     & _match_snoop
                                     & valid.bit_select(snoop_addr.line, 1)),
                ]
                # check is the snoop match a write from this core
                with m.If(way.snoop_hit):
                    m.d.sync += valid.bit_select(snoop_addr.line, 1).eq(0)
                # --------------------------------------------------------------

        return m
示例#30
0
 def __init__(self, width):
     self.restart = Signal()
     self.en = Signal()
     self.done = Signal()
     self.max = Signal(width)