Jump to content
  • 0

Using the Genesys-ZU UART1 with the bare metal driver


John J

Question

I'm using Vivado and Vitis 2020.1 projects created from the bare metal Genesys-ZU Hello World example as a base to create an application which will be controlled over UART1.  In Vivado, I've enabled UART1 and mapped it to EMIO pins.

Using the Xilinx UART polled example UART0 passes and UART1 fails XUartPs_SelfTest().

Is there something special that needs to be done to UART1 to get it to at least pass the self-test?

I've also:

  • Created my own base project using XSCT and replacing the Xilinx FSBL with the Digilent custom FSBL with the same result
  • Changed the standard output of the FSBL and application to use UART1 as default with the same result
  • Mapped UART1 to MIO pins to see if it would pass the self test but got the same result

I have been through several tutorials and browsed the available information, but I still appear to be missing some step in the configuration of the port.

Does someone have any ideas on what I'm missing here?

I've tried this on a 3EG and a 5EV Genesys-ZU board.

Thank you for your help.

JJJ

Edited by John J
typo
Link to comment
Share on other sites

20 answers to this question

Recommended Posts

  • 2

Hey @John J,

We could reproduce the issue and I wanted to share the debug process and the solution.

Checked out the 3eg/master hello_world reference project. Activated UART1 in Vivado, exported and imported into Vitis. Added driver init calls for UART0 and UART1. Calling SelfTest on UART0 works, UART1 does not.

Tried reading addresses 0xFF000000 and 0xFF010000 with mrd in the XSCT console. Reading 0xFF010000, which is the UART1 base address fails:

xsct% mrd 0xFF010000
Memory read error at 0xFF010000. Blocked address 0xFF010000. Block level reset, bit 2 in RST_LPD_IOU2

Indeed register RST_LPD_IOU2 (CRL_APB) at address 0x00FF5E0238 reads with bit 2, uart1_reset, set to 1. UART1 is being kept in reset, which is the reason for SelfTest (and any other action) failing.

Block-level resets should be de-activated during PSU init by the FSBL. If UART1 is enabled in Vivado, there should be a corresponding write to its reset in the exported psu_init.c. The exported psu_init.c indeed de-asserts reset, but the 3eg_fsbl/src/psu_init.c does not:

