Zynq MPSOC is an heterogeneous device with 3 kind of devices inside. On this project we will design a signal generator, but the goal of this project is not the signal generator itself, but the way of how to run Petalinux on the Application Processing Unit (APU), a C application on Real time Processing Unit (RPU), and a design on the Programmable Logic (PL).
The way I've use does not use any AMP library like OpenAMP, only Vitis and the partitions of the BOOT image.
The board I will use for this project is Genesys ZU board from Digilent.
Downloading board files.First we need to download the board file from Digilent’s Github.
Next we have to copy the board file folder into
<vivado installation dir>/data/boards/board_files
Then, Genesys ZU board can be selected from project creation menu.
Creating the project.Open Vivado and create a new project.
And select Genesys ZU board.
Once we have the new project created, we will create a new block design where we will add all necessary components.
Now, add Zynq subsystem to the block design. For do that, we have to press over + sign, and write on search box Zynq Ultrascale+ MPSoC.
Once the Zynq will be added to our block design, the IP must be configured according Genesys ZU hardware. For do that, we have to click on Run Block Automation, Apply board preset, and the IP will be configured.
First we will add the AXI controller for the 4 leds on the board. For this, we have open the tab Board, and the double click over 4 LEDs.
Then vivado will ask where we want to use for connect the LEDs output. In this case, we will create an AXI GPIO.
Finally, an AXI GPIO will be added.
Now we have to connect this new IP to the Zynq block, and this can be do automatically clicking over Run connection Automation.
Genesys ZU board, by default, do not start the supply for FMC and SYZYGY, for do that, according to the reference manual we have to manage 2 pins of the Platform management MCU.
The code I’ve used manage this pins, and generate a delay for release a secondary reset. This reset allow the board run the SYZYGY and FMC power supplies before the driver start the configuration.
/**
Module name: vadj_set_genesys_v1_0
Author: P Trujillo (pablo@controlpaths.com)
Date: Jun 2020
Description: V adjust management.
Revision: 1.0 Module created.
**/
module vadj_set_genesys_v1_0 (
input clk, /* Input clock */
input rstn, /* Reset input */
output o_lvl_adj0, /* VADJ_LEVEL0 pin */
output o_lvl_adj1, /* VADJ_LEVEL1 pin */
output o_auto_vadj, /* VADJ_AUTO pin */
output reg or_rstn /* Reset output for ADC driver */
);
assign o_lvl_adj0 = 1'b1;
assign o_lvl_adj1 = 1'b1;
reg [17:0] r18_delay_counter; /* Register for delay */
always @(posedge clk)
if (!rstn)
r18_delay_counter <= 18'd0;
else
r18_delay_counter <= (!(&r18_delay_counter))? r18_delay_counter+18'd1:r18_delay_counter;
assign o_auto_vadj = &r18_delay_counter[16:0];
always @(posedge clk)
or_rstn <= &r18_delay_counter? 1'b1:1'b0;
endmodule
This module must be added to the design through Add sources link.
When the module is on the project, it will be added to the block design by right click and selecting Add module option.
Then select the new module created.
And the module will be added to the block design.
Next, create the ports of the module by clicking Ctrl+K on the ports of the module.
Design we are going to create will store up to 2 different signals. These signals will be generated by the RPU, and then read by the DAC driver. For store those 2 signals, we will use 2 different BRAM modules.
For add the first BRAM module, we have to click on + symbol, and the type BRAM, and select AXI BRAM Controller.
When AXI BRAM controller is added we have to configure it by setting the number of BRAM interfaces in 1. This configuration will release the second port of the BRAM module for be used by the DAC driver.
Once configured, we have to click on Run connection automation, and a BRAM module will be added and connected.
As the configuration of the AXI BRAM Controller only use one port of BRAM, the BRAM module added only has one port, but we will need both, so we have to select True Dual Port RAM on Memory type.
Then we will have one port connected to the AXI BRAM Controller, and the other one open.
Repeat the process for add a second BRAM with same configuration. Ensure that BRAM connection is made to other BRAM generator.
Now we have to check the addresses that Vivado assign to the BRAM, and in the case that both BRAM modules have the same address, we have to change one of them.
Next we have to create the DAC driver. Add a new design source
ZMOD DAC driver will set the configuration and manage the data to the DAC. Configuration of the DAC, can be operated through a SPI communication, or by setting the pins. Driver I’ve used uses the state of 4 pins to configure DAC.
14 bit data will be sent to the DAC through a DDR interface. For generate a DDR interface on Zynq MPSOC, a ODDRE1 primitive has to be used. This primitive has 2 data inputs, 1 clock input, and 1 output data. Data on 2 inputs is send by the output data on each clock edge.
As I said, data will be stored in 2 BRAM modules. Data to the DAC has a width of 14 bits, and data on BRAM has a width of 32 bits, so we can use the high side of the BRAM data to store configuration.
Module also manage 2 clock output for the DAC. This clocks have to use a Clock Forwarding technique with ODDRE1 modules for ensure that clock uses clock paths to the output pin.
/**
Module name: zmod_dac_driver
Author: P Trujillo (pablo@controlpaths.com)
Date: Aug 2020
Description: Driver for ad9717. Zynq MPSOC. ZMOD DAC from Digilent
Revision: 1.0 All in one driver. Include Relay and gpio configuration and BRAM management
**/
module zmod_dac_driver_bram_v1_0 (
input clk, /* Clock input */
input clk_dac,
input rstn, /* Reset input */
output o_clkdac,
/* bram i interface */
output reg [31:0] or32_bram_i_address,
output [3:0] o4_bram_i_we,
input [31:0] i32_bram_i_data,
/* bram q interface */
output reg [31:0] or32_bram_q_address,
output [3:0] o4_bram_q_we,
input [31:0] i32_bram_q_data,
/* ddr signal */
output o_clkio,
output signed [13:0] os14_data, /* Parallel DDR data for ADC*/
output rst_spi, /* DAC reset out*/
output or_sck, /* DAC SPI clk out*/
output or_cs, /* DAC SPI cs out*/
output o_sdo, /* DAC SPI data IO out*/
output o_relay, /* Output relay */
output o_dac_fsadji, /* Full scale selection */
output o_dac_fsadjq /* Full scale selection */
);
localparam lp32_ctrl_reg_address = 32'h001000; /* Control register address address*/
reg signed [13:0] rs14_data_i; /* Data for vector i*/
reg signed [13:0] rs14_data_q; /* Data for vector q*/
reg [15:0] r16_ctrl_reg_i; /* Control register for data i*/
reg [15:0] r16_ctrl_reg_q; /* Control register for data q*/
wire [11:0] w12_length_data_i; /* Configured length data for vector i*/
wire [13:0] w14_length_data_i; /* Configured length data for vector i x4*/
wire [11:0] w12_length_data_q; /* Configured length data for vector q*/
wire [13:0] w14_length_data_q; /* Configured length data for vector q x4*/
reg [13:0] r14_index_data_i; /* Reading index for vector i*/
reg [13:0] r14_index_data_q; /* Reading index for vector i*/
wire w_oenable_i; /* output enable signal for vector i*/
wire w_oenable_q; /* output enable signal for vector q*/
wire [3:0] w4_prescaler_data_i; /* Prescaler for data vector i*/
wire [3:0] w4_prescaler_data_q; /* Prescaler for data vector q*/
reg [3:0] r4_prescaler_counter_data_i; /* Prescaler counter for vector i*/
reg [3:0] r4_prescaler_counter_data_q; /* Prescaler counter for vector q*/
/* Disable write enable for this port */
assign o4_bram_q_we = 4'd0;
assign o4_bram_i_we = 4'd0;
/* i data management */
assign w12_length_data_i = r16_ctrl_reg_i[9:0];
assign w14_length_data_i = {w12_length_data_i, 2'b00};
assign w_oenable_i = r16_ctrl_reg_i[10];
assign w4_prescaler_data_i = r16_ctrl_reg_i[15-:4];
assign o_dac_fsadji = r16_ctrl_reg_i[11];
always @(posedge clk)
if (!rstn) begin
rs14_data_i <= 14'd0;
r14_index_data_i <= 12'd0;
r4_prescaler_counter_data_i <= 10'd0;
r16_ctrl_reg_i <= 16'd0;
or32_bram_i_address <= 32'd0;
end
else
if (r4_prescaler_counter_data_i == w4_prescaler_data_i) begin
r4_prescaler_counter_data_i <= 10'd0;
r14_index_data_i <= (r14_index_data_i < w14_length_data_i)? r14_index_data_i+14'd4:14'd0;
r16_ctrl_reg_i <= i32_bram_i_data[31-:16];
or32_bram_i_address <= {20'd0,r14_index_data_i};
rs14_data_i <= w_oenable_i? i32_bram_i_data[13:0]:14'd0;
end
else
r4_prescaler_counter_data_i <= r4_prescaler_counter_data_i+10'd1;
/* q data management */
assign w12_length_data_q = r16_ctrl_reg_q[9:0];
assign w14_length_data_q = {w12_length_data_q, 2'b00};
assign w_oenable_q = r16_ctrl_reg_q[10];
assign w4_prescaler_data_q = r16_ctrl_reg_q[15-:4];
assign o_dac_fsadjq = r16_ctrl_reg_q[11];
always @(posedge clk)
if (!rstn) begin
rs14_data_q <= 14'd0;
r14_index_data_q <= 12'd0;
r4_prescaler_counter_data_q <= 10'd0;
r16_ctrl_reg_q <= 16'd0;
or32_bram_q_address <= 32'd0;
end
else
if (r4_prescaler_counter_data_q == w4_prescaler_data_q) begin
r4_prescaler_counter_data_q <= 10'd0;
r14_index_data_q <= (r14_index_data_q < w14_length_data_q)? r14_index_data_q+14'd4:14'd0;
r16_ctrl_reg_q <= i32_bram_q_data[31-:16];
or32_bram_q_address <= {20'd0,r14_index_data_q};
rs14_data_q <= w_oenable_q? i32_bram_q_data[13:0]:14'd0;
end
else
r4_prescaler_counter_data_q <= r4_prescaler_counter_data_q+10'd1;
/* Output data management */
generate for(genvar i=0; i<=13; i=i+1) begin
ODDRE1 #(
.IS_C_INVERTED(1'b0),
.IS_D1_INVERTED(1'b0),
.IS_D2_INVERTED(1'b0),
.SIM_DEVICE("ULTRASCALE"),
.SRVAL(1'b0)
)
ODDRE1_inst0 (
.Q(os14_data[i]),
.C(clk_dac),
.D1(rs14_data_i[i]),
.D2(rs14_data_q[i]),
.SR(!rstn)
);
end
endgenerate
/* Clock forwarding */
/* dclkio */
ODDRE1 #(
.IS_C_INVERTED(1'b0),
.IS_D1_INVERTED(1'b0),
.IS_D2_INVERTED(1'b0),
.SIM_DEVICE("ULTRASCALE"),
.SRVAL(1'b0)
)
ODDRE1_inst1 (
.Q(o_clkio),
.C(clk_dac),
.D1(1'b1),
.D2(1'b0),
.SR(!rstn)
);
/* clk dac */
ODDRE1 #(
.IS_C_INVERTED(1'b0),
.IS_D1_INVERTED(1'b0),
.IS_D2_INVERTED(1'b0),
.SIM_DEVICE("ULTRASCALE"),
.SRVAL(1'b0)
)
ODDRE1_inst2 (
.Q(o_clkdac),
.C(clk_dac),
.D1(1'b1),
.D2(1'b0),
.SR(!rstn)
);
/* Output relay management */
assign o_relay = w_oenable_i | w_oenable_q;
/* Configure dac by gpio */
assign rst_spi = 1'b1; /* spi_mode = off*/
assign or_sck = 1'b0; /* clkin = dclkio*/
assign or_cs = 1'b0; /* pwrdn = 0 */
assign o_sdo = 1'b1; /* input format = 2's complement */
endmodule
When module is complete, we have to add it to the Block design, by right click on the diagram and selecting Add module.
And select new module created.
When module is added, we can connect the clock signals to the 100MHz clock from the Zynq, and reset to the global reset.
Also we can connect the BRAM signals to the DAC driver. Then, as the BRAM reset is not negated, we have to add a utility vector logic block.
Then configure this block as NOT gate
And connect the input to the negate reset of the system, and the output to the reset of the BRAM blocks.
Finally create the ports for the DAC
When we validate the design, Vivado returns a Critical warning. This critical warnings indicates that modules that use as reset the signal generated by vadj_set_genesys, have an asynchronous reset, but this is not true, because this signal is generates synchronously with the clock.
So, we can create the HDL wrapper.
Select option Let Vivado manage wrapper and auto-update.
Once wrapper is generated, we can generate the bitstream
Once the bitstream is generated, we have to export the hardware to use it on Vitis and Petalinux. File > Export > Export hardware.
Select a fixed platform
And include the bitstream on hardware exported.
Next we have to create the application for the RPU on Vitis. To open Vitis, click on Tools > Launch Vitis IDE.
On Vitis, first we have to create a new platform
On the next window, select a name for the platform.
And finally, select the XSA file on Vivado project folder to create the platform. Also we have to select the R5 0 as the processor for the platform.
Once we have a platform to work, we have to create a project for this platform.
Select a name for the project, and select the R5 0 processor to RUN the project.
As all the code will be created by us, we can select the Empty application template.
Next we have to add a new file to the project with the name main.c, and we can start typing the code.
Since our signal generator has the the option to generate a sinusoidal signal, design has to use the math library. In C code, we have to include math library
#include “math.h”
But this is not enough to use math functions. In addition to this include, we have to include the library m.
Open properties window of the project.
Then we have to navigate to C/C++ Build > Settings > Libraries, and add the library m.
Once added, Apply and Close.
Then we can to write the code.
First, we have to declare a pointers to the addresses shared with APU. in this case we only shared 2 variables of 32 bits, so the addresses will be 0x00000000 and 0x00000004.
/* Pointers declaration to ATCM memory */
long *shared_configuration_amplitude = (long*)0x00000000;
long *shared_configuration_ctrl = (long*)0x00000004;
Once variables are declared, we have to extract the information of that variables. Information what we need will be amplitude of the signal, kind of signal, prescaler of the signal and its length.
/* Pointers parser*/
amplitude1 = *shared_configuration_amplitude & 0x3FFF;
amplitude2 = (*shared_configuration_amplitude >> 16) & 0x3FFF;
signal1 = *shared_configuration_ctrl & 0x3;
signal2 = (*shared_configuration_ctrl >> 2) & 0x3;
prescaler1 = (*shared_configuration_ctrl >> 4) & 0xf;
prescaler2 = (*shared_configuration_ctrl >> 8) & 0xf;
length1 = (*shared_configuration_ctrl >> 12) & 0x3ff;
length2 = (*shared_configuration_ctrl >> 22) & 0x3ff;
Now, according the register definition to share with the PL, we have to create different functions to write on this BRAM addresses. The operation on all functions are similar, first we read data, and then overwrite only the corresponding bits. Data will be write on all addresses of the BRAM. That allow us to change the signal information at any time. For write on the BRAM addresses I used the xil_io functions.
/* Function for write the length of the signal
Arguments:
- channel: Channel to write length [0, 1]
- length: Length of the signal [0,1023]
*/
void write_length (channel, length) {
long data;
long add;
int i;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
/* Before write, the value of the complete register is read, and than only the corresponding register is wrote*/
for(i=0;i<MAX_LENGTH;i++) {
data = Xil_In32(add);
data &= 0xffd0ffff;
data |= (length&0x3FF) << 16;
Xil_Out32(add, data);
add += 4;
}
}
/* Function for write the scale of the output.
Arguments:
- channel: Channel to write length [0, 1]
- fs: Scale of the signal [0,1]
*/
void write_fs (channel, fs) {
long data;
long add;
int i;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
/* Before write, the value of the complete register is read, and than only the corresponding register is wrote*/
for(i=0;i<MAX_LENGTH;i++) {
data = Xil_In32(add);
data &= 0xf7ffffff;
data |= (fs&0x1) << 27;
Xil_Out32(add, data);
add += 4;
}
}
/* Function for write the scale of the output.
Arguments:
- channel: Channel to write length [0, 1]
- oena: Output relay status [0,1]
*/
void write_oena (channel, oena) {
long data;
long add;
int i;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
/* Before write, the value of the complete register is read, and than only the corresponding register is wrote*/
for(i=0;i<MAX_LENGTH;i++) {
data = Xil_In32(add);
data &= 0xfbffffff;
data |= (oena&0x1) << 26;
Xil_Out32(add, data);
add += 4;
}
}
/* Function for write the timer prescaler.
Arguments:
- channel: Channel to write length [0, 1]
- prescaler: Count for increase one adress of the memory [0,15]
*/
void write_prescaler (channel, prescaler) {
long data;
long add;
int i;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
/* Before write, the value of the complete register is read, and than only the corresponding register is wrote*/
for(i=0;i<MAX_LENGTH;i++) {
data = Xil_In32(add);
data &= 0x0fffffff;
data |= (prescaler&0xf) << 28;
Xil_Out32(add, data);
add += 4;
}
}
/* Function for generate a signal.
Arguments:
- channel: Channel to write length [0, 1]
- signal: Type of signal
- amplitude: Amplitude of the signal [-8192:8191]
- offset: DC component of the signal [-8192:8191]
- phase: delay of the signal [0:1024]
*/
void function_gen (channel, signal, amplitude, offset, phase) {
if (signal == SIGNAL_DC)
function_dc(channel, amplitude);
else if (signal == SIGNAL_SIN)
function_sin(channel, amplitude, offset, phase);
}
/* Function for generate a dc signal.
Arguments:
- channel: Channel to write length [0, 1]
- amplitude: Amplitude of the signal [-8192:8191]
*/
void function_dc (channel, amplitude) {
long add;
long data;
int signal;
int i;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
signal = amplitude;
write_length(channel, 0);
data = Xil_In32(add);
data &= 0xffff0000;
data |= signal & 0xffff;
Xil_Out32(add, signal);
}
/* Function for generate a signal.
Arguments:
- channel: Channel to write length [0, 1]
- amplitude: Amplitude of the signal [-8192:8191]
- offset: DC component of the signal [-8192:8191]
- phase: delay of the signal [0:1024]
*/
void function_sin (channel, amplitude, offset, phase) {
long add;
int length = 128;
float angle=0;
float angle_step;
long index_add;
int i;
float signal;
int signal_q;
long data;
if (channel == 0)
add = ADD_DATA_I;
else
add = ADD_DATA_Q;
angle_step = 2*M_PI/length;
index_add = 0;
/* Loop to generate the signal */
for(i=0;i<length;i++) {
signal = sin(angle);
angle += angle_step;
signal_q = (int)(signal*amplitude)+offset;
data = Xil_In32(add);
data &= 0xffff0000;
data |= (signal_q & 0xffff);
Xil_Out32(add, data);
add += 4;
}
write_length(channel, length);
}
Modifying the linker script.Now, we have modify the linker script. On this design, 2 processor will be run, the APU, and the RTU. By default, when we create an application on Vitis, this application is configured to run on main memory, in this case, the DDR4 module, but this is a problem, because the APU application, Petalinux, will be executed in the same memory, so this will cause errors. For avoid that, we have to configure RPU application to be in TCM instead of DDR4 module. TCM are 2 memories (TCMa and TCMb), exclusively for RPU, and RPU has a non-latency access to this memory, among other advantages. More information about TCM can be found on technical Reference Manual (UG1084, Ch4).
As I said, each core of R5 RPU has its own TCM memories, and for each core, memories have the same addresses, ATCM can be found at 0x00000000 and BTCM can be found at 0x00020000, but remember, this addresses point to different memories.
Also, TCM has a secondary access through the Global address view. This port made possible that TCM can be accessed from APU or the other RPU core. This secondary port, has no the benefits that the main port, so the access from this port has a higher latency.
Well, to change the memory from our application will be executed, we have to open the linker script file, lscript.ld, and modify the destination of all regions to psu_r5_0_atcm_MEM_0
Since the memory we will use is located at 0x00000000, the shared variable between APU and RPU can be located at the same memory, that is the reason of the pointer to shared_configuration variable.
long*shared_configuration = (long*)0x00000000;
If we take a look to dissasembly window on Vitis, we can see as all the code is stored in the range 0x00000000 to 0x0001000.
Once we have our code compiled, we have a.elf file witch we will use to generate the boot image.
Next, build Petalinux.
Building Petalinux.First we will add the environment variables to out PATH.
Then, we can create a new Petalinux project
petalinux-create --type project --template zynqMP --name apu_rpu_signalgen
Then we have to connect our Petalinux distribution to the exported hardware generated by Vivado
petalinux-config --get-hw-description <path to .xsa folder>
Navigate to Ethernet settings to change the IP configuration.
Subsystem AUTO Hardware Settings -->
Ethernet Settings -->
[ ] Obtain IP address automatically = N
(192.168.1.10) Static IP address
(255.255.255.0) Static IP netmask
(192.168.0.1) Static IP gateway
And exit.
Then we have to add some packages to petalinux, but instead to add them through the command
petalinux-config -c rootfs
we will add them modifying the file petalinuxbsp.conf
./apu_rpu_signalgen/project-spec/meta-user/conf
the resulting file is as shown
#User Configuration
IMAGE_INSTALL_append = " \
python \
python-pip \
python3 \
python3-pip \
"
#OE_TERMINAL = "tmux"
Now, we have to modify the device tree file to configure the peripherals on Genesys. Luckily, Digilent give us this file, so the only we have to do is copy from Digilent’s file, the configuration corresponding to the gigabit ethernet port on../apu_rpu_signalgen/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/media/xilinx-vip.h>
#include <dt-bindings/net/ti-dp83867.h>
#include <dt-bindings/phy/phy.h>
/ {
};
/*
devicetree/bindings/net/macb.txt
devicetree/bindings/net/ti,dp83867.txt
devicetree/bindings/net/phy.txt
*/
&gem0 {
phy-handle = <&phy0>;
phy-mode = "rgmii-id";
phy0: phy@15 {
reg = <0x0F>;
reset-gpios = <&gpio 44 GPIO_ACTIVE_LOW>;
reset-assert-us = <1>;
reset_deassert-us = <200>;
interrupt-parent = <&gpio>;
interrupts = <38 IRQ_TYPE_LEVEL_LOW>;
ti,rx-internal-delay = <DP83867_RGMIIDCTL_2_00_NS>; //overwrites strap config
ti,tx-internal-delay = <DP83867_RGMIIDCTL_1_50_NS>; //overwrites strap config
ti,fifo-depth = <DP83867_PHYCR_FIFO_DEPTH_4_B_NIB>;
ti,clk-output-sel = <DP83867_CLK_O_SEL_REF_CLK>;
};
};
Finally, we can build our petalinux distribution
petalinux-build
After a while...
When petalinux is built, we have to generate a file to set the partitions of the boot file. This file has a extension.bif, and is generated from Vitis.
Generatig BOOT image.On Vitis window, Xilinx > Create Boot Image
On the opened window, we have to selected as architecture, in this case, Zynq MP, and select a file where the output bif will be created, and the BOOT.BIN file.
Then, we have to create the different partitions, by pressing Add on the middle-bottom of the window. First we will add the First Stage Bootloader, selecting as File, zynqmp_fsbl.elf, the partotios type has to be set on Bootloader, Destination CPU will be A53 0, because this is the core that starts first.
Then, clicking on Ok, this partition will be create.
Next partition to create is the for the PMU firmware. In this case, selecting as partition type pmu, de destination device and destination CPU will be configured automatically.
For the PL, we will select the.bit file, and PL as destination device.
Next we will create the petalinux partition. For that, we have to select the file bl31.elf with the next configuration.
For the application executed on RTU, the corresponding elf file has to be selected, and the destination CPU has to be set in R5 0
last, the partition for uboot
Once we have all partitions configured, click on Create Image, and BOOT.bin file will be created.
This file, with image.ub and boot.scr has to be copied on SD card.
It's important to know that if we want to change only the RPU firmware, we only have to edit the file of the R5 partition, re-generate the BOOT.bin, and replace it on SD card.
On Genesys ZU board, check that boot configuration is configured for SD, insert the SD card on the slot, and power up the board.
With some terminal application like Putty, we can connect to the board and check the start up.
Also, we can check the SSH connection.
Now, we can start to develop the high level code for manage the signal generator.
Python design.The great advantage of run a Linux distribution is all the possibilities that can this can offer to us. One of those possibilities is execute Python scripts. For this project, I will use a python script to receive the settings of the generated signal through a TCP protocol.
On Python, we need to import socket library to use all needed classes and functions. All information about socket library can be found here.
import socket
Socket library is installed by default when we add python3 to petalinuxbsp.conf.
Next we have to create a new socket.
listening_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
and finally, we will open the socket on the corresponding address and begin listening.
# Create and open a server on localhost IP, on port 10000
server_address = ('localhost', 10000)
sock.bind(server_address)
# start listening
sock.listen(1)
Once socket is created, we are going to wait for new connection by pooling inside an infinite loop.
/* Infinite loop to wait connections */
while True:
print('waiting connection')
connection, client_address = sock.accept()
try:
print('Client connected', client_address)
while True:
# Receiving data
data = connection.recv(5)
accept function waits until a client is connected, once a client is connected to the Genesys ZU board, data received will be stored on data variable with the instruction recv. Value between () is corresponding to the length expected. In this case we will use 1 byte for signal type (dc, sine, rectangular...), next 2 bytes for signal length, next byte for prescaler, and next for control (output enable, FS,...), so the expected data length will be 5.
Once data is received we have to design a little parser for decoding.
# Parsing received data from client.
signal_type = data[0]
signal_length = (data[1]&0x3f) + data[2]
signal_prescaler = data[3]&0xf
signal_oe = data[4]&0x1
signal_fs = (data[4]>>1)&0x1
Once data is recovered, we need to send it to the RPU, for that we will use the mmap library, os library and also struct.
import os
import mmap
import struct
now we have to create the memory access to the TCM memory of RPU, but from the global memory access, that is 0xFFE0 0000
# open dev/mem file
foo = os.open("/dev/mem", os.O_RDWR | os.O_SYNC)
# Create IP peripheral
data2rpu = mmap.mmap(foo, 0xf, flags=mmap.MAP_SHARED, prot=(mmap.PROT_READ | mmap.PROT_WRITE), offset=0xFFE00000)
finally we can write data encoded again according RPU C code on the corresponding addresses
# Data to write on ATCM from Global address view.
data2rpu[0:2] = struct.pack("=H", data2write)
The result will be the DAC sinthesizyng the signal configured by TCP.
On this project, we had used 3 different devices on the Zynq MPSOC, and all of them talking with each other. Even this application is not very intense in terms of computation, this is only an example of how we can use the entire device and how easy is to connect them. Also, SYZYGY connector makes easy interface high speed devices like DACs or ADCs to achieve high frequencies, what make this project ideal for begin your own signal generator. Thanks for read!
Comments