|
| 1 | +Using Pin Signatures and Software Drivers |
| 2 | +========================================== |
| 3 | + |
| 4 | +This guide explains how to use ChipFlow's pin signature system and attach software drivers to your hardware designs. |
| 5 | + |
| 6 | +Overview |
| 7 | +-------- |
| 8 | + |
| 9 | +ChipFlow provides a standardized way to: |
| 10 | + |
| 11 | +1. Define external pin interfaces for your design using **Pin Signatures** (UARTSignature, GPIOSignature, etc.) |
| 12 | +2. Attach software driver code to peripherals using **SoftwareDriverSignature** |
| 13 | +3. Connect pre-built software binaries to flash memory using **attach_data()** |
| 14 | + |
| 15 | +Pin Signatures |
| 16 | +-------------- |
| 17 | + |
| 18 | +Pin signatures define the external interface of your design. ChipFlow provides several built-in signatures for common peripherals: |
| 19 | + |
| 20 | +Available Pin Signatures |
| 21 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 22 | + |
| 23 | +- ``UARTSignature()`` - Serial UART interface (TX, RX) |
| 24 | +- ``GPIOSignature(pin_count)`` - General purpose I/O pins |
| 25 | +- ``SPISignature()`` - SPI master interface (SCK, COPI, CIPO, CSN) |
| 26 | +- ``I2CSignature()`` - I2C bus interface (SCL, SDA) |
| 27 | +- ``QSPIFlashSignature()`` - Quad SPI flash interface |
| 28 | +- ``JTAGSignature()`` - JTAG debug interface |
| 29 | + |
| 30 | +All pin signatures support additional options: |
| 31 | + |
| 32 | +- ``sky130_drive_mode`` - Set drive strength for Sky130 (e.g., ``Sky130DriveMode.OPEN_DRAIN_STRONG_UP``) |
| 33 | + |
| 34 | +Using Pin Signatures in Your Top-Level Design |
| 35 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 36 | + |
| 37 | +Pin signatures are used when defining your top-level component's interface: |
| 38 | + |
| 39 | +.. code-block:: python |
| 40 | +
|
| 41 | + from amaranth.lib.wiring import Out |
| 42 | + from chipflow_lib.platforms import UARTSignature, GPIOSignature, QSPIFlashSignature |
| 43 | +
|
| 44 | + class MySoC(wiring.Component): |
| 45 | + def __init__(self): |
| 46 | + super().__init__({ |
| 47 | + "uart": Out(UARTSignature()), |
| 48 | + "gpio": Out(GPIOSignature(pin_count=8)), |
| 49 | + "flash": Out(QSPIFlashSignature()), |
| 50 | + }) |
| 51 | +
|
| 52 | +These signatures tell ChipFlow how to connect your design to the physical pins of your chip. |
| 53 | + |
| 54 | +Sky130-Specific Pin Configuration |
| 55 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 56 | + |
| 57 | +For Sky130 chips, you can configure the I/O cell drive mode: |
| 58 | + |
| 59 | +.. code-block:: python |
| 60 | +
|
| 61 | + from chipflow_lib.platforms import Sky130DriveMode, GPIOSignature |
| 62 | +
|
| 63 | + # Use open-drain with strong pull-up for I2C |
| 64 | + super().__init__({ |
| 65 | + "i2c_gpio": Out(GPIOSignature( |
| 66 | + pin_count=2, |
| 67 | + sky130_drive_mode=Sky130DriveMode.OPEN_DRAIN_STRONG_UP |
| 68 | + )) |
| 69 | + }) |
| 70 | +
|
| 71 | +Software Driver Signatures |
| 72 | +--------------------------- |
| 73 | + |
| 74 | +The ``SoftwareDriverSignature`` allows you to attach C/C++ driver code to your hardware peripherals. This is useful for providing software APIs that match your hardware registers. |
| 75 | + |
| 76 | +Creating a Peripheral with Driver Code |
| 77 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 78 | + |
| 79 | +Here's how to create a peripheral that includes software driver code: |
| 80 | + |
| 81 | +.. code-block:: python |
| 82 | +
|
| 83 | + from amaranth.lib.wiring import In, Out |
| 84 | + from amaranth_soc import csr |
| 85 | + from chipflow_lib.platforms import UARTSignature, SoftwareDriverSignature |
| 86 | +
|
| 87 | + class UARTPeripheral(wiring.Component): |
| 88 | + def __init__(self, *, addr_width=5, data_width=8, init_divisor=0): |
| 89 | + # Your peripheral implementation here... |
| 90 | +
|
| 91 | + # Define the signature with driver code attached |
| 92 | + super().__init__( |
| 93 | + SoftwareDriverSignature( |
| 94 | + members={ |
| 95 | + "bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)), |
| 96 | + "pins": Out(UARTSignature()), |
| 97 | + }, |
| 98 | + component=self, |
| 99 | + regs_struct='uart_regs_t', # Name of register struct in C |
| 100 | + c_files=['drivers/uart.c'], # C implementation files |
| 101 | + h_files=['drivers/uart.h'] # Header files |
| 102 | + ) |
| 103 | + ) |
| 104 | +
|
| 105 | +Driver File Organization |
| 106 | +~~~~~~~~~~~~~~~~~~~~~~~~ |
| 107 | + |
| 108 | +Driver files should be placed relative to your peripheral's Python file: |
| 109 | + |
| 110 | +.. code-block:: text |
| 111 | +
|
| 112 | + chipflow_digital_ip/io/ |
| 113 | + ├── _uart.py # Peripheral definition |
| 114 | + └── drivers/ |
| 115 | + ├── uart.h # Header with register struct and API |
| 116 | + └── uart.c # Implementation |
| 117 | +
|
| 118 | +Example Header File (uart.h) |
| 119 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 120 | + |
| 121 | +.. code-block:: c |
| 122 | +
|
| 123 | + #ifndef UART_H |
| 124 | + #define UART_H |
| 125 | +
|
| 126 | + #include <stdint.h> |
| 127 | +
|
| 128 | + // Register structure matching your hardware |
| 129 | + typedef struct __attribute__((packed, aligned(4))) { |
| 130 | + uint8_t config; |
| 131 | + uint8_t padding_0[3]; |
| 132 | + uint32_t phy_config; |
| 133 | + uint8_t status; |
| 134 | + uint8_t data; |
| 135 | + uint8_t padding_1[6]; |
| 136 | + } uart_mod_regs_t; |
| 137 | +
|
| 138 | + typedef struct __attribute__((packed, aligned(4))) { |
| 139 | + uart_mod_regs_t rx; |
| 140 | + uart_mod_regs_t tx; |
| 141 | + } uart_regs_t; |
| 142 | +
|
| 143 | + // Driver API |
| 144 | + void uart_init(volatile uart_regs_t *uart, uint32_t divisor); |
| 145 | + void uart_putc(volatile uart_regs_t *uart, char c); |
| 146 | + void uart_puts(volatile uart_regs_t *uart, const char *s); |
| 147 | +
|
| 148 | + #endif |
| 149 | +
|
| 150 | +The register structure must use ``__attribute__((packed, aligned(4)))`` to match the hardware layout. |
| 151 | + |
| 152 | +Example Implementation File (uart.c) |
| 153 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 154 | + |
| 155 | +.. code-block:: c |
| 156 | +
|
| 157 | + #include "uart.h" |
| 158 | +
|
| 159 | + void uart_init(volatile uart_regs_t *uart, uint32_t divisor) { |
| 160 | + uart->tx.config = 0; |
| 161 | + uart->tx.phy_config = divisor & 0x00FFFFFF; |
| 162 | + uart->tx.config = 1; |
| 163 | + uart->rx.config = 0; |
| 164 | + uart->rx.phy_config = divisor & 0x00FFFFFF; |
| 165 | + uart->rx.config = 1; |
| 166 | + } |
| 167 | +
|
| 168 | + void uart_putc(volatile uart_regs_t *uart, char c) { |
| 169 | + if (c == '\n') |
| 170 | + uart_putc(uart, '\r'); |
| 171 | + while (!(uart->tx.status & 0x1)) |
| 172 | + ; |
| 173 | + uart->tx.data = c; |
| 174 | + } |
| 175 | +
|
| 176 | +Using Peripherals in Your SoC |
| 177 | +------------------------------ |
| 178 | + |
| 179 | +Here's a complete example of using peripherals with driver code in your top-level design: |
| 180 | + |
| 181 | +.. code-block:: python |
| 182 | +
|
| 183 | + from amaranth import Module |
| 184 | + from amaranth.lib.wiring import Out, flipped, connect |
| 185 | + from amaranth_soc import csr |
| 186 | +
|
| 187 | + from chipflow_digital_ip.io import UARTPeripheral, GPIOPeripheral |
| 188 | + from chipflow_lib.platforms import UARTSignature, GPIOSignature |
| 189 | +
|
| 190 | + class MySoC(wiring.Component): |
| 191 | + def __init__(self): |
| 192 | + super().__init__({ |
| 193 | + "uart_0": Out(UARTSignature()), |
| 194 | + "gpio_0": Out(GPIOSignature(pin_count=8)), |
| 195 | + }) |
| 196 | +
|
| 197 | + def elaborate(self, platform): |
| 198 | + m = Module() |
| 199 | +
|
| 200 | + # Create CSR decoder for peripheral access |
| 201 | + csr_decoder = csr.Decoder(addr_width=28, data_width=8) |
| 202 | + m.submodules.csr_decoder = csr_decoder |
| 203 | +
|
| 204 | + # Instantiate UART peripheral |
| 205 | + m.submodules.uart_0 = uart_0 = UARTPeripheral( |
| 206 | + init_divisor=int(25e6//115200) |
| 207 | + ) |
| 208 | + csr_decoder.add(uart_0.bus, name="uart_0", addr=0x02000000) |
| 209 | +
|
| 210 | + # Connect to top-level pins |
| 211 | + connect(m, flipped(self.uart_0), uart_0.pins) |
| 212 | +
|
| 213 | + # Instantiate GPIO peripheral |
| 214 | + m.submodules.gpio_0 = gpio_0 = GPIOPeripheral(pin_count=8) |
| 215 | + csr_decoder.add(gpio_0.bus, name="gpio_0", addr=0x01000000) |
| 216 | +
|
| 217 | + # Connect to top-level pins |
| 218 | + connect(m, flipped(self.gpio_0), gpio_0.pins) |
| 219 | +
|
| 220 | + return m |
| 221 | +
|
| 222 | +The driver code is automatically collected during the ChipFlow build process and made available to your software. |
| 223 | + |
| 224 | +Attaching Software Binaries |
| 225 | +---------------------------- |
| 226 | + |
| 227 | +The ``attach_data()`` function allows you to attach pre-built software binaries (like bootloaders) to flash memory interfaces. |
| 228 | + |
| 229 | +Basic Usage |
| 230 | +~~~~~~~~~~~ |
| 231 | + |
| 232 | +.. code-block:: python |
| 233 | +
|
| 234 | + from pathlib import Path |
| 235 | + from chipflow_lib.platforms import attach_data, SoftwareBuild |
| 236 | +
|
| 237 | + def elaborate(self, platform): |
| 238 | + m = Module() |
| 239 | +
|
| 240 | + # ... create your flash peripheral (spiflash) ... |
| 241 | +
|
| 242 | + # Build software from source files |
| 243 | + sw = SoftwareBuild( |
| 244 | + sources=Path('design/software').glob('*.c'), |
| 245 | + offset=0x100000 # Start at 1MB offset in flash |
| 246 | + ) |
| 247 | +
|
| 248 | + # Attach to both internal and external interfaces |
| 249 | + attach_data(self.flash, m.submodules.spiflash, sw) |
| 250 | +
|
| 251 | + return m |
| 252 | +
|
| 253 | +The ``attach_data()`` function: |
| 254 | + |
| 255 | +1. Takes the **external interface** (``self.flash``) from your top-level component |
| 256 | +2. Takes the **internal component** (``m.submodules.spiflash``) that implements the flash controller |
| 257 | +3. Takes the **SoftwareBuild** object describing the software to build and load |
| 258 | + |
| 259 | +The software is automatically compiled, linked, and loaded into the simulation or silicon design. |
| 260 | + |
| 261 | +SoftwareBuild Parameters |
| 262 | +~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 263 | + |
| 264 | +.. code-block:: python |
| 265 | +
|
| 266 | + SoftwareBuild( |
| 267 | + sources, # List or glob of .c source files |
| 268 | + includes=[], # List of .h include files to copy |
| 269 | + include_dirs=[], # Additional include directories |
| 270 | + offset=0 # Offset in flash memory (in bytes) |
| 271 | + ) |
| 272 | +
|
| 273 | +Complete Example |
| 274 | +---------------- |
| 275 | + |
| 276 | +Here's a complete working example combining all concepts: |
| 277 | + |
| 278 | +.. code-block:: python |
| 279 | +
|
| 280 | + from pathlib import Path |
| 281 | + from amaranth import Module |
| 282 | + from amaranth.lib import wiring |
| 283 | + from amaranth.lib.wiring import Out, flipped, connect |
| 284 | + from amaranth_soc import csr, wishbone |
| 285 | +
|
| 286 | + from chipflow_digital_ip.io import UARTPeripheral, GPIOPeripheral |
| 287 | + from chipflow_digital_ip.memory import QSPIFlash |
| 288 | + from chipflow_lib.platforms import ( |
| 289 | + UARTSignature, GPIOSignature, QSPIFlashSignature, |
| 290 | + Sky130DriveMode, attach_data, SoftwareBuild |
| 291 | + ) |
| 292 | +
|
| 293 | + class MySoC(wiring.Component): |
| 294 | + def __init__(self): |
| 295 | + # Define top-level pin interfaces |
| 296 | + super().__init__({ |
| 297 | + "flash": Out(QSPIFlashSignature()), |
| 298 | + "uart": Out(UARTSignature()), |
| 299 | + "gpio": Out(GPIOSignature(pin_count=8)), |
| 300 | + "i2c_pins": Out(GPIOSignature( |
| 301 | + pin_count=2, |
| 302 | + sky130_drive_mode=Sky130DriveMode.OPEN_DRAIN_STRONG_UP |
| 303 | + )) |
| 304 | + }) |
| 305 | +
|
| 306 | + self.csr_base = 0xb0000000 |
| 307 | + self.bios_offset = 0x100000 # 1MB |
| 308 | +
|
| 309 | + def elaborate(self, platform): |
| 310 | + m = Module() |
| 311 | +
|
| 312 | + # Create bus infrastructure |
| 313 | + csr_decoder = csr.Decoder(addr_width=28, data_width=8) |
| 314 | + m.submodules.csr_decoder = csr_decoder |
| 315 | +
|
| 316 | + # QSPI Flash with driver |
| 317 | + m.submodules.flash = flash = QSPIFlash(addr_width=24, data_width=32) |
| 318 | + csr_decoder.add(flash.csr_bus, name="flash", addr=0x00000000) |
| 319 | + connect(m, flipped(self.flash), flash.pins) |
| 320 | +
|
| 321 | + # UART with driver (115200 baud at 25MHz clock) |
| 322 | + m.submodules.uart = uart = UARTPeripheral( |
| 323 | + init_divisor=int(25e6//115200) |
| 324 | + ) |
| 325 | + csr_decoder.add(uart.bus, name="uart", addr=0x02000000) |
| 326 | + connect(m, flipped(self.uart), uart.pins) |
| 327 | +
|
| 328 | + # GPIO with driver |
| 329 | + m.submodules.gpio = gpio = GPIOPeripheral(pin_count=8) |
| 330 | + csr_decoder.add(gpio.bus, name="gpio", addr=0x01000000) |
| 331 | + connect(m, flipped(self.gpio), gpio.pins) |
| 332 | +
|
| 333 | + # I2C pins (using GPIO with open-drain) |
| 334 | + m.submodules.i2c = i2c_gpio = GPIOPeripheral(pin_count=2) |
| 335 | + csr_decoder.add(i2c_gpio.bus, name="i2c", addr=0x01100000) |
| 336 | + connect(m, flipped(self.i2c_pins), i2c_gpio.pins) |
| 337 | +
|
| 338 | + # Build and attach BIOS software |
| 339 | + sw = SoftwareBuild( |
| 340 | + sources=Path('design/software').glob('*.c'), |
| 341 | + offset=self.bios_offset |
| 342 | + ) |
| 343 | + attach_data(self.flash, flash, sw) |
| 344 | +
|
| 345 | + return m |
| 346 | +
|
| 347 | +See Also |
| 348 | +-------- |
| 349 | + |
| 350 | +- :doc:`chipflow-toml-guide` - Configuring your ChipFlow project |
| 351 | +- :doc:`autoapi/chipflow_lib/platform/index` - Platform API reference |
| 352 | +- :doc:`platform-api` - Complete platform API including SimPlatform and attach_data |
0 commit comments