{"id":22954998,"url":"https://github.com/miranda1000/vivado_ps-pl_data_exchange","last_synced_at":"2026-01-11T01:59:21.255Z","repository":{"id":199686926,"uuid":"703282832","full_name":"miranda1000/Vivado_PS-PL_data_exchange","owner":"miranda1000","description":"Creating a Custom IP for PS-PL data exchange in Vivado","archived":false,"fork":false,"pushed_at":"2023-10-11T15:01:37.000Z","size":5579,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T18:04:27.764Z","etag":null,"topics":["digilent-zybo","tutorial","vhdl","vitis","vivado","zybo-z7"],"latest_commit_sha":null,"homepage":"","language":"VHDL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/miranda1000.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-11T00:36:16.000Z","updated_at":"2025-03-18T02:28:26.000Z","dependencies_parsed_at":null,"dependency_job_id":"dad6a6c7-0613-46c6-9b5c-1ba3e485bdb0","html_url":"https://github.com/miranda1000/Vivado_PS-PL_data_exchange","commit_stats":null,"previous_names":["rogermiranda1000/vivado_ps-pl_data_exchange","miranda1000/vivado_ps-pl_data_exchange"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miranda1000%2FVivado_PS-PL_data_exchange","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miranda1000%2FVivado_PS-PL_data_exchange/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miranda1000%2FVivado_PS-PL_data_exchange/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/miranda1000%2FVivado_PS-PL_data_exchange/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/miranda1000","download_url":"https://codeload.github.com/miranda1000/Vivado_PS-PL_data_exchange/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246733284,"owners_count":20824909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["digilent-zybo","tutorial","vhdl","vitis","vivado","zybo-z7"],"created_at":"2024-12-14T16:21:46.712Z","updated_at":"2026-01-11T01:59:21.230Z","avatar_url":"https://github.com/miranda1000.png","language":"VHDL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Creating a Custom IP for PS-PL data exchange in Vivado\n\nIn this tutorial you'll learn how to exchange information between the PS and PL, effectively allowing to read and write to the default USART from the PL.\n\n## The results\n\nIn this example the USART data will be sent to the PL, and then the PL will send back the same data to the PL to be print in the terminal, and also (if it's a number) it will light their corresponding binary value using the on-board LEDs.\n\n![steps](./images/steps.gif \"steps\")\n\n![results](./images/results.jpg \"results\")\n\n## Requirements\n\n- Vivado \u0026 Vitis 2023.1 (you can check how to install them [here](https://digilent.com/reference/programmable-logic/guides/installing-vivado-and-vitis))\n- A ZYBO Z7-10, or 20\n- Your board XDC file (you should be able to find it [here](https://github.com/Digilent/digilent-xdc/archive/master.zip))\n\n## Steps\n\n### Creating a new Vivado project\n\n1. Create a new Vivado project\n\n![create project](./images/00-create_project.jpg \"create project\")\n\nSelect \"RTL Project\" as the Project Type.\n\n![create project - project type](./images/01-create_project_2.jpg \"create project - project type\")\n\n2. Under the \"Boards\" section, select your board. If you don't have it installed, hit the \"download\" button next to it\n\n![select board](./images/02-select_board.jpg \"select board\")\n\n3. Select \"Finish\"\n\n![create project summary](./images/03-create_project_summary.jpg \"create project summary\")\n\n4. As this tutorial will use VHDL as language, enter the Settings menu (top left, the cogwheel icon), and switch \"Target language\" to VHDL. Then hit \"Apply\", and \"OK\"\n\n![target language](./images/04-vhdl_language.jpg \"target language\")\n\n### Creating a custom AXI IP\n\nTo interact with the AXI interface we'll need a custom IP.\n\n1. Under \"Tools\", click \"Create and Package New IP...\"\n\n![create ip](./images/10-create_ip.jpg \"create ip\")\n\n2. Hit \"Next\", select \"Create a new AXI4 peripheral\", then \"Next\"\n\n![create ip homepage](./images/11-create_ip_2.jpg \"create ip homepage\")\n\n![create AXI4 ip](./images/12-create_axi4_ip.jpg \"create AXI4 ip\")\n\n3. Set the IP name `usart_to_pl`. You can also set a description\n\n![ip name](./images/13-ip_details.jpg \"ip name\")\n\n4. Leave the ports as default\n\n![ip ports](./images/14-ip_ports.jpg \"ip ports\")\n\n5. Select \"Edit IP\", then \"Finish\"\n\n![edit ip](./images/15-edit_ip.jpg \"edit ip\")\n\n### Editing the custom IP\n\nOnce you've created the IP a new Vivado window will open. On Sources \u003e Design Sources you'll find the `usart_to_pl` wrapper, and the instance.\n\n#### Editing the custom IP instance\n\n1. First we'll edit the `usart_to_pl` instance. Double click on `usart_to_pl_v_1_0_S00_AXI_inst`\n\n![edit instance](./images/16-edit_inst.jpg \"edit instance\")\n\n2. Add the following ports:\n\n```vhdl\n-- Users to add ports here\n\t\t\nusart_print : in std_logic_vector(7 downto 0);\nusart_print_valid : in std_logic;\nusart_print_done : out std_logic;\n\nusart_read : out std_logic_vector(7 downto 0);\nusart_read_valid : out std_logic;\nusart_read_request : in std_logic;\n```\n\nThey will be used to request read and prints from the USART.\n\n3. On the \"user logic\" section, link the out ports with the register 0 and 1:\n\n```vhdl\n-- Add user logic here\n\nusart_read \u003c= slv_reg0(7 downto 0);\nusart_read_valid \u003c= slv_reg0(8);\nusart_print_done \u003c= slv_reg1(0);\n```\n\n4. To send data, we'll need to modify the \"Implement memory mapped register\" section. Find it, and then change the sensitivity list and `loc_addr` `10` and `11` output:\n\n```vhdl\n-- Implement memory mapped register select and read logic generation\n-- Slave register read enable is asserted when valid address is available\n-- and the slave is ready to accept the read address.\nslv_reg_rden \u003c= axi_arready and S_AXI_ARVALID and (not axi_rvalid) ;\n\nprocess (slv_reg0, slv_reg1, usart_print_valid, usart_print, usart_read_request, axi_araddr, S_AXI_ARESETN, slv_reg_rden)\nvariable loc_addr :std_logic_vector(OPT_MEM_ADDR_BITS downto 0);\nbegin\n  -- Address decoding for reading registers\n  loc_addr := axi_araddr(ADDR_LSB + OPT_MEM_ADDR_BITS downto ADDR_LSB);\n  case loc_addr is\n    when b\"00\" =\u003e\n      reg_data_out \u003c= slv_reg0;\n    when b\"01\" =\u003e\n      reg_data_out \u003c= slv_reg1;\n    when b\"10\" =\u003e\n      reg_data_out \u003c= (C_S_AXI_DATA_WIDTH-1 downto 9 =\u003e '0') \u0026 usart_print_valid \u0026 usart_print; -- 8 LSB is the data, and the followed by the \"is valid\" bit. The rest is all 0\n    when b\"11\" =\u003e\n      reg_data_out \u003c= (C_S_AXI_DATA_WIDTH-1 downto 1 =\u003e '0') \u0026 usart_read_request;\n    when others =\u003e\n      reg_data_out  \u003c= (others =\u003e '0');\n  end case;\nend process; \n```\n\n#### Editing the custom IP wrapper\n\n5. Now open the wrapper (`usart_to_pl_v_1_0`)\n\n6. Add the ports we've added earlier:\n\n```vhdl\n-- Users to add ports here\n\nusart_print : in std_logic_vector(7 downto 0);\nusart_print_valid : in std_logic;\nusart_print_done : out std_logic;\n\nusart_read : out std_logic_vector(7 downto 0);\nusart_read_valid : out std_logic;\nusart_read_request : in std_logic;\n```\n\n7. In the `usart_to_pl_v1_0_S00_AXI` port definition, you'll have to add the ports again:\n\n```vhdl\narchitecture arch_imp of usart_to_pl_v1_0 is\n-- component declaration\n  component usart_to_pl_v1_0_S00_AXI is\n    generic (\n      C_S_AXI_DATA_WIDTH\t: integer\t:= 32;\n      C_S_AXI_ADDR_WIDTH\t: integer\t:= 4\n    );\n    port (\n      usart_print : in std_logic_vector(7 downto 0);\n      usart_print_valid : in std_logic;\n      usart_print_done : out std_logic;\n      usart_read : out std_logic_vector(7 downto 0);\n      usart_read_valid : out std_logic;\n      usart_read_request : in std_logic;\n\n      S_AXI_ACLK\t: in std_logic;\n      ...\n```\n\n8. On the part of the code the instance is made, connect the ports:\n\n```vhdl\n-- Instantiation of Axi Bus Interface S00_AXI\nusart_to_pl_v1_0_S00_AXI_inst : usart_to_pl_v1_0_S00_AXI\n  generic map (\n    C_S_AXI_DATA_WIDTH\t=\u003e C_S00_AXI_DATA_WIDTH,\n    C_S_AXI_ADDR_WIDTH\t=\u003e C_S00_AXI_ADDR_WIDTH\n  )\n  port map (\n    usart_print =\u003e usart_print,\n    usart_print_valid =\u003e usart_print_valid,\n    usart_print_done =\u003e usart_print_done,\n    usart_read =\u003e usart_read,\n    usart_read_valid =\u003e usart_read_valid,\n    usart_read_request =\u003e usart_read_request,\n\n    S_AXI_ACLK\t=\u003e s00_axi_aclk,\n    ...\n```\n\n#### Finishing the custom IP\n\n9. On the \"Package IP\" tab, go to \"Customization Parameters\", then hit \"Merge changes from Customization Parameters Wizard\"\n\n![apply parameters changes](./images/19-apply_changes.jpg \"apply parameters changes\")\n\n10. On \"Compatibility\", make sure \"zynq\" is there. Otherwise, hit the \"+\" button, \"Add Family Explicitly...\", and select \"zynq\". Life-cycles are irrelevant in this tutorial\n\n![check compatibility](./images/20-check_board.jpg \"check compatibility\")\n\n\u003cdetails\u003e\n  \u003csummary\u003eNot there? Check: how to add a compatible board\u003c/summary\u003e\n\n  ![add compatibility board](./images/21-add_board.jpg \"add compatibility board\")\n\n  ![select compatibility board](./images/22-select_board_to_add.jpg \"select compatibility board\")\n\u003c/details\u003e\n\n11. On Vivado version 2023.1 there's a bug with the generated Makefile on custom IPs (you can check for more information [here](https://support.xilinx.com/s/question/0D52E00006iHq5pSAC/board-images-not-set-and-xpfm-file-deleted-automatically?language=en_US)). To solve it you'll have to go to the IP path you've selected, go to `drivers/usart_to_pl_v1_0/src`, and change the Makefile from:\n\n```\nINCLUDEFILES=*.h\nLIBSOURCES=*.c\nOUTS = *.o\n```\n\nTo:\n\n```\nINCLUDEFILES=$(wildcard *.h)\nLIBSOURCES=$(wildcard *.c)\nOUTS=$(wildcard *.o)\n```\n\n12. Go to \"Review and Package\", and hit \"Re-Package IP\"\n\n![package ip](./images/25-package_ip.jpg \"package ip\")\n\n13. Close the project\n\n![close custom ip project](./images/26-close_project.jpg \"close custom ip project\")\n\n### Creating a Block Design\n\n1. On the left, select \"IP Integrator \u003e Create Block Design\"\n\n![create block design](./images/30-create_block_design.jpg \"create block design\")\n\n2. You can set a name if you want, I'll leave it as default\n\n![block design name](./images/31-block_design_name.jpg \"block design name\")\n\n3. The design will open. Right click on it, \"Add IP...\"\n\n![add ip on design](./images/32-add_ip.jpg \"add ip on design\")\n\n4. Search for \"ZYNQ7 Processing System\", and add it\n\n![add the ps](./images/33-add_ps.jpg \"add the ps\")\n\n5. On the top, click \"Run Block Automation\". Leave it all as default, hit \"OK\"\n\n![run block automation](./images/34-auto_block.jpg \"run block automation\")\n\n![run block automation](./images/35-run_auto_block.jpg \"run block automation\")\n\n6. Again, right click, \"Add IP...\", and add the custom IP\n\n![add custom ip](./images/36-add_custom_ip.jpg \"add custom ip\")\n\n7. On the top, click \"Run Connection Automation\". Leave it all as default, hit \"OK\"\n\n![run connection automation](./images/37-auto_trace.jpg \"run connection automation\")\n\n![run connection automation](./images/38-run_auto_trace.jpg \"run connection automation\")\n\n8. You should see something like this:\n\n![block diagram result](./images/39-add_ip_result.jpg \"block diagram result\")\n\n### Adding extra sources\n\nTo broadcast the data and send it to the LEDs we'll need 3 files:\n- `ascii_to_number`: will take the ASCII data and convert it to a binary output\n- XDC file: will tell Vivado how to connect the external ports to the Zybo board\n- `usart_broadcaster`: will request the data, and then send it back and to `ascii_to_number`, respecting the timings defined by the custom IP (we'll talk about it later)\n\n1. Right click on \"Design Sources\", then \"Add Sources...\"\n\n![add sources](./images/40-add_sources.jpg \"add sources\")\n\n2. We'll add first two design sources\n\n![add design sources](./images/41-add_design_sources.jpg \"add design sources\")\n\n3. Hit \"Create File\", add an VHDL file `usart_broadcaster` and then `ascii_to_number`, and then hit \"Finish\"\n\n![create design sources](./images/42-create_file.jpg \"create design sources\")\n\n![create design source](./images/43-create_file_1.jpg \"create design source\")\n\n![created design sources](./images/44-created_files.jpg \"created design sources\")\n\n4. Leave everything as default, hit \"OK\"\n\n![review design sources](./images/45-review_files.jpg \"review design sources\")\n\n5. Open `ascii_to_number`, paste the following code:\n\n```vhdl\nlibrary IEEE;\nuse ieee.std_logic_1164.all;\nuse ieee.numeric_std.all;\n\nentity ascii_to_number is\n  Port (\n    value : in std_logic_vector(7 downto 0);\n    value_valid : in std_logic;\n    \n    o : out std_logic_vector(3 downto 0)\n  );\nend ascii_to_number;\n\narchitecture Behavioral of ascii_to_number is\nbegin\n    process(value,value_valid)\n        variable result : unsigned(7 downto 0);\n    begin\n        if (value_valid = '1' and (value \u003e= x\"30\" and value \u003c= x\"39\")) then -- got a number?\n            result := unsigned(value) - to_unsigned(48, 8); -- equivalent for ASCII character '0'; remember that 0 to 9 are consecutive ASCII elements\n        end if;\n        \n        o \u003c= std_logic_vector(result(3 downto 0));\n    end process;\nend Behavioral;\n```\n\n6. Open `usart_broadcaster`, paste the following code:\n\n```vhdl\nlibrary IEEE;\nuse IEEE.STD_LOGIC_1164.ALL;\n\nentity usart_broadcaster is\n  Port (\n    clock : in std_logic;\n    resetn : in std_logic;\n\n    usart_data : out std_logic_vector(7 downto 0);\n    usart_data_valid : out std_logic;\n    \n\t\t\n    usart_print : out std_logic_vector(7 downto 0);\n    usart_print_valid : out std_logic;\n    usart_print_done : in std_logic;\n    usart_read : in std_logic_vector(7 downto 0);\n    usart_read_valid : in std_logic;\n    usart_read_request : out std_logic\n  );\nend usart_broadcaster;\n\narchitecture behavioral of usart_broadcaster is\n    type t_State is (DATA_REQUEST, DATA_SEND);\n    signal state : t_State;\nbegin\n    process(clock) is\n    begin\n        if rising_edge(clock) then\n            if resetn = '0' then\n                state \u003c= DATA_REQUEST;\n            else\n                case state is\n                    -- request a read\n                    when DATA_REQUEST =\u003e\n                        usart_print_valid \u003c= '0'; -- done printing\n                        usart_data_valid \u003c= '0'; -- out data invalid\n\n                        usart_read_request \u003c= '1';\n                        \n                        if (usart_read_valid = '1' and usart_print_done = '0') then\n                            -- done reading and ready to print; prepare printing\n                            usart_data \u003c= usart_read;\n                            usart_print \u003c= usart_read;\n\n                            state \u003c= DATA_SEND;\n                        end if;\n\n                    -- print request\n                    when DATA_SEND =\u003e\n                        usart_read_request \u003c= '0'; -- done reading\n                        usart_data_valid \u003c= '1'; -- out data valid\n\n                        -- data already loaded on the last state\n                        usart_print_valid \u003c= '1';\n                        \n                        if (usart_print_done = '1' and usart_read_valid = '0') then\n                            -- done printing and ready to read\n                            state \u003c= DATA_REQUEST;\n                        end if;\n                end case;\n            end if;\n        end if;\n    end process;\nend behavioral;\n```\n\nThe broadcaster has two states, one to request a data, and the other to print it (and forward it to the other PL blocks). That way we meet the following timing criteria:\n\n![print request](./images/print_request.jpg \"print request\")\n\n![scan request](./images/scan_request.jpg \"scan request\")\n\n7. Drag\u0026drop `usart_broadcaster`, then connect the ports with their respective ports on `usart_to_pl`. Connect `clock` to `s00_axi_aclk`, and `resetn` to `s00_axi_resetn`. For convenience, I've rotated the usart_to_pl block in the diagram\n\n![broadcaster connection](./images/48-broadcaster.jpg \"broadcaster connection\")\n\n8. Drag\u0026drop `ascii_to_number`, then connect the ports to the broadcaster\n\n9. Right click on `ascii_to_number`'s out port, \"Make External\"\n\n![external pin](./images/50-external_pin.jpg \"external pin\")\n\n10. Set the external pin name to \"led\"\n\n![external pin name](./images/51-external_pin_name.jpg \"external pin name\")\n\n11. Now we'll add the XDC file. Right click on \"Design Sources\", \"Add Sources...\", and this time select \"Add or create constraints\"\n\n![add sources](./images/40-add_sources.jpg \"add sources\")\n\n![add constraints source](./images/52-add_constraints.jpg \"add constraints source\")\n\n12. Select \"Add Files\"\n\n![add existant constraints source](./images/53-add_existant_constraint.jpg \"add existant constraints source\")\n\n13. Search your board's XDC file (if you don't have it check how to get it on the [Requirements section](#requirements))\n\n![add master constraint](./images/54-add_master_constraint.jpg \"add master constraint\")\n\n14. Make sure \"Copy constraints file into project\" is checked, then hit \"Finish\"\n\n![copy master constraint](./images/55-copy_constraint.jpg \"copy master constraint\")\n\n15. The file is under the Constraints folder, double click to open it\n\n![open constraint](./images/56-open_constraint.jpg \"open constraint\")\n\n16. Uncomment the LEDs constraints:\n\n```\n##LEDs\nset_property -dict { PACKAGE_PIN M14   IOSTANDARD LVCMOS33 } [get_ports { led[0] }]; #IO_L23P_T3_35 Sch=led[0]\nset_property -dict { PACKAGE_PIN M15   IOSTANDARD LVCMOS33 } [get_ports { led[1] }]; #IO_L23N_T3_35 Sch=led[1]\nset_property -dict { PACKAGE_PIN G14   IOSTANDARD LVCMOS33 } [get_ports { led[2] }]; #IO_0_35 Sch=led[2]\nset_property -dict { PACKAGE_PIN D18   IOSTANDARD LVCMOS33 } [get_ports { led[3] }]; #IO_L3N_T0_DQS_AD1N_35 Sch=led[3]\n```\n\n### Exporting to Vitis\n\n1. Right click on the created block design, \"Create HDL Wrapper...\"\n\n![create hdl wrapper](./images/58-hdl_wrapper.jpg \"create hdl wrapper\")\n\n2. Select \"Let Vivado manage wrapper and auto update\", and hit \"OK\"\n\n![create hdl wrapper](./images/59-create_dhl_wrapper.jpg \"create hdl wrapper\")\n\nIf you get a `Parameter has negative value` warning ignore it.\n\n![ignore warning](./images/60-ignore-warning.jpg \"ignore warning\")\n\n3. On the top, select \"Generate Bitstream\"\n\n![generate bitstream](./images/61-generate_bitstream.jpg \"generate bitstream\")\n\n4. Launch as many jobs as you can, then hit \"OK\"\n\n![start bitstream generation](./images/62-start_bistream_generation.jpg \"start bitstream generation\")\n\n5. Wait for the bitstream generation (you'll see the loading process on the top right)\n\n![wait bitstream generation](./images/63-bistream_generation_in_progress.jpg \"wait bitstream generation\")\n\n6. Once it's done a window will pop, hit \"Cancel\"\n\n![close window](./images/64-bistream_generation_done.jpg \"close window\")\n\n7. Select \"File \u003e Export \u003e Export Hardware...\"\n\n![export hardware](./images/65-export_hardware.jpg \"export hardware\")\n\n8. Click \"Next\"\n\n![export hardware homepage](./images/66-export_hardware_platform.jpg \"export hardware homepage\")\n\n9. Make sure \"Include bitstream\" is selected, then hit \"Next\"\n\n![export hardware with bitstream](./images/67-include_bitstream.jpg \"export hardware with bitstream\")\n\n10. Click \"Next\", \"Finish\"\n\n![xsa path](./images/68-xsa_path.jpg \"xsa path\")\n\n![export hardware summary](./images/69-export_hardware_summary.jpg \"export hardware summary\")\n\n### Creating a Vitis project\n\n1. Launch Vitis (you can use \"Tools \u003e Launch Vitis IDE\")\n\n![launch Vitis](./images/70-launch_vitis.jpg \"launch Vitis\")\n\n2. Go to \"File \u003e New \u003e Application Project...\"\n\n![create application](./images/71-create_application.jpg \"create application\")\n\n3. Hit \"Next\"\n\n![create application homepage](./images/72-create_app_homepage.jpg \"create application homepage\")\n\n4. Go to \"Create a new platform from hardware (XSA)\", select \"Browse...\" and select the XSA you've exported on Vivado\n\n![create platform from xsa](./images/73-create_platform_from_xsa.jpg \"create platform from xsa\")\n\n![select exported xsa](./images/74-select_exported_xsa.jpg \"select exported xsa\")\n\n5. Make sure \"Generate boot components\" is checked, then hit \"Next\"\n\n![create platform](./images/75-create_platform.jpg \"create platform\")\n\n6. Set `usart_from_pl` as \"Application project name\", then hit \"Next\"\n\n![application name](./images/76-app_name.jpg \"application name\")\n\n7. Leave the domain as default, hit \"Next\"\n\n![application domain](./images/77-app_domain.jpg \"application domain\")\n\n8. Select \"Empty Application (C)\"\n\n![empty application](./images/78-empty_app.jpg \"empty application\")\n\n### Creating the Vitis main\n\n1. Right click the `src` folder, \"New \u003e File\"\n\n![new file](./images/79-new_file.jpg \"new file\")\n\n2. Set `main.c` as name, hit \"Finish\"\n\n![new main](./images/80-new_main.jpg \"new main\")\n\n3. Open `main.c` (double click) and paste the following code:\n\n```c\n#include \u003cstdio.h\u003e\n#include \"xil_printf.h\"\n#include \"xbasic_types.h\"\n#include \"xparameters.h\"\n#include \"xuartps_hw.h\" // XUARTPS_FIFO_OFFSET\n\n#define is_valid(data) ((data \u0026 (1\u003c\u003c8)) \u003e 0)\n\nXuint8 unwaited_read(unsigned char *valid) {\n\t*valid = XUartPs_IsReceiveData(STDIN_BASEADDRESS);\n\tif (!(*valid)) return 0;\n\n\treturn (Xuint8) XUartPs_ReadReg(STDIN_BASEADDRESS, XUARTPS_FIFO_OFFSET);\n}\n\nint main() {\n\tXuint32 data;\n\tXuint8 inp, valid;\n\tXuint8 last_send_request = 0, last_print_request = 0;\n\tXuint8 send_request, print_request;\n  volatile Xuint32 *slaveaddr_p = (Xuint32 *) XPAR_USART_TO_PL_0_S00_AXI_BASEADDR;\n\n  xil_printf(\"\\r\\nWrite something:\\r\\n\");\n\n  while (1) {\n    send_request = (*(slaveaddr_p+3)) \u0026 0x01;\n\t\tif (send_request \u003e 0) {\n      if (send_request != last_send_request) {\n\t\t\t\t// send chars from usart to PL\n\t\t\t\tinp=unwaited_read(\u0026valid);\n\t\t\t\tif (valid) {\n\t\t\t\t\tdata = (Xuint32)inp;\n\t\t\t\t\tdata |= (1\u003c\u003c8); // mark as valid\n\t\t\t\t\t*slaveaddr_p = data; // send data\n\n\t\t\t\t\tlast_send_request = send_request;\n\t\t\t\t}\n      }\n\t\t}\n\t\telse {\n\t\t\t*slaveaddr_p = 0; // invalid\n      last_send_request = send_request;\n\t\t}\n\n\t\t// print data from PL to usart\n\t\tdata = *(slaveaddr_p+2);\n\t\tprint_request = is_valid(data);\n\t\tif (print_request) {\n\t\t\tif (print_request != last_print_request) {\n\t\t\t\txil_printf(\"%c\", data\u00260xFFFF);\n\t\t\t\t*(slaveaddr_p+1) = 1; // print ok\n\n\t\t\t\tlast_print_request = print_request;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t*(slaveaddr_p+1) = 0; // done printing\n\t\t\tlast_print_request = print_request;\n\t\t}\n  }\n\n  return 0;\n}\n```\n\n4. Build the project (top left hammer icon)\n\n![build project](./images/82-build.jpg \"build project\")\n\n### Uploading the code to the board\n\n1. Connect your board to the computer\n\n2. Right click `usart_from_pl`, then \"Run As \u003e Launch Hardware (Single Application Debug)\"\n\n![upload project](./images/83-upload.jpg \"upload project\")\n\n### Interacting with the board\n\nYou'll need a serial terminal to interact with the code, in this section we'll use the one included in Vitis.\n\n1. Go to \"Window \u003e Show view...\"\n\n![showing the Vitis serial terminal](./images/90-serial_terminal_1.jpg \"showing the Vitis serial terminal\")\n\n2. Search for \"Vitis Serial Terminal\", then hit \"Open\"\n\n![showing the Vitis serial terminal](./images/91-serial_terminal_2.jpg \"showing the Vitis serial terminal\")\n\n3. Now you should have it on the bottom right corner. Click the plus (+) icon to connect to the board\n\n![connecting to the board](./images/92-serial_terminal_add.jpg \"connecting to the board\")\n\n4. Select the only port available, then hit \"OK\". Leave the Baud Rate as it is (115200)\n\n![connecting to the board](./images/93-serial_terminal_add_2.jpg \"connecting to the board\")\n\n5. Send a number, and see the LEDs change!\n\n![sending data with the serial terminal](./images/94-send_serial_terminal.jpg \"sending data with the serial terminal\")\n\n![results](./images/results.jpg \"results\")\n\n### Thanks for following!\n\nRemember to give me a star if it was useful! I'm also open for PR for code improvements.\n\n## References\n\n- To create a project, refer to the [\"Getting Started with Vivado and Vitis for Baremetal Software Projects\" guide](https://digilent.com/reference/programmable-logic/guides/getting-started-with-ipi)\n- All the boards XDC files are on the [Digilent XDC GitHub repo](https://github.com/Digilent/digilent-xdc)\n- To create a slave AXI custom IP, refer to [\"Creating a Custom IP core using the IP Integrator\"](https://digilent.com/reference/learn/programmable-logic/tutorials/zybo-creating-custom-ip-cores/start)\n- For the PS-PL exchange I used part of the code found on the [\"Creating a Custom AXI4 Master in Vivado\" tutorial](https://github.com/k0nze/zedboard_axi4_master_burst_example)\n- The bug about the Makefile was discussed on the xilinx forum [\"Board Images not set and .xpfm file deleted automatically\"](https://support.xilinx.com/s/question/0D52E00006iHq5pSAC/board-images-not-set-and-xpfm-file-deleted-automatically)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiranda1000%2Fvivado_ps-pl_data_exchange","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmiranda1000%2Fvivado_ps-pl_data_exchange","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmiranda1000%2Fvivado_ps-pl_data_exchange/lists"}