MCU SoC Example
The mcu_soc example demonstrates a more complete microcontroller-style System-on-Chip with multiple peripherals, targeting the IHP SG13G2 process.
Overview
This example creates a full-featured MCU with:
CV32E40P RISC-V CPU - A 32-bit RISC-V processor with debug support
JTAG Debug Module - Full debug capabilities via JTAG interface
QSPI Flash - External flash memory for code storage
SRAM - 2KB of on-chip RAM
Multiple GPIO Banks - 2 banks of 8-bit GPIO (16 total)
Multiple UARTs - 2 UART interfaces
SPI Controllers - 3 user SPI interfaces
I2C Controllers - 2 I2C bus interfaces
PWM Controllers - 10 motor PWM outputs
Project Structure
mcu_soc/
├── chipflow.toml # Project configuration
├── design/
│ ├── design.py # Main SoC design
│ ├── ips/ # Custom IP cores (PWM, etc.)
│ ├── openocd/ # OpenOCD debug configuration
│ ├── software/ # Firmware source code
│ ├── steps/ # Custom build steps
│ └── tests/ # Test reference data
└── README.md
Configuration
The chipflow.toml targets the IHP SG13G2 process with a PGA144 package:
[chipflow]
project_name = "chipflow-examples"
[chipflow.top]
soc = "design.design:MySoC"
[chipflow.steps]
board = "design.steps.board:MyBoardStep"
[chipflow.silicon]
process = "ihp_sg13g2"
package = "pga144"
[chipflow.test]
event_reference = "design/tests/events_reference.json"
Peripheral Configuration
The MCU SoC uses parameterized peripheral counts:
self.user_spi_count = 3 # 3 SPI interfaces
self.i2c_count = 2 # 2 I2C interfaces
self.motor_count = 10 # 10 PWM channels
self.uart_count = 2 # 2 UART interfaces
self.gpio_banks = 2 # 2 GPIO banks
self.gpio_width = 8 # 8 bits per bank
Interface Declaration
The design dynamically creates interfaces based on peripheral counts:
interfaces = {
"flash": Out(QSPIFlashSignature()),
"cpu_jtag": Out(JTAGSignature())
}
for i in range(self.user_spi_count):
interfaces[f"user_spi_{i}"] = Out(SPISignature())
for i in range(self.i2c_count):
interfaces[f"i2c_{i}"] = Out(I2CSignature())
for i in range(self.uart_count):
interfaces[f"uart_{i}"] = Out(UARTSignature())
for i in range(self.gpio_banks):
interfaces[f"gpio_{i}"] = Out(GPIOSignature(pin_count=self.gpio_width))
Memory Map
Region |
Base Address |
Description |
|---|---|---|
SPI Flash |
|
Code storage |
SRAM |
|
2KB on-chip RAM |
Debug |
|
Debug module |
SPI Flash CSR |
|
Flash control registers |
GPIO CSR |
|
GPIO registers (offset |
UART CSR |
|
UART registers (offset |
SoC ID CSR |
|
SoC identification |
User SPI CSR |
|
SPI registers (offset |
I2C CSR |
|
I2C registers (offset |
Motor PWM CSR |
|
PWM registers (offset |
Debug Support
The MCU SoC includes full JTAG debug support via the CV32E40P debug module:
debug = OBIDebugModule()
wb_arbiter.add(debug.initiator)
wb_decoder.add(debug.target, name="debug", addr=self.debug_base)
m.d.comb += cpu.debug_req.eq(debug.debug_req)
m.d.comb += [
debug.jtag_tck.eq(self.cpu_jtag.tck.i),
debug.jtag_tms.eq(self.cpu_jtag.tms.i),
debug.jtag_tdi.eq(self.cpu_jtag.tdi.i),
debug.jtag_trst.eq(self.cpu_jtag.trst.i),
self.cpu_jtag.tdo.o.eq(debug.jtag_tdo),
]
OpenOCD configuration files are provided in design/openocd/ for hardware debugging.
Custom IP: PWM Controller
The example includes a custom PWM IP for motor control in design/ips/pwm.py. This demonstrates how to create custom peripherals with their own IO signatures.
Running the Example
cd mcu_soc
pdm chipflow pin lock
pdm sim-check
pdm submit --wait
Key Differences from Minimal
Different CPU: Uses CV32E40P instead of Minerva, with full debug support
More Peripherals: Multiple instances of each peripheral type
JTAG Debug: Full hardware debugging capability
Custom IP: Includes custom PWM peripheral
Different Target: IHP SG13G2 process instead of SKY130
Customization Ideas
Adjust peripheral counts to match your requirements
Add additional custom IP cores
Modify memory sizes for your application
Change the target process/package for different fabrication options