This guide details the process of integrating custom hardware designs created in the Programmable Logic (PL) with the Processing System (PS) of a System-on-Chip (SoC) using the AXI4 interface. The core principle involves creating a custom AXI4-compliant IP core from the PL design, which can then be accessed by the PS.
Creating a Custom AXI4 Peripheral IP To begin, initiate the creation of a new AXI4 peripheral IP. This involves launching the IP packaging interface and selecting the option to create a new AXI4 peripheral. Subsequent steps are generally straightforward, leading to an interface for editing the IP. If the edit interface isn't automatically opened, it can be accessed by right-clicking the custom IP in the IP catalog and selecting "Edit IP in Package".
Managing Verilog Files within the IP A critical aspect of IP creation is managing source Verilog files. If a new file is created directly within the IP editor without specifying a dedicated directory within the main project's source path, it may be lost upon packaging. Similarly, importing files from external directories can lead to them becoming unusable after the first packaging. To circumvent this, it's recommended to first create Verilog files within the main project's source directory. Then, add these existing files to the IP editor. This ensures the files are properly associated and can be reused. Files added to the IP from the main project should be temporarily removed from the main project's source list (without deleting them) to avoid interference with subsequent steps.
Here is an example Verilog module for controlling LEDs:
module stream_led (
input wire clk_user,
input wire rst_n_user,
input wire state_user,
output reg [2:0] led_user
);
reg [25:0] counter;
// Clock counter to control LED blinking frequency
always @(posedge clk_user or negedge rst_n_user) begin
if (!rst_n_user) begin
counter <= 1'b0;
end else if (counter == 26'd50_000_000 - 1'b1) begin
counter <= 1'b0;
end else if (state_user) begin // Control counting based on PS input
counter <= counter + 1'b1;
end
end
// State machine for LED patterns
always @(posedge clk_user or negedge rst_n_user) begin
if (!rst_n_user) begin
led_user <= 3'b0;
end else if (counter == 26'd50_000_000 - 1'b1) begin
case (led_user)
3'b000: led_user <= 3'b001;
3'b001: led_user <= 3'b010;
3'b010: led_user <= 3'b100;
3'b100: led_user <= 3'b001; // Cycle back
default: led_user <= 3'b000;
endcase
end
end
endmodule
Connecting the PL Module Standard Verilog practices apply for module instantiation. In this setup, slv_reg3 is utilized to receive input from the PS and drive the PL design. The clk_user and rst_n_user signals are directly mapped from the PL ports, and led_user is connected to the top-level output port.
// Instantiate the custom LED stream module
stream_led U1_user (
.clk_user(CLK_USER), // Connect to the PL clock
.rst_n_user(RST_N_USER), // Connect to the PL reset
.state_user(slv_reg3), // Input from PS via AXI slave register 3
.led_user(LED_USER) // Output to the LEDs
);
Packaging the IP Core The IP core is packaged using the "Create and Package IP" option within the IP editor. Select "Package your current project" and proceed through the wizard.
Revisiting IP Edits It's generally advisable to thoroughly simulate and verify PL code before packaging it into an IP. Issues often arise from incorrect port connections. If an IP requires modification after initial packaging, use the "Edit IP in Package" option found by right-clicking the custom IP in the IP catalog. This is also useful if the port list in the Block Design (BD) did not refresh correctly after the initial IP packaging.
Refreshing IP Cores in the Design After re-packaging an IP, it needs to be refreshed within the IP catalog. This is typically done by opening the "IP Status" window (which appears after packaging), selecting the IP with an update indicator (e.g., an exclamation mark), and clicking "Update". If the IP has already been instantiated in a BD and its top-level ports are in use, or if synthesis and implementation have been performed, the BD needs to regenerate its output products. This is achieved by right-clicking the BD and selecting "Generate Output Products". It is good practice to refresh the IP after instantiation in the BD to ensure consistency.
Failure to regenerate output products after IP updates can lead to issues such as the inability to export the XSA file, even if the bitstream generation succeeds. The error messages often indicate a discrepancy between the IP in the BD and the catalog.
ERROR: [Common 17-69] Command failed: The current design is not implemented.
ERROR: [Common 17-69] Command failed: ERROR: [Common 17-69] Command failed: The current design is not implemented.
Configuring Ports I/O port configurations for the PL are set in the "Elaborated Design" view, referencing the development board's schematic. It's important to note that configuring PS ports here will result in errors, as these are managed by the ARM core's registers.
Exporting the XSA File Once the bitstream is generated, the hardware design can be exported as an XSA file, which is then used in the Vitis development environment. This export option is typically found under "File" -> "Export" -> "Export Hardware".
Software Testing in Vitis Import the generated hardware design into the Vitis platform. The provided C code demonstrates how the PS interacts with the custom AXI4 peripheral.
#include "xil_io.h" // For hardware I/O operations
#include "sleep.h" // For delay functions
#include "stdio.h" // For standard input/output
#include "xparameters.h" // For system parameters (base addresses)
// Define the base address of the custom IP
#define IP_BASE XPAR_MYIP_V1_0_0_BASEADDR
// Define offsets for the AXI slave registers
#define REG0_OFFSET 0 // For outputting data to PS (e.g., LED status)
#define REG1_OFFSET 4 // For reading button input from PS
#define REG2_OFFSET 8 // For configuring the IP from PS
#define REG3_OFFSET 12 // For enabling/disabling PL functionality
int main() {
int button_state;
// Initialize the custom IP, potentially configuring behavior
// Writing 0xffff to REG2 might set initial parameters or enable modes
Xil_Out32(IP_BASE + REG2_OFFSET, 0xffff);
while (1) {
// Read the state of buttons connected to REG1
button_state = Xil_In32(IP_BASE + REG1_OFFSET);
// Write the button state to REG0, possibly to control LEDs based on buttons
Xil_Out32(IP_BASE + REG0_OFFSET, button_state);
// Enable the PL functionality (e.g., start the LED blinker) by writing 1 to REG3
// This line drives the PL logic, enabling the expected behavior.
Xil_Out32(IP_BASE + REG3_OFFSET, 1);
}
return 0; // Should not be reached in this infinite loop
}
The statement Xil_Out32(IP_BASE + REG3_OFFSET, 1); is crucial for enabling the PL functionality, such as activating the LED blinker, from the PS. The preceding code snippet is adapted from previous learning examples.
Experimental Results and Observations On-board validation confirmed the expected behavior. An interesting observation during debugging is that while the PS code execution can be halted and resumed by the debugger, the PL logic continues to operate independently. This highlights that debugging actions primarily affect the PS. Debugging the PL requires dedicated tools like the Integrated Logic Analyzer (ILA) and Virtual I/O (VIO) cores.