Skip to content

Commit 50d6b9f

Browse files
committed
Add comprehensive guide for using pin signatures and software drivers
Created detailed documentation covering: - How to use pin signatures (UARTSignature, GPIOSignature, etc.) - How to create peripherals with SoftwareDriverSignature - Organizing driver C/H files alongside Python peripherals - Using attach_data() to load software into flash - Complete working examples from chipflow-digital-ip This guide bridges the gap between the raw API reference and practical usage, using real examples from the chipflow-digital-ip library.
1 parent cfa7d5b commit 50d6b9f

File tree

2 files changed

+353
-0
lines changed

2 files changed

+353
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ It is developed at https://github.com/chipFlow/chipflow-lib/ and licensed `BSD 2
1212
getting-started
1313
chipflow-toml-guide
1414
chipflow-commands
15+
using-pin-signatures
1516
API Reference <autoapi/index>
1617
platform-api

docs/using-pin-signatures.rst

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
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

Comments
 (0)