$ diff /D/git/zuca/sw/ws/3eg_hw_pf/export/3eg_hw_pf/hw/psu_init.c /D/git/zuca/sw/ws/3eg_fsbl/src/psu_init.c
1064,1087d1063
<     * Register : UART1_REF_CTRL @ 0XFF5E0078
<
<     * Clock active signal. Switch to 0 to disable the clock
<     *  PSU_CRL_APB_UART1_REF_CTRL_CLKACT                           0x1
<
<     * 6 bit divider
<     *  PSU_CRL_APB_UART1_REF_CTRL_DIVISOR1                         0x1
<
<     * 6 bit divider
<     *  PSU_CRL_APB_UART1_REF_CTRL_DIVISOR0                         0xf
<
<     * 000 = IOPLL; 010 = RPLL; 011 = DPLL; (This signal may only be toggled af
<     * ter 4 cycles of the old clock and 4 cycles of the new clock. This is not
<     *  usually an issue, but designers must be aware.)
<     *  PSU_CRL_APB_UART1_REF_CTRL_SRCSEL                           0x0
<
<     * This register controls this reference clock
<     * (OFFSET, MASK, VALUE)      (0XFF5E0078, 0x013F3F07U ,0x01010F00U)
<     */
<       PSU_Mask_Write(CRL_APB_UART1_REF_CTRL_OFFSET,
<               0x013F3F07U, 0x01010F00U);
< /*##################################################################### */
<
<     /*
16267,16269d16242
<     * Block level reset
<     *  PSU_CRL_APB_RST_LPD_IOU2_UART1_RESET                        0
<
16272c16245
<     * (OFFSET, MASK, VALUE)      (0XFF5E0238, 0x00000006U ,0x00000000U)
---
>     * (OFFSET, MASK, VALUE)      (0XFF5E0238, 0x00000002U ,0x00000000U)
16275c16248
<               0x00000006U, 0x00000000U);
---
>               0x00000002U, 0x00000000U);

Vitis does not automatically update the psu_init.* local copies in standalone application projects. These should actually be linked from the platform, but Vitis does not do that.

There are three work-arounds possible:

  1. Overwrite psu_init.* in 3eg_fsbl after every xsa import.
  2. Export xsa to sw/src/3eg_hw_pf. Checkout Vitis workspace again using sw/src/checkout.tcl.
  3. Generate boot components in platform project, internalizing fsbl and automatizing psu_init update. This hinges on Xilinx fixing the FSBL BSP optimization flag bug, which is the reason for externalizing the FSBL in the first place.
Link to comment
Share on other sites

  • 1

Hi @elodg,

I'm working with @John J on this project. 

  1. We copied the psu_init.* files from platform/hw/ folder and using the the test code provided by @JColvin Then we cleaned and rebuilt all projects without updating the Hardware Specification which seemed to work. 
  2. I'm not finding a sw/src/checkout.tcl file.  Can you post a link to the build process that uses that file?
Edited by Eminem
Link to comment
Share on other sites

  • 0

I've used the EMIO to connect PL logic or pins to a spare PS UART on many occasions. I've never used the any of the UART self-test software example projects. A better test is to implement a UART in logic and do you own test. Once that passes you can connect the UART EMIO signals to external pins. The EMIO UART interface might be more complicated than you are expecting. Also, sometimes the software tools confuse UART0 and UART1. Also, make sure that your software project are connected to the correct PS UART instance.

You have to be careful with the software examples in the tools. Many times they require a particular hardware loopback or other arrangement. I don't remember well enough to say for sure, but I think that the PS UART has an internal loopback capability that an EMIO UART wouldn't have, unless you designed a UART to emulate that. A simple test could be to connect the Tx and Rx signals in logic for your example.

Personally, I prefer to just connect this type of PS UART via EMIO directly to pins and use a 3.3V TTL USB UART cable or breakout board to test it from a PC. You can always add an ILA to troubleshoot problems, though I don't recall ever needing to do that. All of the issues that I recall facing are related to commentary in this post.

I'm 90+% sure that your problem is related to these comments and not the board or tools.  

Edited by zygot
Link to comment
Share on other sites

  • 0

@zygot,

I appreciate all input that anyone can provide.

I would like to try implementing a PL UART, but we have a design that uses the PS UART already.  That's additional scope for my project.

Also, it's not the example that is failing, but the self-test in the bare metal driver, XUartPs_SelfTest(), that fails.  it also fails the internal loopback test when I map to MIO pins, but I can't actually use those to see if it's the self-test in the driver that is failing, as the available pins are already connected on the Genesys-ZU board.

I believe you are right about the provided code confusing the ports, as I have found more than one minor error in the code.  The UART interrupt example has had the wrong interrupt controller number for years.  In addition, even if I set the FSBL stdout to use UART1, that doesn't work.  I do see videos where people swap FSBL ports without issue.

It would be nice to find the root cause here.

John J

Edited by John J
Link to comment
Share on other sites

  • 0
I had supplied a link to this project: https://forum.digilent.com/topic/22512-manipulate-pl-logic-using-ps-registers/

Unfortunately, I didn't have my scripting blocker active and Digilent's website didn't allow me to choose how the line would be displayed after copying it to the post. I can't see the link above when I have scripting blocked in my browser, as I do now.

Anyway, look it over. It might provide some clues. Understand that UltraScale might be different than Z7000 in terms of EMIO signals and software.

The Xilinx software tools always have bugs. Some are not too bothersome difficult to work out and others are very time consuming to resolve. Cost of doing business. . Just be careful that what the tools are doing is what you intend.

Sometimes you just have to create a special experimental project to explore the root cause of unexplained issues. I do this all the time. In the end the extra effort usually saves time because you can simplify the experimental project an make it simpler. Of course you need access to hardware to do this.

I use UART interface for all of my projects, even if only for debugging. I have 4-5 TTL USB UART cables and breakout board and usually they are all connected to some board. All you need are 2 spare GPIO connector pins and a DGND pin and off you go. BTW the tutorial referenced above has a generic UART that works well and is simple to understand so I include it in my published project as a source.

Since you are using a Linux based software environment there are a number of extra steps to consider when adding user mode hardware. Personally, I prefer to test hardware interfaces in as simple a fashion as possible. You need to work in a way that suits you... but consider alternatives when brute force isn't working. Edited by zygot
Link to comment
Share on other sites

  • 0

Hi @John J,

I finally got back into the office to be able to actually test out the UART1 functionality with regards to the SelfTest. Like you, UART1 also fails the self test (I had it left in it's default configuration as EMIO pins, error code 1054 which based on the selftest program corresponds to the test string not matching the received string).

What I am not certain of and will need to reach out about is if the UART1 is accessible; in my serial terminal when booting up it says the Platform MCU firmware is not running which is what the UART1 is connected to, at least based on the schematic materials and the reference manual. I am able to seemingly send data to the Platform MCU over the connected COM port, but it is not responding with data in a format I am expecting, so I am not certain if I am communicating with the module incorrectly or have done my setup wrong.

Thanks,
JColvin

Link to comment
Share on other sites

  • 0

@JColvin

Huh?  Even though I don't have the FTDI schematic page, the PMU should be connected to port 2 on the FTDI chip per the naming in the schematic.  The board reference also states this.

UART1 on the PS is not even mapped in the Genesys-ZU board files, as all pins that it can be mapped to are connected to other I/O devices.

I did try to use MIO pins to run the internal loopback self-test in the driver library code on UART1, but it failed.  While debugging, I saw that all data is returned as 0.  Is the code even interacting with the UART1 hardware?

On a side note, it would be helpful to have the FTDI schematic page, and Xilinx does provide it for their boards.

Edited by John J
Link to comment
Share on other sites

  • 0

Hi @John J,
I was able to get the UART1 PS selftest (and external loopback) working with EMIO. In my initial test I had forgotten to make the UART1 module on the UltraScale+ IP in the block design external (and then of course constrained them to a pair of external pins, in this case a pair of Pmod pins). With regards to the XUartPs_SelfTest, a return of zero is a successful test (i.e. no error codes thrown). I've provided the main.c that I used (which also has 4 buttons and LEDs enabled in the hardware design so that pushing one of the buttons lights up all of the LEDs; this can be commented out of course).

Spoiler


#include "xparameters.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "xil_types.h"
#include "xuartps.h"

// Get device IDs from xparameters.h
#define BTN_ID XPAR_AXI_GPIO_BUTTONS_DEVICE_ID
#define LED_ID XPAR_AXI_GPIO_LED_DEVICE_ID
#define BTN_CHANNEL 1
#define LED_CHANNEL 1
#define BTN_MASK 0b1111
#define LED_MASK 0b1111
#define UART0_ID XPAR_PSU_UART_0_DEVICE_ID
#define UART1_ID XPAR_PSU_UART_1_DEVICE_ID

int main() {
	//variables for the external echo loopback
	u8 numcharSend = 0;	//how many characters we got from host serial terminal to send to other board
	u8 numcharRecv = 0; //how many characters we received from the other board
	u8 character = 0;	//variable to store the received character
	//XuartPs variables
	XUartPs uart0, uart1;
	XUartPs_Config *cfg_uartptr;
	s32 uart0result, uart1result;
	//XGpio parameters
	XGpio_Config *cfg_ptr;
	XGpio led_device, btn_device;
	u32 data;

	//Initialize both UART0 (host PC connection) and UART1 (external connection)
	cfg_uartptr = XUartPs_LookupConfig(UART0_ID);
	XUartPs_CfgInitialize(&uart0, cfg_uartptr, cfg_uartptr->BaseAddress);
	cfg_uartptr = XUartPs_LookupConfig(UART1_ID);
	XUartPs_CfgInitialize(&uart1, cfg_uartptr, cfg_uartptr->BaseAddress);

	while(XUartPs_IsSending((&uart0))){} 	//wait for any FSBL printing to finish

	xil_printf("Entered function main\r\n");
	while(XUartPs_IsSending((&uart0))){}	//wait for xil_printf to finish

	uart0result = XUartPs_SelfTest(&uart0);
	xil_printf("SelfTest Uart0: %d, a zero indicates no errors\r\n", uart0result);
	while(XUartPs_IsSending((&uart0))){}

	uart1result = XUartPs_SelfTest(&uart1);
	xil_printf("SelfTest Uart1: %d, a zero indicates no errors", uart1result);
	while(XUartPs_IsSending((&uart1))){}


	// Initialize LED Device
	cfg_ptr = XGpio_LookupConfig(LED_ID);
	XGpio_CfgInitialize(&led_device, cfg_ptr, cfg_ptr->BaseAddress);

	// Initialize Button Device
	cfg_ptr = XGpio_LookupConfig(BTN_ID);
	XGpio_CfgInitialize(&btn_device, cfg_ptr, cfg_ptr->BaseAddress);

	// Set Button Tristate
	XGpio_SetDataDirection(&btn_device, BTN_CHANNEL, BTN_MASK);

	// Set Led Tristate
	XGpio_SetDataDirection(&led_device, LED_CHANNEL, 0);

	while (1) {
		data = XGpio_DiscreteRead(&btn_device, BTN_CHANNEL);
		data &= BTN_MASK;
		if (data != 0) {
			data = LED_MASK;
		} else {
			data = 0;
		}
		XGpio_DiscreteWrite(&led_device, LED_CHANNEL, data);

		//loop back that takes data from UART0 host PC COM port,
		//sends the received character out the external UART1 txd,
		numcharSend = XUartPs_Recv(&uart0, &character, 1);
		if (numcharSend > 0) {
			XUartPs_Send(&uart1, &character, 1);
		}
		//any character received from the external UART1 rxd (done externally via a wire),
		//send that character back to the UART0 host PC COM port
		numcharRecv = XUartPs_Recv(&uart1, &character, 1);
		if (numcharRecv > 0) {
			XUartPs_Send(&uart0, &character, 1);
		}
	}
}
data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

The UART1 channel does not have its own COM port associated with it; that other channel (i.e. not the one used for the Primary Host PC serial communication) on the FT4232HQ is reserved for the PC to communicate with the Platform MCU (and then the Platform MCU communicates with the rest of the system over I2C).

As for communicating with the Platform MCU itself over it's COM port, you will need to configure the serial terminal to 115200 baud, 8 bits, even parity, 1 stop bit, with a line feed following each command (described further in it's section of the Reference Manual, https://digilent.com/reference/programmable-logic/genesys-zu/reference-manual#platform_mcu).

As for the FTDI page in the schematic, Digilent has made the choice to not reveal this page of the schematic. You are not the only one disappointed with this of course, but it is what it is.

Let me know if you have any questions.

Thanks,
JColvin

Link to comment
Share on other sites

  • 0

Hi @JColvin

Thank you for the example.  I will check it out Monday, as I am out of the office today.  My hardware config already pretty much matches yours, so that's perfect.

Looking at your code, I believe the Xilinx example is the problem. (just off the top of my head)  I don't think they wait the way your code does, and yours make sense.  I should know better than to trust their examples.

I did not need to connect with the PMU, but I was bit confused by your earlier comment.

I'll let you know what I find out Monday.

Thanks again.

Link to comment
Share on other sites

  • 0

Hi @JColvin.

In your example, what is the process for creating definitions in the headers for XPAR_AXI_GPIO_BUTTONS_DEVICE_ID and XPAR_AXI_GPIO_LED_DEVICE_ID?

Are these manually entered or generated by Vivado and Vitis.  I have not seen those definitions anywhere.

Thank you for your help.

Link to comment
Share on other sites

  • 0

Hi @John J,

Those values I got from the xparameters.h file which was automatically generated because I had added the buttons and LEDs from the board tab to be controlled as AXI GPIO blocks. (I attached a picture of my block design for the 3EG)
The xparameters.h file itself lives in one of the many "include" folders that get exported to SDK/Vitis. Within the wrapper, one of the many places it will be in is:
<name_of_wrapper>/<processor_name>/<domain_processor_name>/bsp/<processor_name>/include
Or you can always just add the line
#include "xparameters.h"
and then do a ctrl+left-mouse-click to have SDK/Vitis find and open the file for you.

I then usually ctrl+F for the name of the IP block I'm looking for (AXI_GPIO for example) or something like UART or UARTPS. Of course, all this is done simply because I want to use the pre-existing Xilinx library functions with the already imported base addresses and other parameters so I don't have to manually do it myself.

Let me know if you have any questions.

Thanks,
JColvin

 

image.png

Link to comment
Share on other sites

  • 0

Hi @JColvin.

That is what I thought about the headers, but the secret is the renaming of the AXI GPIO interfaces.  Got it.  Thanks.

My UART1 fails the test in your code, so I assume that I'm missing something in Vivado.

Xilinx Zynq MP First Stage Boot Loader
Release 2020.1   Aug 29 2022  -  19:45:30
Reset Mode      :       System Reset
Platform: Silicon (4.0), Running on A53-0 (64-bit) Processor, Device Name: XCZU3EG
Digilent Genesys ZU board-specific init
In JTAG Boot Mode
PMU-FW is not running, certain applications may not be supported.
Protection configuration applied
PL Configuration done successfully
Exit from FSBL
Entered function main
SelfTest Uart0: 0, a zero indicates no errors
SelfTest Uart1: 1054, a zero indicates no errors

My constraints are:

set_property -dict { PACKAGE_PIN AG14  IOSTANDARD LVCMOS33 } [get_ports { UART_1_0_txd }]; #
set_property -dict { PACKAGE_PIN AH14  IOSTANDARD LVCMOS33  PULLUP TRUE } [get_ports { UART_1_0_rxd }]; #

My diagram is below.

What is missing?

Thank you for your help.

image.thumb.png.b6fcfba4db8cb76f5c755ca5ee2caefb.png

Edited by John J
Link to comment
Share on other sites

  • 0

Do not confuse PMU-FW with Platform MCU firmware, as they are separate processors. The original issue, the PS UART1 controller failing internal loopback has nothing to do with those, is odd and should pass just like UART0 in my opinion.

Sources:

https://github.com/Xilinx/embeddedsw/blob/master/XilinxProcessorIPLib/drivers/uartps/examples/xuartps_selftest_example.c

https://github.com/Xilinx/embeddedsw/blob/d37a0e8824182597abf31ac3f1087a5321b33ad7/XilinxProcessorIPLib/drivers/uartps/src/xuartps_selftest.c#L129

https://github.com/Xilinx/embeddedsw/blob/d37a0e8824182597abf31ac3f1087a5321b33ad7/lib/bsp/standalone/src/common/xstatus.h#L231

The error is a data comparison error between sent and received bytes. The test indeed seems to be using internal loopback. The only difference I can think of can be the ref_clk configuration, which is independent for the two UART controllers. We use UART1  in our manufacturing test mapped to EMIO and it works. Our https://github.com/Digilent/Genesys-ZU-HW/tree/5ev/oob/master repo shows this.

 

Edited by elodg
Link to comment
Share on other sites

  • 0

Hi @elogd.

You are correct about me confusing the terms for PMU vs Platform MCU.  It was my mistake.  That never had an affect on my project.  I just got confused about JColvin's comment.

I took a look at the clock configuration, and both UARTs look identical to me.  Are there other options elsewhere in the Zynq configuration?

image.png.5f8efbeea646f05b3c99e905a27d4e22.png

I am trying to set up the OOB project to see if there is something missing in the configuration, but I am having issues with the instructions appearing to not match the project.  I will keep trying.

Thank you for your help.

 

 

Edited by John J
Link to comment
Share on other sites

  • 0

The code above still fails on my 3EG and 5EV boards.

Since my Vivado configuration for UART0 and UART1 only differ by the output pins (see below), and I'm using the working main() provided above, I wonder if I'm missing something in the Vitis project.  I even looked through the *zynq _ultra_ps*.xci file to find any base configuration differences, but nothing looks different.

My process is to create the XSA and then create a Vitis Hello World workspace based on the XSA.  Then I just replace the hello world C file with the main() source above.

Is there something that needs to be done to make UART1 work in the Vitis project outside of using the main() listed above?

Thank you for your help.

 

This is my UART configurations in the block diagram.

"PSU__CRL_APB__UART0_REF_CTRL__ACT_FREQMHZ": {
"value": "100.000000"
},
"PSU__CRL_APB__UART0_REF_CTRL__FREQMHZ": {
"value": "100"
},
"PSU__CRL_APB__UART0_REF_CTRL__SRCSEL": {
"value": "IOPLL"
},
"PSU__IRQ_P2F_UART0__INT": {
"value": "0"
},
"PSU__UART0__BAUD_RATE": {
"value": "115200"
},
"PSU__UART0__MODEM__ENABLE": {
"value": "0"
},
"PSU__UART0__PERIPHERAL__ENABLE": {
"value": "1"
},
"PSU__UART0__PERIPHERAL__IO": {
"value": "MIO 18 .. 19"
},
"UART0_BOARD_INTERFACE": {
"value": "custom"
},
LPD;UART0;FF000000;FF00FFFF;1


"PSU__CRL_APB__UART1_REF_CTRL__ACT_FREQMHZ": {
"value": "100.000000"
},
"PSU__CRL_APB__UART1_REF_CTRL__FREQMHZ": {
"value": "100"
},
"PSU__CRL_APB__UART1_REF_CTRL__SRCSEL": {
"value": "IOPLL"
},
"PSU__IRQ_P2F_UART1__INT": {
"value": "0"
},
"PSU__UART1__BAUD_RATE": {
"value": "115200"
},
"PSU__UART1__MODEM__ENABLE": {
"value": "0"
},
"PSU__UART1__PERIPHERAL__ENABLE": {
"value": "1"
},
"PSU__UART1__PERIPHERAL__IO": {
"value": "EMIO"
},
"UART1_BOARD_INTERFACE": {
"value": "custom"
},
LPD;UART1;FF010000;FF01FFFF;1

"PSU__UART0_LOOP_UART1__ENABLE": {
"value": "0"
},

 

 

 

Link to comment
Share on other sites

  • 0

Hi @jcolvin,

Can you tell me if there is something significant that I'm missing in the process of using UART1?

Essentially, I just have been enabling UART1 as EMIO in my Vivado design, mapping the pins in my constraints, creating a Hello World Vitis project based on the Vivado design and substituting your main() code.

Is there anything else that should be done?

Thank you.

Link to comment
Share on other sites

  • 0
On 10/12/2022 at 6:37 PM, Eminem said:

Hi @elodg,

I'm working with @John J on this project. 

  1. We copied the psu_init.* files from platform/hw/ folder and using the the test code provided by @JColvin Then we cleaned and rebuilt all projects without updating the Hardware Specification which seemed to work. 
  2. I'm not finding a sw/src/checkout.tcl file.  Can you post a link to the build process that uses that file?

Glad it worked.

Umbrella repo: https://github.com/Digilent/Genesys-ZU/tree/3eg/master

SW submodule with script instructions: https://github.com/Digilent/Genesys-ZU-SW/tree/38d18e311bf41830ad22bec9dfad83f6e2a3b6ee/src

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...