Bar
SpaceWire UK
Specialist providers of VHDL Intellectual Property & Design Services
BarBarBarBar
Tutorial
Missing Image!
Part 14 - Add textural information inside the PL to provide project & build information

Introduction

This tutorial details the steps required to add a read-only register bank inside the PL that will contain product identification, version & timestamp information. The tutorial covers quite a range of activities and includes the use of numerous languages; C, HTML, Javascript, System Verilog, TCL & Verilog.

Aims

The aims of this tutorial are as follows :-

    Part 1 - Project Setup

    1. Setup environment
    2. Change present working directory

    Part 2 - Firmware Development

    1. Launch Vivado & open project
    2. Create AXI4-Lite peripheral (HDL)
    3. Open block design
    4. Re-customize AXI GPIO
    5. Add peripheral to block design
    6. Edit address map
    7. Create, move & modify HDL wrapper
    8. Create build hook scripts
    9. Update testbench
    10. Generate bitstream
    11. Export hardware platform

    Part 4 - Revision Control

    1. Commit new & updated files

    Part 5 - OS Development

    1. Create peek string C application
    2. Update website to display ID strings
    3. Update Javascript to peek ID strings
    4. Update hardware platform
    5. Build & package PetaLinux

    Part 6 - Revision Control

    1. Commit new & updated files

    Part 7 - Hardware Deployment

    1. Setup Zedboard hardware
    2. Launch MiniCom terminal emulator
    3. Deploy PetaLinux on Zedboard
    4. Check everything is working as expected

    Part 6 - Quickstart

    1. Obtain tutorial files from Bitbucket, create & build projects, deploy on Zedboard
    #### Part 1 - Project Setup ####

    1. Setup environment

    Setup Xilinx design environment for the 2021.2 toolset.
    steve@Desktop:~$ xilinx
    Xilinx tools available tools at /opt/Xilinx :-
    1) 2021.2 - Vivado - SDK - Vitis - PetaLinux
    0) Exit
    Please select tools required or exit : 1
    
    Tools are as follows :-
    vivado @ /opt/Xilinx/Vivado/2021.2/bin/vivado
    vitis @ /opt/Xilinx/Vitis/2021.2/bin/vitis
    petalinux-build @ /opt/Xilinx/PetaLinux/2021.2/tool/tools/common/petalinux/bin/petalinux-build
    

    2. Change present working directory

    Change the present working directory to be the project directory.
    steve@Desktop:~$ cd ~/projects/zedboard_leds_switches
    
    #### Part 2 - Firmware Development ####

    3. Launch Vivado & open project

    Launch Vivado quietly from a Terminal.
    steve@Desktop:~/projects/zedboard_leds_switches$ vivado -nojournal -nolog -notrace fw/vivado/project.xpr &
    

    4. Create AXI4-Lite peripheral (HDL)

    Add a new HDL source file to Vivado by right clicking on Design Sources within Sources and selecting Add Sources... from the context menu. Missing Image! In the Add Sources dialog select Add or create design sources and then click Next. Missing Image! In the Add Sources - Add or Create Design Sources dialog click on Create File. Missing Image! In the Create Source File dialog set File type to Verilog, File name to axi_identification and File location to ~/projects/zedboard_leds_switches/fw/src/design. Click OK to continue. Missing Image! In the Add Sources - Add or Create Design Sources dialog click Finish. Missing Image! In the Define Module dialog click OK to continue. Missing Image! Edit the newly added axi_identication by double clicking on it within Sources. Missing Image! Using the AXI4 peripheral generated in the previous tutorial as a starting point a new AXI4 peripheral can be created containing registers that hold textural information. No write access to the registers is required but the write logic is left in to make the AXI interface complete (most of it will be optimised away). Modify axi_identication.v and replace its stub code with something similar to the following.

    axi_identification.v

    //
    // File .......... axi_identification.v
    // Author ........ Steve Haywood
    // Version ....... 1.0
    // Date .......... 5 December 2021
    // Standard ...... IEEE 1364-2001
    // Description ...
    //   AXI4-Lite controlled register bank written in basic Verilog. The register
    // bank contains the following fixed length identification and timestamp
    // strings.
    //
    // Product Description ........ 128 Characters - 32 Registers - 0x000 - 0x07F
    // Company ....................  64 Characters - 16 Registers - 0x080 - 0x0BF
    // Author .....................  64 Characters - 16 Registers - 0x0C0 - 0x0FF
    // Product Version ............  16 Characters -  4 Registers - 0x100 - 0x10F
    // Firmware Build Timestamp ...  32 Characters -  8 Registers - 0x110 - 0x12F
    // Unused ..................... 208 Characters - 52 Registers - 0x130 - 0x1FF
    //                              ---              ---            -------------
    //                              512              128          - 0x000 - 0x1FF
    //
    
    
    `timescale 1 ns / 1 ps
    
    
    module axi_identification #
     (
      // Parameters (AXI4-Lite)
      localparam c_addr_w           = 9,                       // P:Address Bus Width (bits)
      localparam c_data_w           = 32,                      // P:Data Bus Width (bits)
      // Parameters (ID Strings)
      localparam c_id_description_w = 128,                     // P:Description Width (chars)
      localparam c_id_company_w     = 64,                      // P:Company Width (chars)
      localparam c_id_author_w      = 64,                      // P:Author Width (chars)
      localparam c_id_version_w     = 16,                      // P:Version Width (chars)
      localparam c_id_timestamp_w   = 32                       // P:Timestamp Width (chars)
     )
     (
      // Clock & Reset
      input  wire                             aclk,            // I:Clock
      input  wire                             aresetn,         // I:Reset
      // Identification Strings
      input  wire [8*c_id_description_w-1 :0] id_description,  // I:Product Description
      input  wire [8*c_id_company_w-1 : 0]    id_company,      // I:Company
      input  wire [8*c_id_author_w-1 : 0]     id_author,       // I:Author
      input  wire [8*c_id_version_w-1 : 0]    id_version,      // I:Product Version
      input  wire [8*c_id_timestamp_w-1 : 0]  id_timestamp,    // I:Firmware Build Timestamp
      // AXI4-Lite - Write Address Channel
      input  wire [c_addr_w-1 : 0]            s_axi_awaddr,    // I:Write Address
      input  wire [2 : 0]                     s_axi_awprot,    // I:Write Protection Type
      input  wire                             s_axi_awvalid,   // I:Write Address Valid
      output reg                              s_axi_awready,   // O:Write Address Ready
      // AXI4-Lite - Write Data Channel
      input  wire [c_data_w-1 : 0]            s_axi_wdata,     // I:Write Data
      input  wire [(c_data_w/8)-1 : 0]        s_axi_wstrb,     // I:Write Strobes
      input  wire                             s_axi_wvalid,    // I:Write Valid
      output reg                              s_axi_wready,    // O:Write Ready
      // AXI4-Lite - Write Response Channel
      output reg  [1 : 0]                     s_axi_bresp,     // O:Write Response
      output reg                              s_axi_bvalid,    // O:Write Response Valid
      input  wire                             s_axi_bready,    // I:Write Response Ready
      // AXI4-Lite - Read Address Channel
      input  wire [c_addr_w-1 : 0]            s_axi_araddr,    // I:Read Address
      input  wire [2 : 0]                     s_axi_arprot,    // I:Read Protection Type
      input  wire                             s_axi_arvalid,   // I:Read Address Valid
      output reg                              s_axi_arready,   // O:Read Address Ready
      // AXI4-Lite - Read Data Channel
      output reg  [c_data_w-1 : 0]            s_axi_rdata,     // O:Read Data
      output reg  [1 : 0]                     s_axi_rresp,     // O:Read Response
      output reg                              s_axi_rvalid,    // O:Read Valid
      input  wire                             s_axi_rready     // I:Read Ready
     );
    
    
      // Constants
      localparam no_regs = 2**(c_addr_w - $clog2(c_data_w / 8));
      localparam id_description_lo = 0;
      localparam id_description_hi = id_description_lo + (c_id_description_w / 4) - 1;
      localparam id_company_lo     = 1 + id_description_hi;
      localparam id_company_hi     = id_company_lo + (c_id_company_w / 4) - 1;
      localparam id_author_lo      = 1 + id_company_hi;
      localparam id_author_hi      = id_author_lo + (c_id_author_w / 4) -1;
      localparam id_version_lo     = 1 + id_author_hi;
      localparam id_version_hi     = id_version_lo + (c_id_version_w / 4) - 1;
      localparam id_timestamp_lo   = 1 + id_version_hi;
      localparam id_timestamp_hi   = id_timestamp_lo + (c_id_timestamp_w / 4) - 1;
    
    
      // Signals
      reg aw_en;
      reg [c_addr_w-$clog2(c_data_w / 8) - 1 : 0] axi_awaddr;
      reg [c_addr_w-$clog2(c_data_w / 8) - 1 : 0] axi_araddr;
      reg [c_data_w-1 : 0] registers_wr [no_regs - 1 : 0];
      wire [c_data_w-1 : 0] registers_rd [no_regs - 1 : 0];
    
    
      // Write Address Ready
      always @(posedge aclk)
        if (!aresetn) begin
          s_axi_awready <= 1'b0;
          aw_en <= 1'b1;
        end else if (s_axi_awvalid && !s_axi_awready && s_axi_wvalid && aw_en) begin
          s_axi_awready <= 1'b1;
          aw_en <= 1'b0;
        end else if (s_axi_bvalid && s_axi_bready) begin
          s_axi_awready <= 1'b0;
          aw_en <= 1'b1;
        end else
          s_axi_awready <= 1'b0;
    
    
      // Write Address Latch
      always @(posedge aclk)
        if (!aresetn)
          axi_awaddr <= {c_addr_w{1'b0}};
        else if (s_axi_awvalid && !s_axi_awready && s_axi_wvalid && aw_en)
          axi_awaddr <= s_axi_awaddr[c_addr_w - 1 : $clog2(c_data_w/8)];
    
    
      // Write Data Ready
      always @(posedge aclk)
        if (!aresetn)
          s_axi_wready <= 1'b0;
        else if (s_axi_awvalid && s_axi_wvalid && !s_axi_wready && aw_en)
          s_axi_wready <= 1'b1;
        else
          s_axi_wready <= 1'b0;
    
    
      // Write Data
      integer i;
      always @(posedge aclk)
        if (!aresetn)
          for (i = 0; i <= no_regs - 1; i = i + 1)
            registers_wr[i] <= {c_data_w{1'b0}};
        else if (s_axi_awvalid && s_axi_awready && s_axi_wvalid && s_axi_wready)
          for (i = 0; i <= c_data_w/8 - 1; i = i + 1)
            if (s_axi_wstrb[i])
              registers_wr[axi_awaddr][i*8 +: 8] <= s_axi_wdata[i*8 +: 8]; // Byte-Wise Write
    
    
      // Write Response
      always @(posedge aclk)
        if (!aresetn) begin
          s_axi_bvalid <= 1'b0;
          s_axi_bresp  <= 2'b00;
        end else if (s_axi_awvalid && s_axi_awready && s_axi_wvalid && s_axi_wready && !s_axi_bvalid) begin
          s_axi_bvalid <= 1'b1;
          s_axi_bresp  <= 2'b00;
        end else if (s_axi_bvalid && s_axi_bready)
          s_axi_bvalid <= 1'b0;
    
    
      // Read Address Ready
      always @(posedge aclk)
        if (!aresetn)
          s_axi_arready <= 1'b0;
        else if (s_axi_arvalid && !s_axi_arready)
          s_axi_arready <= 1'b1;
        else
          s_axi_arready <= 1'b0;
    
    
      // Read Address Latch
      always @(posedge aclk)
        if (!aresetn)
          axi_araddr  <= {c_addr_w{1'b0}};
        else if (s_axi_arvalid && !s_axi_arready)
          axi_araddr <= s_axi_araddr[c_addr_w - 1 : $clog2(c_data_w/8)];
    
    
      // Read Response
      always @(posedge aclk)
        if (!aresetn) begin
          s_axi_rvalid <= 1'b0;
          s_axi_rresp  <= 2'b00;
        end else if (s_axi_arvalid && s_axi_arready && !s_axi_rvalid) begin
          s_axi_rvalid <= 1'b1;
          s_axi_rresp  <= 2'b00;
        end else if (s_axi_rvalid && s_axi_rready)
          s_axi_rvalid <= 1'b0;
    
    
      // Read Data
      always @(posedge aclk)
        if (!aresetn)
          s_axi_rdata <= {c_data_w{1'b0}};
        else if (s_axi_arvalid && s_axi_arready && !s_axi_rvalid)
          s_axi_rdata <= registers_rd[axi_araddr];
    
    
      // Read Register Constants
      genvar j;
      generate
        for (j = 0; j <= no_regs - 1; j = j + 1) begin
          if      (j <= id_description_hi) assign registers_rd[j] = id_description[32*(j-id_description_lo) +: 32];
          else if (j <= id_company_hi    ) assign registers_rd[j] = id_company    [32*(j-id_company_lo    ) +: 32];
          else if (j <= id_author_hi     ) assign registers_rd[j] = id_author     [32*(j-id_author_lo     ) +: 32];
          else if (j <= id_version_hi    ) assign registers_rd[j] = id_version    [32*(j-id_version_lo    ) +: 32];
          else if (j <= id_timestamp_hi  ) assign registers_rd[j] = id_timestamp  [32*(j-id_timestamp_lo  ) +: 32];
          else                             assign registers_rd[j] = 32'h00000000;
        end
      endgenerate
    
    
    endmodule 
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/src/design/axi_identification.v -O fw/src/design/axi_identification.v
    

    5. Open block design

    Open the existing block design by clicking on Open Block Design under IP INTEGRATOR. Get a better view of the Block Design by clicking on its Float Missing Image! icon. Missing Image!

    6. Re-customize AXI GPIO

    Double click on the AXI GPIO module to begin re-customization. Change the Default Output Value of GPIO to 0x00000082 so the updated PL design is easily identifiable from the previous one. Click OK to commit the changes. Missing Image!

    7. Add peripheral to block design

    Add the newly created peripheral to the Block Design by dragging it from Sources and dropping it in Diagram. Note the reason Verilog is being used for this and not System Verilog is because this operation is currently not supported for System Verilog. Missing Image! Connect the Register Bank to the rest of the system by clicking on Run Connection Automation.

    In the Run Connection Automation dialog tick All Automation and click OK to proceed. Missing Image! Make the ID signals external to the Block Design by highlighting them and selecting Make External from the right mouse context menu. Note the reason signals are being used and not parameters is because passing parameters into a Block Design is currently not supported. Missing Image! Rename the external signals to remove the _0 from their names. Missing Image! Verify the block design is error free by clicking on the Validate Design Missing Image! icon. All being well the Validation successful dialog should now appear.

    8. Edit address map

    Check the address map assignment inside the Address Editor under the BLOCK DESIGN section. The axi_identification_0 module should have a Master Base Address of 0x43C1_0000 and a range of 64K. Change the Master Base Address to 0x4000_0000and leave the Range set to 64K. The product identification will now reside at the very start of the PL address space. Missing Image! Save the Block Design.

    9. Create, move & modify HDL wrapper

    The HDL wrapper must now be hand edited so the ID information can be provided using parameters, this means the wrapper can no longer be managed automatically by Vivado.

    Create an updated wrapper to include the new ID signals by right clicking on the system_i block design inside Sources and selecting Create HDL Wrapper... from the context menu.

    In the Create HDL Wrapper dialog select Copy generated wrapper to allow user edits and then click OK. Missing Image! Copy the generated wrapper into the user area outside of Vivado and rename it such that it becomes a System Verilog source.
    steve@Desktop:~/projects/zedboard_leds_switches$ cp fw/vivado/project.srcs/sources_1/imports/hdl/system_wrapper.v fw/src/diagram/system/hdl/system_wrapper.sv
    
    Remove the generated wrapper from Vivado by right clicking on system_wrapper inside Sources and selecting Remove File from Project... from the context menu.

    In the Remove Sources dialog select Also delete the project local file/directory from disk and then click OK to continue. Missing Image! Add system_wrapper.sv back into the project from its new location /home/steve/projets/zedboard_leds_switches/fw/src/diagram/system/hdl.

    Edit the wrapper file as follows:- The resulting source should look similar to the following...

    system_wrapper.sv

    //
    // File .......... system_wrapper.sv
    // Author ........ Steve Haywood
    // Version ....... 1.0
    // Date .......... 6 December 2021
    // Standard ...... IEEE 1800-2017
    // Description ...
    //   Top level wrapper for the underlying block design.
    //
    
    
    timeunit      1ns;
    timeprecision 1ps; 
    
    
    module system_wrapper #
     ( 
      // Parameters
      parameter string id_description = "",  // P:Firmware Description ... Max 128 Characters
      parameter string id_company     = "",  // P:Company ................ Max  64 Characters
      parameter string id_author      = "",  // P:Author ................. Max  64 Characters
      parameter string id_version     = "",  // P:Version Number ......... Max  16 Characters
      parameter string id_timestamp   = ""   // P:Build Timestamp ........ Max  32 Characters
     )
     (
      // LEDs
      output [ 7:0] leds_tri_o,         // O:LEDs
      // Switches
      input  [ 7:0] switches_tri_i,     // I:Switches
      // System
      inout  [14:0] DDR_addr,           // B:Address
      inout  [ 2:0] DDR_ba,             // B:Bank Address
      inout         DDR_cas_n,          // B:Column Address Select
      inout         DDR_ck_n,           // B:Clock (Neg)
      inout         DDR_ck_p,           // B:Clock (Pos)
      inout         DDR_cke,            // B:Clock Enable
      inout         DDR_cs_n,           // B:Chip Select
      inout  [ 3:0] DDR_dm,             // B:Data Mask
      inout  [31:0] DDR_dq,             // B:Data Input/Output
      inout  [ 3:0] DDR_dqs_n,          // B:Data Strobe (Neg)
      inout  [ 3:0] DDR_dqs_p,          // B:Data Strobe (Pos)
      inout         DDR_odt,            // B:Output Dynamic Termination
      inout         DDR_ras_n,          // B:Row Address Select
      inout         DDR_reset_n,        // B:Reset
      inout         DDR_we_n,           // B:Write Enable
      inout         FIXED_IO_ddr_vrn,   // B:Termination Voltage
      inout         FIXED_IO_ddr_vrp,   // B:Termination Voltage
      inout  [53:0] FIXED_IO_mio,       // B:Peripheral Input/Output
      inout         FIXED_IO_ps_clk,    // B:System Reference Clock
      inout         FIXED_IO_ps_porb,   // B:Power On Reset
      inout         FIXED_IO_ps_srstb   // B:External System Reset
    );
    
    
      // Function to convert a string to a vector (max 128 characters)
      function [1023:0] fmt(input string str);
        int len;
        bit [1023:0] tmp;
        len = str.len();
        for (int i=0; i<len; i++)
          tmp[8*i +: 8] = str.getc(i);
        fmt = tmp;
      endfunction
    
    
      // Top-Level Block Design
      system system_i
       (
        // Identification Strings
        .id_description    ( fmt(id_description) ),  // P:Firmware Description
        .id_company        ( fmt(id_company)     ),  // P:Company
        .id_author         ( fmt(id_author)      ),  // P:Author
        .id_version        ( fmt(id_version)     ),  // P:Version Number
        .id_timestamp      ( fmt(id_timestamp)   ),  // P:Build Timestamp
        // LEDs
        .leds_tri_o        ( leds_tri_o          ),  // O:LEDs
        // Switches
        .switches_tri_i    ( switches_tri_i      ),  // I:Switches
        // System
        .DDR_addr          ( DDR_addr            ),  // B:Address
        .DDR_ba            ( DDR_ba              ),  // B:Bank Address
        .DDR_cas_n         ( DDR_cas_n           ),  // B:Column Address Select
        .DDR_ck_n          ( DDR_ck_n            ),  // B:Clock (Neg)
        .DDR_ck_p          ( DDR_ck_p            ),  // B:Clock (Pos)
        .DDR_cke           ( DDR_cke             ),  // B:Clock Enable
        .DDR_cs_n          ( DDR_cs_n            ),  // B:Chip Select
        .DDR_dm            ( DDR_dm              ),  // B:Data Mask
        .DDR_dq            ( DDR_dq              ),  // B:Data Input/Output
        .DDR_dqs_n         ( DDR_dqs_n           ),  // B:Data Strobe (Neg)
        .DDR_dqs_p         ( DDR_dqs_p           ),  // B:Data Strobe (Pos)
        .DDR_odt           ( DDR_odt             ),  // B:Output Dynamic Termination
        .DDR_ras_n         ( DDR_ras_n           ),  // B:Row Address Select
        .DDR_reset_n       ( DDR_reset_n         ),  // B:Reset
        .DDR_we_n          ( DDR_we_n            ),  // B:Write Enable
        .FIXED_IO_ddr_vrn  ( FIXED_IO_ddr_vrn    ),  // B:Termination Voltage
        .FIXED_IO_ddr_vrp  ( FIXED_IO_ddr_vrp    ),  // B:Termination Voltage
        .FIXED_IO_mio      ( FIXED_IO_mio        ),  // B:Peripheral Input/Output
        .FIXED_IO_ps_clk   ( FIXED_IO_ps_clk     ),  // B:System Reference Clock
        .FIXED_IO_ps_porb  ( FIXED_IO_ps_porb    ),  // B:Power On Reset
        .FIXED_IO_ps_srstb ( FIXED_IO_ps_srstb   )   // B:External System Reset
       );
    
    
    endmodule
    
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/src/diagram/system/hdl/system_wrapper.sv -O fw/src/diagram/system/hdl/system_wrapper.sv
    

    10. Create build hook scripts

    To pass textural information into the parameters of system_wrapper.sv a pre-synthesis TCL script can be used. Versioning and timestamping can also be achieved using this script. Care must be taken when a build fails and a bitstream isn't generated not to leave a version/revision missing from the build history. A post-bitstream TCL script can be used to assist with the missing version/revision problem. Note there are very many ways to achieve versioning with fallback, most of which are dependant on how revision control is used with the build process and committing of source & bitstream. The approach used here shall be a product identification file containing information pertinent to a successfully created bitstream. After a successful build the product identification file along with the new bitstream could be committed into revision control. If source is committed into revision control prior to a build being undertaken a tag from the commit could be included in the product identification file & bitstream to link the build back to the version of the source used to create it.

    Create an initial product identification file and edit it to include the product information fields. Note the version will be incremented during the build process so this is set to 0.0 initially ready for it becoming 1.0. The timestamp will be overwritten so this can be set to anything.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/project.txt
    

    project.txt

    Zedboard LEDs & Switches Example Design
    SpaceWire UK
    Steve Haywood
    1.0
    10-Jan-2022 - 17:02:03
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/project.txt -O fw/project.txt
    
    Create pre-synthesis & post-bitstream TCL scripts and edit to include the required code.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/src/script/pre_synth.tcl
    

    pre_synth.tcl

    #
    # File .......... pre_synth.tcl
    # Author ........ Steve Haywood
    # Version ....... 1.0
    # Date .......... 24 October 2021
    # Description ...
    #   Script to read product/project information from a text file, increase
    # the version/revision, get current timestamp and set the generics/parameters
    # on the top level module.
    #
    
    # Read Product/Project Information
    set id_file [open "../../../project.txt" r]
    gets $id_file id_description
    gets $id_file id_company
    gets $id_file id_author
    gets $id_file id_version
    gets $id_file id_timestamp
    close $id_file
    
    # Increase Version
    set id_version [expr $id_version+1]
    
    # Increase Revision
    #set id_version [expr $id_version+0.1]
    
    # Get Timestamp
    set id_timestamp "[clock format [clock seconds] -format {%d-%b-%Y - %H:%M:%S}]"
    
    # Write New Product/Project Information
    set id_file [open "../../../project_new.txt" w]
    puts $id_file $id_description
    puts $id_file $id_company
    puts $id_file $id_author
    puts $id_file $id_version
    puts $id_file $id_timestamp
    close $id_file
    
    # Replace space character with its octal code
    proc space_replace {str} {
      return [string map {" " "\\040"} $str]
    }
    
    # Replace spaces in information strings
    set id_description [space_replace $id_description]
    set id_company     [space_replace $id_company]
    set id_author      [space_replace $id_author]
    set id_version     [space_replace $id_version]
    set id_timestamp   [space_replace $id_timestamp]
    
    # Set Generics/Parameters
    set_property generic " \
      id_description=\"$id_description\" \
      id_company=\"$id_company\" \
      id_author=\"$id_author\" \
      id_version=\"$id_version\" \
      id_timestamp=\"$id_timestamp\" \
    " [current_fileset]
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/src/script/pre_synth.tcl -O fw/src/script/pre_synth.tcl
    
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/src/script/post_bit.tcl
    

    post_bit.tcl

    #
    # File .......... post_bit.tcl
    # Author ........ Steve Haywood
    # Version ....... 1.0
    # Date .......... 24 October 2021
    # Description ...
    #   Script to replace 'project.txt' (Information about last version of firmware)
    # with 'project_new.txt' (Information about new version of firmware). The file
    # replace only occurs if the bitstream was generated successfully.
    #
    
    file delete ../../../project.txt
    file rename ../../../project_new.txt ../../../project.txt
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/src/script/post_bit.tcl -O fw/src/script/post_bit.tcl
    
    Add the pre-synthesis & post-bitstream hooks into Vivado's build process by navigating Tools » Settings....

    In the Settings dialog select Synthesis under Project Settings and click the ... button across from tcl.pre to select the pre-synthesis file. Missing Image! In the Select A Tcl Script dialog select New script and set this to /home/steve/projets/zedboard_leds_switches/fw/src/script/pre_synth.tcl. Click OK to continue. Missing Image! In the Settings dialog select Bitstream under Project Settings and click the ... button across from tcl.post to select the post-bitstream file. Missing Image! In the Select A Tcl Script dialog select New script and set this to /home/steve/projets/zedboard_leds_switches/fw/src/script/post_bit.tcl. Click OK to continue. Missing Image!

    11. Update testbench

    Update the simple testbench to include the new functionality.

    Details to follow...
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/src/testbench/testbench.sv
    

    testbench.sv

    //
    // File .......... testbench.sv
    // Author ........ Steve Haywood
    // Version ....... 3.0
    // Date .......... 22 December 2021
    // Description ...
    //   Very simple testbench to check correct connectivity of the block diagram.
    // Uses the Zynq Verification IP suite to read/write the GPIO LED register and
    // read the GPIO switch register.
    // 
    
    
    timeunit      1ns;
    timeprecision 1ps;
    
    
    module testbench;
    
      // Constants
      localparam [31:0] led_addr = 32'h41200000;
      localparam [31:0] sws_addr = 32'h41200008;
      localparam [31:0] led_init = 32'h00000018;
      localparam [31:0] led_next = 32'h00000081;
      localparam [ 7:0] sws_next =  8'b11000011;
      localparam [31:0] reg_addr = 32'h43C00000;
    
    
      // Signals
      wire       ps_clk;
      wire       ps_porb;
      wire       ps_srstb;
      bit [ 7:0] leds_8bits;
      bit [ 7:0] sws_8bits;
      bit        clk;
      bit        resetn;
      bit        bresp;
      bit [31:0] rdata;
      bit        rresp;
    
    
      // 50MHz Clock
      always #10 clk = !clk;
    
    
      // Reset
      initial begin
        repeat(20)
          @(posedge clk);
        resetn = 1'b1;
      end
    
    
      // Drive PL Clock & Reset
      assign ps_clk   = clk;
      assign ps_porb  = resetn;
      assign ps_srstb = resetn;
    
    
      // Stimulus
      initial begin
        // Allow reset time to work
    
        @(posedge resetn);
        repeat(10)
          @(posedge clk);
    
        // Reset PL
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.fpga_soft_reset(32'h1);
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.fpga_soft_reset(32'h0);
    
        // Read GPIO LED Register
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.read_data(led_addr, 4, rdata, rresp);
        $display ("Read of GPIO LED Register = 32'h%x (%s)", rdata, (rdata == led_init) ? "expected" : "unexpected");
    
        // Write GPIO LED Register
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.write_data(led_addr, 4, led_next, bresp);
    
        // Read GPIO LED Register
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.read_data(led_addr, 4, rdata, rresp);
        $display ("Read of GPIO LED Register = 32'h%x (%s)", rdata, (rdata == led_next) ? "expected" : "unexpected");
    
        // Set some of the switches to 'on'
        sws_8bits = sws_next;
    
        // Read GPIO Switch Register
        testbench.system_wrapper_i.system_i.processing_system7_0.inst.read_data(sws_addr, 4, rdata, rresp);
        $display ("Read of GPIO Switch Register = 32'h%x (%s)", rdata, (rdata == sws_next) ? "expected" : "unexpected");
    
        // Write Register Bank Registers
        for (int i = 0; i < 'h10; i = i + 4)
          testbench.system_wrapper_i.system_i.processing_system7_0.inst.write_data(reg_addr + i, 4, 65536 + i, bresp);
    
        // Read Register Bank Register
        for (int i = 0; i < 'h10; i = i + 4) begin
          testbench.system_wrapper_i.system_i.processing_system7_0.inst.read_data(reg_addr, 4, rdata, rresp);
          $display ("Read of GPIO LED Register = 32'h%x (%s)", rdata, (rdata == 65536 + i) ? "expected" : "unexpected");
        end
    
        // All done
        $display ("That's all folks!");
        $stop;
    
      end
    
    
      // Unit Under Test
      system_wrapper #
       (
        // Parameters
        .id_description ( "Description" ),  // P:Firmware Description ... Max 128 Characters
        .id_company     ( "Company"     ),  // P:Company ................ Max  64 Characters
        .id_author      ( "Author"      ),  // P:Author ................. Max  64 Characters
        .id_version     ( "Version"     ),  // P:Version Number ......... Max  16 Characters
        .id_timestamp   ( "Timestamp"   )   // P:Build Timestamp ........ Max  32 Characters
       )
      system_wrapper_i
       (
        // LEDs
        .leds_tri_o        ( leds_8bits ),  // O:LEDs
        // Switches
        .switches_tri_i    ( sws_8bits  ),  // I:Switches
        // System
        .DDR_addr          (            ),  // B:Address
        .DDR_ba            (            ),  // B:Bank Address
        .DDR_cas_n         (            ),  // B:Column Address Select
        .DDR_ck_n          (            ),  // B:Clock (Neg)
        .DDR_ck_p          (            ),  // B:Clock (Pos)
        .DDR_cke           (            ),  // B:Clock Enable
        .DDR_cs_n          (            ),  // B:Chip Select
        .DDR_dm            (            ),  // B:Data Mask
        .DDR_dq            (            ),  // B:Data Input/Output
        .DDR_dqs_n         (            ),  // B:Data Strobe (Neg)
        .DDR_dqs_p         (            ),  // B:Data Strobe (Pos)
        .DDR_odt           (            ),  // B:Output Dynamic Termination
        .DDR_ras_n         (            ),  // B:Row Address Select
        .DDR_reset_n       (            ),  // B:Reset
        .DDR_we_n          (            ),  // B:Write Enable
        .FIXED_IO_ddr_vrn  (            ),  // B:Termination Voltage
        .FIXED_IO_ddr_vrp  (            ),  // B:Termination Voltage
        .FIXED_IO_mio      (            ),  // B:Peripheral Input/Output
        .FIXED_IO_ps_clk   ( ps_clk     ),  // B:System Reference Clock
        .FIXED_IO_ps_porb  ( ps_porb    ),  // B:Power On Reset
        .FIXED_IO_ps_srstb ( ps_srstb   )   // B:External System Reset
       );
    
    
    endmodule
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0016/zedboard_leds_switches/fw/src/testbench/testbench.sv -O fw/src/testbench/testbench.sv
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_leds_switches$ git difftool fw/src/testbench/testbench.sv
    

    12. Generate bitstream

    Generate the programmable logic bitstream by clicking on Generate Bitstream under the PROGRAM AND DEBUG heading inside the Flow Navigator section.Missing Image!

    13. Export hardware platform

    Export the hardware platform by selecting File » Export » Export Hardware... from the main menu.Missing Image!In the Export Hardware Platform dialog click Next.Missing Image!In the Output dialog check Include bitstream and click Next.Missing Image!In the Files dialog set the Export to field to ~/projects/zedboard_leds_switches/fw (by removing the last /vivado part of the path). Click Next.Missing Image!If the Module Already Exported dialog appears click Yes.Missing Image!In the Exporting Hardware Platform dialog click Fniish.Missing Image!
    #### Part 4 - Revision Control ####

    14. Commit new & updated files

    Add and commit the new & updated files, create annotated tags and push the commits & tags up to the remote repositories.
    steve@Desktop:~/projects/zedboard_leds_switches$ git add fw/src/design/axi_identification.v
    steve@Desktop:~/projects/zedboard_leds_switches$ git add fw/src/diagram/system/hdl/system_wrapper.sv
    steve@Desktop:~/projects/zedboard_leds_switches$ git add fw/project.txt
    steve@Desktop:~/projects/zedboard_leds_switches$ git add fw/src/script/pre_synth.tcl
    steve@Desktop:~/projects/zedboard_leds_switches$ git add fw/src/script/post_bit.tcl
    steve@Desktop:~/projects/zedboard_leds_switches$ git commit -a -m "Added AXI Identification to the design."
    steve@Desktop:~/projects/zedboard_leds_switches$ git push
    steve@Desktop:~/projects/zedboard_leds_switches$ git tag -a v3.0 -m "ZYNQ, GPIO, Register Bank & Identification"
    steve@Desktop:~/projects/zedboard_leds_switches$ git push origin v3.0
    
    #### Part 5 - OS Development ####

    15. Create peek string C application

    Using peek.c as a starting point create a new C application to perform the reading of a string from the PL address space.
    steve@Desktop:~/projects/zedboard_leds_switches$ cd ../zedboard_linux
    
    Something similar to the following should do the trick.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peekstring.c
    

    peekstring.c

    //
    // File .......... peekstring.c
    // Author ........ Steve Haywood
    // Version ....... 1.0
    // Date .......... 29 October 2021
    // Description ...
    //   CGI type module to read a string from memory and return it.
    // Usage ...
    //   http://<web address>/cgi-bin/peekstring?base&size&offset&maxchars
    // base = Base address of memory region to be used
    // size = Size of memory region to be used
    // offset = Offset address of string within memory region
    // maxchars = Maximum length of return string
    //
    
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <stdint.h>
    #include <unistd.h>
    
    
    int main()
    {
      int fd;              // File descriptor for mapped memory
      char *map;           // Pointer to mapped memory
      uint32_t base;       // Start address of area to map
      uint32_t size;       // Size of area to map
      uint32_t addr;       // Address within area to access
      uint32_t length;     // Maximum length of string to return
      char *ptr;           // Pointer to character of string
      char *querystring;   // Pointer to QUERY_STRING
      char *rest;          // Pointer to next token within QUERY_STRING
      char *token;         // Pointer to extracted token
      char *endptr = NULL; // Pointer to end of string in str2int conversion
      uint32_t count = 0;  // Number of character output from string
    
      printf("Content-Type: text/plain;charset=us-ascii\n\n");
    
      querystring = getenv("QUERY_STRING");
      if (querystring)
      {
        rest = querystring;
        token = strtok_r(rest, "&", &rest);
        if (token != NULL) {
          base = strtoul(token, &endptr, 0);
          if (token != endptr) {
            token = strtok_r(rest, "&", &rest);
            if (token != NULL) {
              size = strtoul(token, &endptr, 0);
              if (token != endptr) {
                token = strtok_r(rest, "&", &rest);
                if (token != NULL) {
                  addr = strtoul(token, &endptr, 0);
                  if (token != endptr) {
                    token = strtok_r(rest, "&", &rest);
                    if (token != NULL) {
                      length = strtoul(token, &endptr, 0);
                      if (token != endptr) {
                        fd = open( "/dev/mem", O_RDWR);
                        if (fd > 0) {
                          map = mmap(NULL, size, (PROT_READ | PROT_WRITE), MAP_SHARED, fd, base);
                          if (map != MAP_FAILED) {
                            ptr = (char *)(map + addr);
                            while (*ptr != '\0' && count < length) {
                              printf("%c", *ptr);
                              ptr++;
                              count++;
                            }
                            munmap(map, size);
                          } else printf("Error: Memory to mmap");
                          close(fd);
                        } else printf("Error: Failed to open /dev/mem");
                      } else printf("Error: Invalid string length");
                    } else printf("Error: Missing string length");
                  } else printf("Error: Invalid string offset address");
                } else printf("Error: Missing string offset address");
              } else printf("Error: Invalid memory map size");
            } else printf("Error: Missing memory map size");
          } else printf("Error: Invalid memory map base address");
        } else printf("Error: Missing memory map base address");
      } else printf("Error: No QUERY_STRING");
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0017/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peekstring.c -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peekstring.c
    
    Modify the Makefile to include the peekstring application.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
    

    Makefile

    PEEK = peek
    POKE = poke
    PEEKSTRING = peekstring
    
    # Add any other object files to this list below
    PEEK_OBJS = peek.o
    POKE_OBJS = poke.o
    PEEKSTRING_OBJS = peekstring.o
    
    all: $(PEEK) $(POKE) $(PEEKSTRING)
    
    $(PEEKSTRING): $(PEEKSTRING_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(PEEKSTRING_OBJS) $(LDLIBS)
    
    $(POKE): $(POKE_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(POKE_OBJS) $(LDLIBS)
    
    $(PEEK): $(PEEK_OBJS)
    	$(CC) $(LDFLAGS) -o $@ $(PEEK_OBJS) $(LDLIBS)
    
    clean:
    	-rm -f $(PEEKSTRING) $(POKE) $(PEEK) *.elf *.gdb *.o
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0017/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/Makefile
    
    Modify the BitBake recipe to include the peekstring application.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    

    peekpokecgi.bb

    #
    # This is the peekpokecgi aplication recipe
    #
    #
    
    SUMMARY = "peekpokecgi application"
    SECTION = "PETALINUX/apps"
    LICENSE = "MIT"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    SRC_URI = "file://peek.c \
               file://poke.c \
               file://peekstring.c \
               file://Makefile \
              "
    FILES_${PN} += "/srv/www/cgi-bin" 
    S = "${WORKDIR}"
    CFLAGS_prepend = "-I ${S}/include"
    do_compile() {
            oe_runmake
    }
    do_install() {
            install -d ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/peek ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/poke ${D}/srv/www/cgi-bin
            install -m 0755 ${S}/peekstring ${D}/srv/www/cgi-bin
    }
    
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0017/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb -O os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/peekpokecgi.bb
    

    16. Update website to display ID strings

    Edit the existing HTML to have a Product Identification section, this will include a table of information and an update button that calls a Javascript function to perform the webpage update. Lines 33-62 illustrate the potential code required.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi
    

    index.cgi

    #!/bin/sh
    
    # Output Header
    printf "Content-type: text/html\n\n"
    
    # Get information
    sys_host=$(hostname)
    sys_time=$(date)
    sys_load=$(awk '{print $1}' /proc/loadavg)
    sys_up=$(awk '{print $1}' /proc/uptime)
    cpu_model=$(grep model /proc/cpuinfo | cut -d : -f2 | tail -1 | sed 's/\s//')
    cpu_cores=$(grep -c ^processor /proc/cpuinfo)
    mem_total=$(free -m | awk 'NR==2{print $2}')
    mem_used=$(free -m | awk 'NR==2{print $3}')
    mem_free=$(free -m | awk 'NR==2{print $4}')
    net_mac=$(cat /sys/class/net/eth0/address)
    net_ip_loc=$(ip a | grep inet | grep -vw lo | grep -v inet6 | cut -d \/ -f1 | sed 's/[^0-9\.]*//g')
    net_ip_ext=$(wget -q -O- http://ipecho.net/plain)
    
    # Output HTML
    cat <<EOF
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link href="../styles.css" rel="stylesheet">
    <title>Zedboard Webserver</title>
    </head>
    <body onload="add_register()">
    
    <div class="section"><h2>Zedboard Webserver</h2></div>
    
    <div class="section">
    <table>
    <thead>
    <tr><th colspan="3">Firmware Product Information (Read from Firmware) <input type="submit" value="Read ID" id="read_ids" onclick="read_ids()"></th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td style="text-align:right">Description :</td>
    <td style="text-align:left" id="id_0">Unknown</td>
    <td><img id="sid_0" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Company :</td>
    <td style="text-align:left" id="id_1">Unknown</td>
    <td><img id="sid_1" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Author :</td>
    <td style="text-align:left" id="id_2">Unknown</td>
    <td><img id="sid_2" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Build Version :</td>
    <td style="text-align:left" id="id_3">Unknown</td>
    <td><img id="sid_3" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Build Timestamp :</td>
    <td style="text-align:left" id="id_4">Unknown</td>
    <td><img id="sid_4" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    </tbody>
    </table>
    </div>
    
    <div class="section"><img src="../zedboard.png" alt="Missing Image!"></div>
    
    <div class="section">
    
    <table>
    <tr><th colspan="2">System</th></tr>
    <tr><td>Hostname</td>
    <td>${sys_host}</td>
    </tr><tr><td>Time</td><td>${sys_time}</td></tr>
    <tr><td>Uptime</td><td><span id="uptime_text">${sys_up}</span> seconds <button onclick="get_uptime()">Refresh</button> Auto :
    <select id="uptime" onchange="uptime();">
      <option value="0">Off</option>
      <option value="1">1s</option>
      <option value="5">5s</option>
    </select>
    </td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">CPU</th></tr>
    <tr><td>Model</td>
    <td>${cpu_model}</td></tr>
    <tr><td>Cores</td><td>${cpu_cores}</td></tr>
    <tr><td>Load</td><td>${sys_load}</td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">Memory</th></tr>
    <tr><td>Total</td><td>${mem_total} Mb</td></tr>
    <tr><td>Used</td><td>${mem_used} Mb</td></tr>
    <tr><td>Free</td><td>${mem_free} Mb</td></tr>
    </table>
    
    <table>
    <tr><th colspan="2">Network</th></tr>
    <tr><td>MAC Address</td><td>${net_mac}</td></tr>
    <tr><td>Internal IP</td><td>${net_ip_loc}</td></tr>
    <tr><td>External IP</td><td>${net_ip_ext}</td></tr>
    </table>
    
    </div>
    
    <div class="section">
    <table id="registers">
    <tr>
    <th>Address</th>
    <th>Peek Value</th>
    <th>Sel</th>
    <th>Peek</th>
    <th>Status</th>
    <th>Copy</th>
    <th>Poke Value</th>
    <th>Sel</th>
    <th>Poke</th>
    <th>Status</th>
    <th>Description</th>
    </tr>
    </table>
    <br><br>
    <input title="Add new row to end of address table" type="button" value="Add" onclick="add_row()">
    <select title="Set type of row to add to address table" id="type">
      <option value="0">Register</option>
      <option value="1">Section</option>
    </select>
    <input title="Remove last address from table" type="button" value="Remove" onclick="rem_register()">
    <input title="Peek all selected addresses in table" type="button" value="Peek All" onclick="peek_all()">
    <input title="Copy all table peek values into poke values" type="button" value="Copy All" onclick="copy_all()">
    <input title="Poke all selected addresses in table" type="button" value="Poke All" onclick="poke_all()">
    Peek Refresh :
    <select title="Set timer interval for automatic peek of table addresses" id="timer" onchange="timer()">
      <option value="0">Off</option>
      <option value="1">1s</option>
      <option value="5">5s</option>
    </select>
    Number Format :
    <select title="Set number format for peek and poke values" id="format" onchange="format()">
      <option value="0">Hexadecimal</option>
      <option value="1">Unsigned</option>
    </select>
    Configuration :
    <button title="Create configuration file from table" onclick="create_config()">Create...</button> <a title="Right click and Save Link As... to locate and rename this file" download="config.txt" id="download" href="" style="display: none">config.txt</a>
    <input title="Read configuration file into table" type="file" id="load_config">
    </div>
    
    <div class="section">Designed by Steve Haywood @ 2021</div>
    
    <script src="../uptime.js"></script>
    
    </body>
    </html>
    EOF
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0017/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi
    

    17. Update Javascript to peek ID strings

    Edit the existing Javascript to add the functions required to support reading strings from the PL address space and dynamically updating the the webpage accordingly. Lines 5-32 illustrate the potential code required.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
    

    uptime.js

    // Requests
    var timer_uptime;
    var timer_peek_all;
    
    // Peek all strings
    function read_ids() {
      read_id(0x000, 0);
      read_id(0x080, 1);
      read_id(0x0C0, 2);
      read_id(0x100, 3);
      read_id(0x110, 4);
    }
    
    // Peek string & display result
    function read_id(offset, reg) {
      var url = "/cgi-bin/peekstring?0x40000000&4096&" + offset + "&128";
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("sid_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../red.png?" + now;
              img_obj.title = "Last peekstring failed : " + respText.substr(7);
            } else {
              const now = Date.now();
              img_obj.src = "../green.gif?" + now;
              img_obj.title = "Last peekstring successful";
              document.getElementById("id_" + reg).innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Get uptime
    function get_uptime(reg) {
      var url = "cgi-bin/uptime.cgi";
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            txtObj = document.getElementById("uptime_text");
            if (txtObj) {
              txtObj.innerHTML = respText;
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Update uptime timer
    function uptime() {
      clearInterval(timer_uptime);
      var uptime = document.getElementById("uptime");
      var interval = uptime.value;
      if (interval > 0) {
        timer_uptime = setInterval("get_uptime()", 1000 * interval);
      }
    }
    
    // Update peek_all timer
    function timer() {
      clearInterval(timer_peek_all);
      var timer = document.getElementById("timer");
      interval = timer.value;
      if (interval > 0) {
        timer_peek_all = setInterval("peek_all()", 1000 * interval);
      }
    }
    
    // Update number format
    function format() {
      var table = document.getElementById("registers");
      var rows = table.rows.length;
      var peek;
      var poke;
      for (var index = 0; index < rows; index++) {
        peek = document.getElementById("peek_" + index);
        if (peek) {
          peek.value = fmtUnsignedLong(parseInt(peek.value));
        }
        poke = document.getElementById("poke_" + index);
        if (poke) {
          poke.value = fmtUnsignedLong(parseInt(poke.value));
        }
      }
    }
    
    // Convert unsigned long to dec/hex string
    function fmtUnsignedLong(value) {
      var format = document.getElementById("format");
      var hexStr;
      if (format.value == 0) {
        hexStr = value.toString(16).toUpperCase();
        hexStr = "0x" + "00000000".substr(0, 8 - hexStr.length) + hexStr;
      } else {
        hexStr = value.toString(10);
      }
      return hexStr;
    }
    
    // Copy peek to poke
    function copy(reg) {
      var peek = document.getElementById("peek_" + reg);
      if (peek) {
        var poke = document.getElementById("poke_" + reg);
        if (poke) {
          poke.value = peek.value;
        }
      }
    }
    
    // Copy all peek to poke
    function copy_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        copy(index);
      }
    }
    
    // Peek address & display result
    function peek(reg) {
      var url = "/cgi-bin/peek?" + document.getElementById("addr_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("speek_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../red.gif?" + now;
              img_obj.title = "Last peek failed : " + respText.substr(7);
            } else {
              img_obj.src = "../green.gif?" + now;
              img_obj.title = "Last peek successful";
              document.getElementById("peek_" + reg).value = fmtUnsignedLong(parseInt(respText));
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Peek all selected addresses in section
    function peek_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("peek_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(row);
          }
        }
      } while (obj_sel);
    }
    
    // Peek all selected addresses in table
    function peek_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("peek_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            peek(index);
          }
        }
      }
    }
    
    // Poke address & display result
    function poke(reg) {
      var url = "/cgi-bin/poke?" + document.getElementById("addr_" + reg).value + "&" + document.getElementById("poke_" + reg).value;
      if (window.XMLHttpRequest) {
        var ajaxReq = new XMLHttpRequest();
        ajaxReq.onreadystatechange = function() {
          if (ajaxReq.readyState == 4 && ajaxReq.status == 200) {
            var respText = ajaxReq.responseText;
            var img_obj = document.getElementById("spoke_" + reg);
            // Unique number is added to image to avoid caching issues on separate animations
            const now = Date.now();
            if (respText.substr(0,6) == "Error:") {
              img_obj.src = "../red.gif?" + now;
              img_obj.title = "Last poke failed : " + respText.substr(7);
            } else {
              img_obj.src = "../green.gif?" + now;
              img_obj.title = "Last poke successful";
            }
          }
        }
        ajaxReq.open("POST", url, true);
        ajaxReq.send(null);
      }
    }
    
    // Poke all selected addresses in section
    function poke_section(row) {
      var obj_sel;
      do {
        row++;
        obj_sel = document.getElementById("poke_sel_" + row);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke(row);
          }
        }
      } while (obj_sel);
    }
    
    // Poke all selected addresses in table
    function poke_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_sel = document.getElementById("poke_sel_" + index);
        if (obj_sel) {
          if (obj_sel.checked) {
            poke(index);
          }
        }
      }
    }
    
    // Add row to table
    function add_row() {
      const obj_type = document.getElementById("type");
      const row_type = obj_type.value;
      switch(row_type) {
        case "0":
          add_register();
          break;
        case "1":
          add_section();
          break;
        default:
          break;
      }
    }
    
    // Add row to table
    function add_register(reg) {
      var table = document.getElementById("registers");
      var next = table.rows.length - 1;
      var row = table.insertRow(-1);
      var newcell;
      var addr = 0x41200000;
      if (next > 0) {
        const obj_addr = document.getElementById("addr_" + (next - 1));
        if (obj_addr) {
          addr = parseInt(document.getElementById("addr_" + (next - 1)).value) + 4;
        }
      }
      newcell = row.insertCell(0);
      newcell.innerHTML = '<input title="Address to peek/poke" type="text" id="addr_' + next + '" value="' + fmtUnsignedLong(addr) + '" size="10">';
      newcell = row.insertCell(1);
      newcell.innerHTML = '<input title="Value peeked at address" type="text" id="peek_' + next + '" value="0x00000000" size="10" readonly="readonly"></td>';
      newcell = row.insertCell(2);
      newcell.innerHTML = '<input title="Select address for peeking" type="checkbox" id="peek_sel_' + next + '" checked></td>';
      newcell = row.insertCell(3);
      newcell.innerHTML = '<input title="Peek address" type="submit" value="Peek" onclick="peek(' + next + ')">';
      newcell = row.insertCell(4);
      newcell.innerHTML = '<img title="Peek status" id="speek_' + next + '" style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';
      newcell = row.insertCell(5);
      newcell.innerHTML = '<input title="Copy peek value into poke value" type="submit" value=">>" onclick="copy(' + next + ')">';
      newcell = row.insertCell(6);
      newcell.innerHTML = '<input title="Value to poke at address" type="text" id="poke_' + next + '" value="0x00000000" size="10">';
      newcell = row.insertCell(7);
      newcell.innerHTML = '<input title="Select address for poking" type="checkbox" id="poke_sel_' + next + '" checked></td>';
      newcell = row.insertCell(8);
      newcell.innerHTML = '<input title="Poke address" type="submit" value="Poke" onclick="poke(' + next + ')">';
      newcell = row.insertCell(9);
      newcell.innerHTML = '<img title="Poke status" id="spoke_' + next + '" style="vertical-align:middle" src="../amber.gif" alt="Missing Image!">';
      newcell = row.insertCell(10);
      newcell.innerHTML = '<input title="Description of address" type="text" id="name_' + next + '" value="Register @ ' + fmtUnsignedLong(addr) + '" size="40">';
    }
    
    // Add group row to table
    function add_section(reg) {
      var table = document.getElementById("registers");
      var next = table.rows.length - 1;
      var row = table.insertRow(-1);
      var newcell;
      newcell = row.insertCell(0);
      newcell.colSpan = "3";
      newcell.innerHTML = "--- Section ---";
      newcell = row.insertCell(1);
      newcell.innerHTML = '<input title="Peek all selected addresses in section" type="submit" value="Peek" onclick="peek_section(' + next + ')">';
      newcell = row.insertCell(2);
      newcell.colSpan = "4";
      newcell.innerHTML = "--- Section ---";
      newcell = row.insertCell(3);
      newcell.innerHTML = '<input title="Poke all selected addresses in section" type="submit" value="Poke" onclick="poke_section(' + next + ')">';
      newcell = row.insertCell(4);
      newcell = row.insertCell(5);
      newcell.colSpan = "2";
      newcell.innerHTML = '<input title="Description of section" type="text" id="name_' + next + '" value="Section Description" size="40">';
    }
    
    // Remove row from table
    function rem_register(reg) {
      var table = document.getElementById("registers");
      if (table.rows.length > 1) {
        table.deleteRow(-1);
      }
    }
    
    // Remove all rows from table
    function remove_all() {
      var table = document.getElementById("registers");
      var rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        table.deleteRow(-1);
      }
    }
    
    // Note: browser file access is made difficult for security reasons - there maybe a better way of doing file read & write.
    
    var config_file = null;
    
    // Create virtual configuration file from address table for user to download
    function create_config() {
      var text = "";
      var table = document.getElementById("registers");
      const rows = table.rows.length - 1;
      for (var index = 0; index < rows; index++) {
        const obj_addr = document.getElementById("addr_" + index);
        if (obj_addr) { // Register type
          var addr = document.getElementById("addr_" + index).value;
          var peek_sel = document.getElementById("peek_sel_" + index).checked;
          var poke = document.getElementById("poke_" + index).value;
          var poke_sel = document.getElementById("poke_sel_" + index).checked;
          var name = document.getElementById("name_" + index).value;
          text += "reg" + "|" + addr + "|" + peek_sel + "|" + poke + "|" + poke_sel + "|"+ name + "\n";
        } else { // Section type
          var name = document.getElementById("name_" + index).value;
          text += "sec" + "|" + name + "\n";
        }
      }
      const data = new Blob([text], {type: 'text/plain'});
      if (config_file !== null) {
        URL.revokeObjectURL(config_file);
      }
      config_file = URL.createObjectURL(data);
      var link = document.getElementById('download');
      link.href = config_file;
      link.style.display = 'inline';
    }
    
    // Read configuration file and update address table
    function load_config(input) {
      var file = input.target.files[0];
      if (file) {
        var reader = new FileReader();
        reader.onload = function(input) {
          var contents = input.target.result;
          const lines = contents.split(/\r\n|\n/);
          remove_all();
          lines.forEach((line) => {
            if (line.length > 0) {
              var table = document.getElementById("registers");
              var next = table.rows.length - 1;
              const values = line.split("|");
              switch(values[0]) {
                case "reg":
                  add_register();
                  document.getElementById("addr_" + next).value = values[1];
                  if (values[2] == "false") {
                    document.getElementById("peek_sel_" + next).checked = false;
                  }
                  document.getElementById("poke_" + next).value = values[3];
                  if (values[4] == "false") {
                    document.getElementById("poke_sel_" + next).checked = false;
                  }
                  document.getElementById("name_" + next).value = values[5];
                  break;
                case "sec":
                  add_section();
                  document.getElementById("name_" + next).value = values[1];
                  break;
                default:
                  alert("Error: Unrecognized table type found (" + values[0] + "), ignoring.");
              }
            }
          });
        };
        reader.readAsText(file);
      }
    }
    
    document.getElementById('load_config').addEventListener('change', load_config, false);
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0017/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-apps/website/files/uptime.js
    
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux
    

    18. Update hardware platform

    Configure the PetaLinux project to use the exported hardware platform from the zedboard_leds_switches Vivado project.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-config --get-hw-description ../../../zedboard_leds_switches/fw/system_wrapper.xsa
    
    The configuration menu now appears.

    Select Save to save the configuration.Missing Image!Select Ok to confirm the save.Missing Image!Select Exit to continue.Missing Image!Select Exit to exit the menu.Missing Image!

    19. Build & package PetaLinux

    Rebuild PetaLinux to include the updates and package it ready for deployment.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-package --prebuilt --force
    
    #### Part 6 - Revision Control ####

    20. Commit new & updated files

    Add and commit the new & updated files, create annotated tags and push the commits & tags up to the remote repositories.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ cd ../..
    steve@Desktop:~/projects/zedboard_linux$ git add os/petalinux/project-spec/meta-user/recipes-apps/peekpokecgi/files/peekstring.c
    steve@Desktop:~/projects/zedboard_linux$ git commit -a -m "Enhanced webpage for website application to have access to product ID strings within PL address space."
    steve@Desktop:~/projects/zedboard_linux$ git push
    steve@Desktop:~/projects/zedboard_linux$ git tag -a v9.0 -m "PetaLinux, Peek/Poke, LED Runner, Webserver, Peek/Poke CGI, PL Access, Style Sheet, Register Bank & ID Strings with XSA from zedboard_leds_switches v3.0"
    steve@Desktop:~/projects/zedboard_linux$ git push origin v9.0
    steve@Desktop:~/projects/zedboard_linux$ cd os/petalinux
    
    #### Part 7 - Hardware Deployment ####

    21. Setup Zedboard hardware

    If not already, connect up the hardware as follows :-
    1. Xubuntu PC USB ⇄ Zedboard USB JTAG/Debug
    2. Xubuntu PC USB ⇄ Zedboard USB UART
    3. Zedboard Ethernet ⇄ Router
    4. Xubuntu PC Ethenet ⇄ Router
    5. Router ⇄ Internet
    Missing Image!Set the boot mode jumpers on the Zedboard for JTAG.Missing Image!Power on the Zedboard.

    22. Launch MiniCom terminal emulator

    If not already running, open up a new terminal and launch the MiniCom terminal emulator.
    steve@Desktop:~$ minized
    
    Welcome to minicom 2.7.1
    
    OPTIONS: I18n
    Compiled on Dec 23 2019, 02:06:26.
    Port /dev/ttyACM0, 06:34:25
    
    Press CTRL-A Z for help on special keys
    

    23. Deploy PetaLinux on Zedboard

    Deploy PetaLinux to the Zedboard via JTAG.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-boot --jtag --prebuilt 3
    

    24. Check everything is working as expected

    All being well the following sequence of events should be observed, pay particular attention to point 2.
    1. The blue done LED illuminates indicating the Programmable Logic (PL) has been programmed.
    2. LED 7 and 1 illuminate indicating the bitstream from the updated zedboard_leds_switches project is in use.
    3. The software runs on the Processor System (PS).
    4. PetaLinux starts to boot.
    5. The led-runner application launches and executes the expanding & contracting LED illumination sequence.
    6. The PetaLinux login prompt appears in the terminal emulator.
    Access the webserver running on the Zedboard using a browser pointing at the Zedboard's IP address (192.168.2.87). Click the Read ID button to read the Identification information from the PL address space. Use the Browse... button to select the previously saved configuration file zedboard_leds_switches.txt from ~/projects/zedboard_linux/os/petalinux/src/other. Poke the LEDs & Peek the Switches. Poke Register Bank 1, Poke Register Bank 3 and Peek All selected address in the Register Bank. All being well the webpage should look similar to the following... Missing Image!
    #### Part 6 - Quickstart ####

    25. Obtain tutorial files from Bitbucket, create & build projects, deploy on Zedboard

    The source files relating to this tutorial for both Firmware & OS can be obtained from Bitbucket. The Firmare repository is optional and only required if the Firmware is to be modyfied and rebuilt. The OS repository is essential as it contains both PetaLinux and a local copy of the exported hardware (firmware).

    The instructions below assume that Part 1 - Installation of tools, setup of environment and creation of project area has been completed in full and that the environment has been setup as per 1. Setup environment. The root project area ~/projects should be present and contain the common project. The zedboard_leds_switches & zedboard_linux projects should NOT be present. Adjust the commands below to suit if the above differs.

    Part A (optional) - Obtain firmware source, create Vivado project, build firmware & export hardware.
    steve@Desktop:~$ cd ~/projects
    steve@Desktop:~/projects$ git clone -b v3.0 https://bitbucket.org/spacewire_firmware/zedboard_leds_switches
    steve@Desktop:~/projects$ cd zedboard_leds_switches
    steve@Desktop:~/projects/zedboard_leds_switches$ create_vivado_project.sh
    
    Do something with the Firmware (if required) then perform the following steps :-
    1. Generate bitstream
    2. Export hardware platform
    Part B - Obtain OS source, build & deploy on Zedboard.
    steve@Desktop:~$ cd ~/projects
    steve@Desktop:~/projects$ git clone -b v9.0 https://bitbucket.org/spacewire_firmware/zedboard_linux
    steve@Desktop:~/projects$ cd zedboard_linux/os/petalinux
    
    If Part A was executed perform the following step :-
    1. Update hardware platform
    Do something with the OS (if required) then perform the following steps :-
    1. Build & package PetaLinux
    2. Setup Zedboard hardware
    3. Launch MiniCom terminal emulator
    4. Deploy PetaLinux on Zedboard
    5. Check everything is working as expected