Building USoC Part 3: Introduction to MACs and PHYs

When designing digital interfaces, it is incredibly easy to get bogged down in the acronym soup of modern protocols. We treat UART, SPI, QSPI, I2C, and PCIe as entirely distinct entities, each requiring its own siloed hardware controller. Worse, modern enterprise standards like USB4 attempt to multiplex PCIe, USB3, and DisplayPort into a massive, quirky, redundant labyrinth, largely because corporate IP holders refuse to leave legacy baggage behind. But if you strip away the marketing definitions it boils down to two things: who is allowed to talk when (the MAC layer), and how the electrical signals represent data (the PHY layer).

Part 1: Slicing the MAC Layer (Interface, Medium and Traffic Patterns)

The Media Access Control (MAC) layer only cares about the rules of the conversation. When breaking down classic protocols, the MAC layer differences split cleanly across two axes and three traffic patterns:

  1. Interface Axis: Full-Duplex (Separate TX/RX) vs. Half-Duplex (Shared Lane)
  2. Medium Axis: Point-to-Point (Exclusive Pair) vs. Shared Medium (Addressed/Arbitrated)

While at the protocol layer we have three types of traffic patterns:

  1. Control: Fire and forget, timeouts are used to prevent deadlocks and corrupted packets are discarded.
  2. Data: Automatic Repeat Request (ARQ) protocol is used to ensure that all packets arrive uncorrupted and no buffers are overrun.
  3. Isochronous: Timeslices are reserved to send data of predefined size at predefined intervals. No retries are required and frames arriving too late or corrupted are dropped.

This leads to the following high level behavioral matrix:

Traffic Pattern Packet Filtering Mechanism Corrupted Packet Mechanism
Control None Discard
Data (Half Duplex) Destination Address Stop and Wait ARQ
Data (Full Duplex) None Go Back N ARQ
Isochronous Arrival Time Discard

Part 2: Slicing the PHY Layer (How the Clock Moves)

The Physical (PHY) handles receiving and transmitting symbols accross a physical medium that can range from copper to fiber to electromagnetic waves. To distinguish a period of silence from a sequence of zero bits, a signal always requires a clock. How the clock is transmitted and recovered is the main axis of differentiation at the PHY layer.

  • Synchronous Digital: The transmitter sends a dedicated, explicit clock signal on a separate wire or strobe right next to the data bundle (e.g., SPI, I2S). The receiver simply samples data directly on the clock edges. Flight time doesn’t matter because data and clock travel together.
  • Asynchronous Digital: No clock wire exists. The receiver relies on a local clock running faster than the data stream to sample the input data. The sampling vs data rate is the oversampling rate and is usually 8x or 16x the data rate.
  • Asynchronous Analog: An Analog High-Speed SerDes transceiver/receiver is used to lock onto transitions and extract the embedded clock. This requires constant transitions (like 8b/10b encoding) and continuous “Idle symbols” to keep the clock synchronization.
  • Radio / Wireless: Modulating data onto an electromagnetic carrier wave across the airwaves (RF), requiring synthesis of carrier frequencies, mixing, and filtering. The clock is recoverred through Carrier Frequency Offset and Frame Sync Correlation.

Part 3: The Unified MAC-PHY Interface

The Transmit Pipe

  • tx_en: Driven by the MAC. Enables the transmission subsystem.
  • tx_ready: Backpressure signal driven by the PHY, indicating it can accept the next symbol block. This is asserted once PLLs are stabilized and symbol lock is acquired.
  • tx_valid: Asserted by the MAC when valid payload data is ready.
  • tx_data: The actual parallel payload vector, parameterized over the width.
  • tx_elec_idle: Asserted by the MAC to indicate that there is currently no data to send. In a full-duplex system this could enable a fast wake-up power saving mode. In a half-duplex system this releases the output enable pin allowing other parties to claim the bus.

The Receive Pipe

  • rx_en: Driven by the MAC to tell the PHY’s sampling/receiver circuits to wake up and listen.
  • rx_valid: Driven by the PHY when a valid, symbol has been recovered and placed on the data bus.
  • rx_data: The recovered parallel payload vector.
  • rx_elec_idle: Driven by the PHY to signal the medium’s energy state. When rx_elec_idle = 1, the physical medium is completely free/silent. In a shared- medium system, rx_elec_idle can change while transmitting indicating a collision and releasing the bus.

Mapping the Interface Across Every PHY Type

