# Integrate a Peripheral IP This guide walks through the end-to-end flow to add a new memory-mapped peripheral to X-HEEP. We use the existing [`dlc`](https://github.com/x-heep/x-heep/tree/main/hw/ip_examples/dlc) IP as the blueprint because it exercises the full hardware, software, and tooling stack. Set up the X-HEEP environment by following the [Getting Started guide](../GettingStarted/Setup.md). ## 1. Create the IP Structure This is how the folder should look like: ``` └── hw/ip/ ├── data/ │ └──.hjson # register description (for regtool generation) ├── rtl/ │ ├──.sv # RTL of the peripheral │ ├── _reg_top.sv # generated by regtool │ └── _reg_pkg.sv # generated by regtool ├── .core # FuseSoC description ├── .vlt # Verilator waiver └── _gen.sh # helper script running regtool ``` ### X-HEEP-compatible module ports All X-HEEP peripheral modules require specific ports that allows it to communicate with the rest of the system. The following is the absolute minimum starting point to develop your peripheral module ports: ```systemverilog module peripheral ( input logic clk_i, input logic rst_ni, // Register interface input reg_pkg::reg_req_t reg_req_i, output reg_pkg::reg_rsp_t reg_rsp_o ); ``` The CPU can access the configuration registers of the peripheral via the register interface: - `reg_req_i` is the input request, which can either request to write some value into a register or read values from a register - `reg_rsp_o` is the output response, which may contains register data if the request was a read operation In any case, the implementation of this protocol is transparent to the RTL designer, as its included in the **top register module** generated via regtool depending on the `data/.hjson` information. Here is an extended example of a module port structure which includes additional features: ```systemverilog module peripheral ( input logic clk_i, input logic rst_ni, // Register interface input reg_pkg::reg_req_t reg_req_i, output reg_pkg::reg_rsp_t reg_rsp_o, // Done signal output logic peripheral_done_o, // Interrupt signal output logic peripheral_interrupt_o, // Master ports on the system bus output obi_req_t peripheral_master_bus_req_o, input obi_resp_t peripheral_master_bus_resp_i ); ``` About the additional features: - `peripheral_done_o`: this done signal could be useful if you want X-HEEP to expose it, so that external units can be aware of it. For example, each X-HEEP's DMA channel exposes a `dma_done_o` signal. This could be used for example by accelerators that exploit the DMA functionalities to move data in and out of their local memory - `peripheral_interrupt_o`: this signal can be used to set up a dedicated interrupt line reserved for your peripheral. This would allow the execution of custom interrupt handlers in SW, a common and useful feature. - `peripheral_master_bus_req_o / peripheral_master_bus_resp_i`: these are OBI request and response signals, necessary if you want your peripheral to be a master on the system bus. In other words, this would allow your peripheral to read and write data in every memory mapped space of X-HEEP. For example, this could allow your peripheral to read from RAM, or it could allow it to configure other peripherals. ## 2. Describe and Generate Registers In X-HEEP, registers are automatically generated using RegTool by OpenTitan. Please refer to the official documentation for additional information: . In anycase, this tool will generate both RTL and SW components starting from an HJSON description. These are the steps needed to set everything up: 1. Populate `data/.hjson` with registers, fields, reset values, and optional interrupts. DLC’s [`dlc.hjson`](../../../hw/ip_examples/dlc/data/dlc.hjson) is a good example, but you could take inspiration from any X-HEEP `.hjson`. 2. Generate RTL wrappers and the C header files by running the `_gen.sh` script. ## 3. Expose the IP to FuseSoC This is a crucial step in integrating the new peripheral in X-HEEP's flow: 1. Adapt `hw/ip//.core` from [`dlc.core`](../../../hw/ip_examples/dlc/dlc.core). List all RTL files under `files:` and declare dependencies (for example `pulp-platform.org::register_interface`). 2. Add the peripheral as a dependency of the SoC in `core-v-mini-mcu.core`: ```yaml filesets: files_rtl_generic: depend: - example:ip: # use the namespace chosen in your .core file ``` 3. If the IP has lint waivers for Verilator, reference the waiver in the `.core` file: ```yaml filesets: depend: - openhwgroup.org:ip:verilator_waiver files: - hw/ip//.vlt # use the namespace chosen in your .core file ``` ## 4. Reserve Address Space in the Configuration This is another crucial step in the integration of the peripheral. Please be aware that the address map of X-HEEP might change in the future, so consider this for the addresses of the following examples. As said at the beginning of this guide, these steps are the same for any domain you want to integrate the peripheral in, both `user peripherals` and `base peripherals`. 1. Extend the configuration you built (HJSON or Python) with a new entry in the preferred domain. Example (`configs/general.hjson`): ```js peripherals: { address: 0x30000000 length: 0x00100000 // existing peripherals… : { offset: 0x00080000 length: 0x00010000 is_included: "yes" } // existing peripherals… } ``` 2. For Python configs, import a new class in `util/xheep_gen/peripherals/user_peripherals.py` (or `base_peripherals.py` if mandatory): ```python class MyPeripheral(UserPeripheral): _name = "my_peripheral" ``` Instantiate the class in your config script and add it to the user domain before calling `build()`. 3. If your IP generates an interrupt signal, extend the _interrupt list_ with it. ```{warning} This step is necessary only for `peripheral` domain IPs. ``` ```js interrupts: { number: 64 // Do not change this number! list: { // First one is always zero null_intr: 0 uart_intr_tx_watermark: 1 // Other interrupt signals... i2s_intr_event: 50 // Your own peripheral: _intr_event: 51 } } ``` ## 5. Integrate the IP in the Peripheral domain Now, let's instantiate the peripheral in the `peripheral_subsystem.sv.tpl`, as the `.sv` will be regenerated every `make mcu-gen` ran. As a good reference, please look at the `i2s` peripheral. There are some important considerations to make, so this is an example of a peripheral instantiation that mirrors the previous examples: ```systemverilog peripheral_ip #( .reg_req_t(reg_pkg::reg_req_t), .reg_rsp_t(reg_pkg::reg_rsp_t) ) peripheral_ip_i ( .clk_i(clk_cg), .rst_ni, // Register interface .reg_req_i(peripheral_slv_req[core_v_mini_mcu_pkg::PERIPHERL_IDX]), .reg_rsp_o(peripheral_slv_rsp[core_v_mini_mcu_pkg::PERIPHERL_IDX]), // Done signal .peripheral_done_o, // Interrupt signal peripheral_interrupt_o(peripheral_intr_event), // Master ports on the system bus .peripheral_master_bus_req_o, .peripheral_master_bus_resp_i ); ``` The ports of the peripheral subsystem itself have to be changed too if you want to add a master port on the bus or if you want to expose a signal: ```systemverilog module peripheral_subsystem import obi_pkg::*; import reg_pkg::*; #( //do not touch these parameters parameter NEXT_INT_RND = core_v_mini_mcu_pkg::NEXT_INT == 0 ? 1 : core_v_mini_mcu_pkg::NEXT_INT ) ( input logic clk_i, input logic rst_ni, // Other signals... // PDM2PCM Interface output logic pdm2pcm_clk_o, output logic pdm2pcm_clk_en_o, input logic pdm2pcm_pdm_i, // Your peripheral signals output logic peripheral_done_o, output obi_req_t peripheral_master_bus_req_o, input obi_rsp_t peripheral_master_bus_rsp_i ); ``` Some notes: - `clk_i` is connected to `clk_cg`, as the peripheral subsystem can be clock gated. If you want to clock gate _individually_ your peripheral, implement that feature inside your IP. - `reg_req_i / reg_rsp_o` are connected to a register-interface demultiplexer which routes all requests from the bus to the correct peripheral based on their address map. That's why it's __very important__ that you select the correct `peripheral_slv_req` and `peripheral_slv_rsp` signal with the ID associated with your peripheral. - `peripheral_done_o`, `peripheral_master_bus_req_o` and `peripheral_master_bus_rsp_i` connects the module's ports to the peripheral's ports. If your peripheral need a new __system bus master port__, like in our example, you need to modify other files too. In order: - `core_v_mini_pkg.sv.tpl`: this file generates parameter for many X-HEEP modules, included the system bus. In particular, we are interested in increasing the `SYSTEM_XBAR_NMASTER` parameter by the number of master ports we need. In our example, one is sufficient. - `system_bus.sv.tpl`: this is the template for the system bus. Here we need to add a new port of the module and connect `int_master_req` and `int_master_rsp` signals to the new port: ```systemverilog assign int_master_req[9] = peripheral_master_bus_req_i; // Other code... assign peripheral_master_bus_req_o = int_master_resp[9]; ``` - `core_v_mini_mcu.sv.tpl`: finally, we need to update the `system_bus` instantiation, and connect the peripheral master bus signals to the system bus ones. As a good reference for this entire process, consider once again the DMA subsystem. Regardless of system bus master ports, we need to update the `peripheral_susystem` or `ao_peripheral_subsystem` instantiation in `core_v_mini_mcu.sv.tpl` to match out modifications. Furthermore, if your peripheral needs to __expose a signal__, like `peripheral_done_o` in our case, we need to perform some additional modifications. In order: - Add to the ports of the `core_v_mini_mcu` module the signal itself - Update `x_heep_system.sv.tpl` to match the new `core_v_mini_mcu` ports - Update `testharness.sv` to match the new `x_heep_system` ports Finally, if you want, in `testharness.sv` you can instantiate an accelerator that can exploit the `peripheral_done_o` signal. ```{warning} After updating all these templates, run `make mcu-gen` to regenerate them! ``` ## 6. Hook Up FPGA Wrappers 1. Edit the board wrapper templates (e.g. `hw/fpga/xilinx_core_v_mini_mcu_wrapper.sv.tpl`) to expose the new signals and regenerate the concrete `.sv` file. 2. Update board constraint files or scripts (such as `hw/fpga/scripts/generate_sram.tcl.tpl`) when the IP drives physical I/O. 3. Re-run `make mcu-gen` to propagate template changes before rebuilding bitstreams. ## 7. Provide Software Accessors In order to simplify the user experience, create a driver in `sw/device/lib/drivers//`. We suggest taking as a reference the structure of `sw/device/lib/drivers/dlc`, but feel free to be inspired by any X-HEEP driver module. Typical contents: - `.c` / `.h` – MMIO helper functions. - `.h` (generated earlier via `regtool`). - `_structs.h` – emitted by `util/structs_periph_gen.py` if needed. ## 8. Add Firmware Examples and Tests This is strongly suggested, as it can serve both as a guide for future developers and a debugging tool. To do so, write an example application under `sw/applications/example_/` that exercises the new driver. ## 9. (For pull requests) Update Documentation This is __mandatory__ for any new IP that you would like to propose to be included in the official X-HEEP repository. Students, companies and passionate individuals can all contribute to X-HEEP, and we encourage you to do so. However, we need to keep this project maintainable. For this reason, please add a dedicated page in the `Peripherals` folder of the documentation. For reference, please check out our guide on how to update the documentation [here](UpdateDocumentation.md). ## Checklist - [ ] IP directory added under `hw/ip/` (or `hw/ip_examples/`) with `.core`, RTL, and HJSON files. - [ ] `regtool` executed; generated RTL and headers checked in. - [ ] Configuration (HJSON or Python) updated with offset, length, and optional interrupt entries. - [ ] Templates updated and `make mcu-gen` run; regenerated outputs reviewed. - [ ] `peripheral_subsystem` (and other templates) instantiate and connect the IP. - [ ] Testbench and FPGA wrappers handle the new signals. - [ ] Driver (`sw/device/lib/drivers/`) implemented; structs regenerated if needed. - [ ] Example firmware compiled and simulations pass. With these steps complete, your peripheral is fully integrated into the X-HEEP hardware, verification, and software flow.