In this post, we will explore the challenges of building an SoC using Amaranth HDL to create the top-level design and integrate pre-existing SystemVerilog modules. Amaranth HDL is a modern, Python-based hardware description language designed to simplify hardware development by combining Python’s flexibility with the precision required for digital design. By using Python as a base, Amaranth offers a high level of abstraction, enabling faster, more modular code that’s easier to manage and reuse. However, a major question remains: can Amaranth effectively integrate SoC designs when relying on existing components built in SystemVerilog?
Our SoC includes a CPU core, a bus interconnect, memory for code and data storage, and peripherals such as UART and SPI, all developed in SystemVerilog or Verilog. Integrating these SystemVerilog modules with Amaranth provides an opportunity to evaluate its flexibility and interoperability in a practical SoC environment.
SoC Overview
This section provides a brief overview of the key components in the target SoC shown in Figure-1.
Figure 1: Block diagram of target SoC
a- CPU Core: The Syntacore SCR1 (RV32I) serves as the central processing unit. The Syntacore SCR1 was selected for this project based on several key factors:
- Simplicity: The SCR1 is a lightweight RISC-V core that offers essential RV32I functionality without excessive complexity, making it well-suited for projects that prioritize a lean, efficient design.
- No External Interfaces as Top-Level Ports: SCR1’s design minimizes the number of required top-level interfaces, which simplifies integration within an SoC, as the core directly supports IMEM and DMEM connections to the interconnect.
- Modularity: SCR1’s modular and configurable nature allows for customization, making it easy to adapt to a variety of SoC requirements while maintaining a streamlined, efficient architecture.
Both IMEM and DMEM are connected to the AXI interconnect, enabling the CPU to access instruction/data from RAM and communicate with peripherals.
b- AXI Interconnect: AXI interconnect from PULP platform is used to manage communication between the CPU, memory, and peripherals. It provides high-speed data transfer and ensures efficient interaction between system components.
c- APB Peripherals: Standard peripheral APB UART and APB SPI Master are also clone from pulp-platform for demonstrating the ability of SoC to interact with external components like serial terminal and SPI flash.
These components are typically implemented in hardware description language i.e., SystemVerilog due to its ability to interface easily with commercial tools. In this case, we aim to integrate these pre-existing SystemVerilog components into an Amaranth-driven SoC design.
Challenges of Integrating Amaranth with SystemVerilog
One of the key challenges when using Amaranth for integrating target SoC design shown in Figure-1, is integrating it with pre-existing SystemVerilog modules. While Amaranth provides an excellent Python interface for hardware design, it operates in a different paradigm than traditional Verilog/SystemVerilog. Some potential challenges are shown in table below:
Challenge | Description | Examples |
Lack of SystemVerilog Interface Support | Amaranth does not support SystemVerilog’s interface constructs, requiring developers to manually connect and manage each signal, increasing complexity in SoC design workflows. | Example: In the AXI interconnect, SystemVerilog interfaces bundle signals like data, address, and valid signals; in Amaranth, each must be managed separately. |
Missing Simulation Constructs | Amaranth lacks SystemVerilog’s built-in simulation constructs like $display, $monitor, and fork-join, reducing debugging capability and flexibility during testbench creation. | Example: Debugging AXI interfaces in SystemVerilog with $monitor is straightforward, but Amaranth relies on Python’s print() function, lacking seamless integration. |
Basic Built-in Simulator | Amaranth’s simulator is limited for large-scale SoC designs, lacking features like waveform viewing, code coverage, and multi-threaded simulation, affecting test efficiency. | Example: ModelSim provides real-time waveform viewing; Amaranth users must rely on external tools or manual value inspection to track signal states over time. |
Limited Commercial Simulator Integration | Amaranth primarily supports open-source simulators, limiting compatibility with advanced commercial simulators like ModelSim, Questa, and VCS for verification and UVM workflows. | Example: ModelSim’s UVM support simplifies complex SoC testbenches, while Amaranth requires workaround conversions to use such methodologies. |
Strategy to integrate SoC using Amaranth HDL
To address the challenges faced in using Amaranth HDL for SoC integration, especially when working with SystemVerilog interfaces and verification workflows, we can adopt a strategic approach that leverages the strengths of both Amaranth and traditional HDL tools. Here’s a high-level strategy for overcoming the major challenges:
a- Top-Level Generation in Amaranth HDL, SystemVerilog for Testbench
Purpose: Amaranth will be used solely for creating the top-level SoC integration and generating the Verilog file. The testbench will be written in SystemVerilog and simulated with Questasim.
Key Advantage: This approach bypasses Amaranth’s limitations with simulation and verification constructs, such as:
o $display for real-time debugging outputs.
o $readmem for reading memory files.
o $monitor and $time for time-based event tracking and monitoring.
Outcome: By keeping simulation tasks within SystemVerilog and using commercial simulation tools, we leverage Amaranth’s HDL generation strengths while avoiding its debugging limitations.
# Generate the combined Verilog code
if __name__ == "__main__":
top = TopLevel(addr_width=32, data_width=32, num_axi_port_in=2, num_axi_port_out=4)
# Flatten the ports to ensure they are correctly passed
verilog_code = verilog.convert(
top,
ports=[
top.pwrup_rst_n, top.cpu_rst_n, top.test_rst_n, top.rst, top.clk, top.rtc_clk, top.intr,
top.rx, top.tx, top.ev,
top.spi_clk, top.spi_csn, top.spi_sdi, top.spi_sdo, top.spi_mode
]
)
with open("SoC/top.sv", "w") as f:
f.write(verilog_code)
b- Wrappers for Interface-Based Modules
Challenge: Integrating modules with SystemVerilog interface ports in Amaranth can be cumbersome since interfaces are not natively supported.
Solution: Use Instance() in Amaranth HDL to integrate modules e.g. instance of APB UART shown below . For modules that rely on SystemVerilog interfaces, create separate SystemVerilog wrappers to “unbundle” these interfaces into individual signals. These wrappers can then be instantiated in Amaranth by directly connecting each unbundled signal individually.
# Instance of the APB UART
self.apb_uart = Instance(
"apb_uart_sv", # Name of the Verilog module
p_APB_ADDR_WIDTH=12,
i_CLK=self.clk,
i_RSTN=self.rst,
i_PADDR=self.apb_master[0].paddr[2:14],
i_PWDATA=self.apb_master[0].pwdata,
i_PWRITE=self.apb_master[0].pwrite,
i_PSEL=self.apb_master[0].psel,
i_PENABLE=self.apb_master[0].penable,
o_PRDATA=self.apb_master[0].prdata,
o_PREADY=self.apb_master[0].pready,
o_PSLVERR=self.apb_master[0].pslverr,
i_rx_i=self.rx,
o_tx_o=self.tx,
o_event_o=self.ev[0]
)
In this SoC, only the AXI crossbar (AXI XBAR) uses interface-based ports—specifically, slv_ports_req_i
, slv_ports_resp_o
, mst_ports_req_o
, and mst_ports_resp_i
. These ports package the I/O for the four AXI channels: AR, AW, W, and R. To integrate AXI XBAR into the SoC top level (in Amaranth HDL), a wrapper module is needed to unbundle these interfaces. Conveniently, the AXI repository includes a Python script, axi_intercon_gen.py
, which automatically generates this required wrapper, simplifying integration. The axi_intercon_gen.py
script requires a YAML configuration file that defines the number of AXI masters and slaves, as well as the address and data widths for the relevant signals. This YAML file specifies the parameters for the AXI crossbar (XBAR), enabling the script to generate a wrapper that unpacks the interface-based ports (slv_ports_req_i
, slv_ports_resp_o
, mst_ports_req_o
, and mst_ports_resp_i
). These ports encapsulate the four AXI channels (AR, AW, W, R), allowing for streamlined integration of the crossbar into the SoC’s top-level design in Amaranth HDL.
Outcome: This approach enables seamless integration of interface-based modules by breaking down complex connections into manageable signal-level connections.
soc_intercon:
generator: axi_intercon_gen
parameters:
masters:
dma:
id_width : 1
ibus:
id_width : 2
slaves:
ram:
offset : 0
size: 0x10000000
gpio:
offset: 0x91000000
size: 0x1000
rom:
offset : 0xffff0000
size : 32768
c- Python Wrappers for AXI and APB Interfaces for Simplified Integration
Purpose: To streamline and clarify the integration process for frequently-used interfaces like AXI and APB, Python wrappers will be created.
Key Advantage: These Python wrappers encapsulate bundled AXI/APB signals, making it easier to configure and connect all related signals without confusion
Outcome: Using these Python wrappers not only improves code readability but also reduces the potential for errors in signal mapping, making the top-level integration in Amaranth more manageable and clear.
Simulation and Testbench description
Currently, this project supports only Questasim, with work in progress to add support for Verilator. This SystemVerilog testbench (tb_top.sv) simulates a top-level SoC design (top module) with the following key components:
- Clock and Reset:
- Generates a 100 MHz clock (clk) and a slower RTC clock (rtc_i).
- Initializes a reset signal (rst_n) for SoC initialization.
- Interfaces:
- UART slave (rx/tx) for communication. It logs every incoming/outgoing byte from UART interface of target SoC into a file.
- SPI interface for connecting to a flash model (s25fl128s), using spi_sdo, spi_sdi, spi_csn, and spi_clk.
- Program Loading and Control:
- Loads a test program (main.hex) into the SoC’s memory.
- Uses $value$plusargs to get the TESTNAME for running different tests. Presently only 2 tests are developed to demonstrate the functional correctness of target SoC i.e., TestUART and TestSPIMaster. C codes for tests are present at this link.
- Module Instantiation:
- Instantiates the top SoC module and the flash model for SPI communication.
Summary
We used Amaranth for SoC integration and SystemVerilog for simulation, generating a Verilog top file and integrating the AXI XBAR using Python wrappers. While Amaranth offers flexibility with Python, its lack of advanced SystemVerilog and Verilog features (like simulation constructs) presents challenges. This can be addressed with Python scripts for wrapper generation.
Verdict: Amaranth can be a viable solution, especially with Python-based wrapper generation to bridge gaps in module integration. While it lacks some of the advanced features found in traditional SystemVerilog and Verilog, it is still a promising option for projects where flexibility and Python integration are prioritized. For projects with a heavy reliance on legacy SystemVerilog constructs, Amaranth’s limitations in simulation and feature support might be a constraint.