The beauty of this interface layout is that the structural meaning of these signals scales seamlessly across completely different physical environments:

  • tx_elec_idle = 0:
    • Digital: Drives Output Enable of a Tristate Buffer.
    • Async Analog: Disables the Analog Squelch Circuit and starts sending link training symbols to resynchronize the clocks.
    • Radio / Wireless: Powers up the Power Amplifier and starts transmitting the carrier wave.
  • rx_elec_idle = 1:
    • Digital: Indicates the line is floating high (idle/free state).
    • Async Analog: Maps directly to the analog squelch circuit; indicates no differential energy on the RX pair.
    • Radio / Wireless: Indicates a clear channel; no RSSI energy detected above the squelch threshold.
  • rx_elec_idle = 0:
    • Digital: Signals an Electrical Collision on a shared medium (e.g., I2C multi-master collision).
    • Async Analog: Starts link training by driving tx_elec_idle = 1.
    • Radio / Wireless: Signals a Wireless Collision on the carrier frequency.

Part 4: PhyGpio Parameterization - The Universal Digital PHY

Once you view the world through this lens, you realize that advanced techniques like Double Data Rate (DDR) or Multi-Phase PLLs are not protocols—they are just timing optimization tools.

If we externalize the clock generation and phase management, we can build a single, hardware block parameterized to satisfy almost every major interface. This led to the design of the PhyGpio block, parameterized by five variables:

  1. symbol_width: The number of sequential bits or parallel words to shift out per transaction phase.
  2. bus_width: The number of physical data pins allocated to the lane.
  3. oversampling_rate: Actively decimates the transmission speed relative to the master clock strobe. It tells the internal shift registers how many clock ticks to hold a bit on a pin before advancing, allowing a fast external oversampling clock to run edge-detection without accelerating the outbound data rate.
  4. ddr (bool): If true, the interface drives and samples data on both the rising and falling edges of the clock strobe. The bus_width automatically widens to bus_width * 2 to feed both edges simultaneously within a single clock cycle.
  5. half_duplex (bool): If true, reads back the driven value to detect collisions. If driving the output low but reading back a high/idle input, it means another party is using the bus at the same time and the transaction needs to be retried once rx_elec_idle=1.

How Different Protocols Map onto PhyGpio

By dynamically feeding these parameters, a single piece of digital logic morphs seamlessly:

  • UART:
    • symbol_width = 10
    • bus_width = 1
    • oversampling_rate = 16 (asynchronous decimation)
    • ddr = false
    • half_duplex = false
    • Behavior: The MAC packs the [Start (0) | 8 Data Bits | Stop (1)] payload directly into a single 10-bit word and drives the PHY clock at 16x the target baud rate.
  • SPI:
    • symbol_width = 8
    • bus_width = 1
    • oversampling_rate = 1 (synchronous 1:1 clock pass-through)
    • ddr = false
    • half_duplex = false
    • Behavior: The MAC handles the slave select line and interfaces directly with the external synchronous clock logic.
  • Quad SPI (DDR Mode):
    • symbol_width = 8
    • bus_width = 4
    • oversampling_rate = 1
    • ddr = true
    • half_duplex = false
    • Behavior: The data buses widen to 8 bits internally to feed the native physical ODDR/IDDR primitives at the I/O edge directly.
  • I2C:
    • symbol_width = 8
    • bus_width = 1
    • oversampling_rate = 1
    • ddr = false
    • half_duplex = true
    • Behavior: Fits our abstraction slightly awkwardly. To minimize hardware complexity for low-speed chip-to-chip communication, I2C bakes a MAC-level flow control mechanism (the 9th-bit ACK/NACK signal) directly into the PHY layer. While highly efficient for its intended use case, this interleaving of layers means a unified PHY wrapper has to account for protocol-specific timing exceptions during the transaction phase.

Conclusion

The takeaway here is that while a universal PHY/MAC can handle the vast majority of use cases, legacy protocol quirks (like I2C’s 9th-bit ACK) mean we must still allow for minor, protocol-specific edge cases. We have arrived at a clear design for the communication layer of our UBUS protocol. In the upcoming entries of this series, we will dive much deeper into the actual implementation details:

  • Post 4: PhyGpio – We will dive straight into the actual Amaranth HDL code for PhyGpio and see how to wire it up to a custom LinkUart MAC controller.
  • Post 5: Moving to Gigabit SerDes – Transitioning to gigabit streams with PhySerDes, building a Code8b10b block, and architecting the high-speed LinkSerDes8b10b and LinkDualSerdes8b10b layers.
  • Post 6: The Ultimate Integration: LinkUsbc – Bringing it all together, we will build a complete Type-C subsystem that runs LinkUart over the SBU pins for low-speed sideband control while concurrently piping LinkDualSerdes8b10b down the high-speed SuperSpeed lines.
  • Post 7: Building the universal UBUS MAC layer – Handling traffic, ARQ and filtering patterns.

This site uses Just the Docs, a documentation theme for Jekyll.