Bar
SpaceWire UK
Specialist providers of VHDL Intellectual Property & Design Services
BarBarBarBar
Tutorial
Missing Image!
Part 16 - Enhance build system to generate repeatable bitstreams & add ID to PetaLinux

Introduction

This tutorial details the steps required to adapt the build system and project identification strings in such a way that identical bitstreams can be produced from a like-for-like checkout from GIT. The self-incrementing version/revision will be replaced with manual user editing to allow for better control of the version number. The build timestamp will be replaced with the GIT timestamp of the last GIT commit. The hash of the last GIT commit will be added to the list of strings to identify the exact set of source used to produce the bitstream. The build system will query GIT to obtain the information required to ensure the produced bitstream is from a fully committed & pushed set of source. Project identification will also be added to PetaLinux to make it consistent with the Firmware Identification.

Aims

The aims of this tutorial are as follows :-

    Part 1 - Project Setup

    1. Setup environment
    2. Obtain tutorial files from Bitbucket (optional)
    3. Change present working directory

    Part 2 - Firmware Development

    1. Launch Vivado & open project
    2. Add GIT hash entry to AXI Identification module
    3. Open & edit block design
    4. Add GIT hash entry to top level HDL
    5. Modify build hook scripts
    6. Check build hook script is working as expected

    Part 3 - OS Development

    1. Create a PetaLinux build script
    2. Commit new & updated files
    3. Create project information file
    4. Modify BitBake recipe
    5. Update website to display new PetaLinux information & Firmware hash
    6. Update Javascript to deal with new PetaLinux information & Firmware hash
    7. Update hardware platform
    8. Commit new & updated files
    9. Build & package PetaLinux

    Part 4 - Hardware Deployment

    1. Setup Zedboard hardware
    2. Launch MiniCom terminal emulator
    3. Deploy firmware & software on Zedboard
    4. Check everything is working as expected
    #### 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. Obtain tutorial files from Bitbucket (optional)

    Starting from this point, having not followed the previous tutorials, can be achieved by obtaining the required files from BitBucket. The only prerequisite is that Part 1 - Installation of tools, setup of environment and creation of project area as been completed.

    Part A - Obtain Firmware source.
    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
    
    Part B - Obtain OS source.
    steve@Desktop:~$ cd ~/projects
    steve@Desktop:~/projects$ git clone -b v9.0 https://bitbucket.org/spacewire_firmware/zedboard_linux
    

    3. 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 ####

    4. Launch Vivado & open project

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

    5. Add GIT hash entry to AXI Identification module

    Update axi_identication.v to add the new GIT hash entry (id_hash) to the list of identification strings. Increase the id_version width from 16 to 32 characters. Do a little housekeeping on the source code as well.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/src/design/axi_identification.v
    

    axi_identification.v

    //
    // File .......... axi_identification.v
    // Author ........ Steve Haywood
    // Version ....... 1.1
    // Date .......... 2 October 2022
    // 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.
    //
    // Description ................ 128 Characters - 32 Registers - 0x000 - 0x07F
    // Company ....................  64 Characters - 16 Registers - 0x080 - 0x0BF
    // Author .....................  64 Characters - 16 Registers - 0x0C0 - 0x0FF
    // Version ....................  32 Characters -  8 Registers - 0x100 - 0x11F
    // Timestamp ..................  32 Characters -  8 Registers - 0x120 - 0x13F
    // Hash .......................  64 Characters - 16 Registers - 0x140 - 0x17F
    // Unused ..................... 144 Characters - 36 Registers - 0x180 - 0x20F
    //                              ---              ---            -------------
    //                              528              132          - 0x000 - 0x20F
    //
    
    
    `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     = 32,                      // P:Version Width (chars)
      localparam c_id_timestamp_w   = 32,                      // P:Timestamp Width (chars)
      localparam c_id_hash_w        = 64                       // P:Hash 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: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:Version
      input  wire [8*c_id_timestamp_w-1 : 0]  id_timestamp,    // I:Timestamp
      input  wire [8*c_id_hash_w-1 : 0]       id_hash,         // I:Hash
      // 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;
      localparam id_hash_lo        = 1 + id_timestamp_hi;
      localparam id_hash_hi        = id_hash_lo + (c_id_hash_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 if (j <= id_hash_hi       ) assign registers_rd[j] = id_hash       [32*(j-id_hash_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/0018/zedboard_leds_switches/fw/src/design/axi_identification.v -O fw/src/design/axi_identification.v
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_leds_switches$ git difftool fw/src/design/axi_identification.v
    

    6. Open & edit block design

    Open the block design by clicking on Open Block Design under the IP INTEGRATOR heading inside the Flow Navigator section.

    At the top of the the BLOCK DESIGN section there should be a message saying Module references are out-of-date. Refresh Changed Modules. Click on Refresh Changed Modules to update the block design. Missing Image! The axi_identification_0 module in the block design should now show an extra input port called id_hash. Bring the new port out of the block design by right clicking on it and selecting Make External from the context menu. Rename the new external port to id_hash by removing the _0 from its name.

    The id_version external port requires its width to be increased. Click on the id_version external port and in the External Ports Properties pane select Properties and change LEFT from 127 to 255. Missing Image! Verify the block design is error free by clicking on the Validate Design Missing Image! icon. Once validated save the block design. Missing Image!

    7. Add GIT hash entry to top level HDL

    Update system_wrapper.sv to add the new GIT hash entry id_hash to the list of identification strings. Do a little housekeeping on the source code as well.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/src/diagram/system/hdl/system_wrapper.sv
    

    system_wrapper.sv

    //
    // File .......... system_wrapper.sv
    // Author ........ Steve Haywood
    // Version ....... 1.1
    // Date .......... 2 October 2022
    // 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: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 ................ Max  32 Characters
      parameter string id_timestamp   = "",  // P:Timestamp .............. Max  32 Characters
      parameter string id_hash        = ""   // P:Hash ................... Max  64 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:Description
        .id_company        ( fmt(id_company)     ),  // P:Company
        .id_author         ( fmt(id_author)      ),  // P:Author
        .id_version        ( fmt(id_version)     ),  // P:Version
        .id_timestamp      ( fmt(id_timestamp)   ),  // P:Timestamp
        .id_hash           ( fmt(id_hash)        ),  // P:Hash
        // 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/0018/zedboard_leds_switches/fw/src/diagram/system/hdl/system_wrapper.sv -O fw/src/diagram/system/hdl/system_wrapper.sv
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_leds_switches$ git difftool fw/src/diagram/system/hdl/system_wrapper.sv
    

    8. Modify build hook scripts

    Remove the build timestamp field from the product identification file as it is no longer required (the GIT timestamp will be used instead). Manually change the version to 4.0 which will be the version number for the next release of firmware. Note the previous version did not align with the GIT tag which was a mistake, this will now be corrected by using version 4.0.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl fw/project.txt
    

    project.txt

    Zedboard LEDs & Switches Example Design
    SpaceWire UK
    Steve Haywood
    4.0
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0018/zedboard_leds_switches/fw/project.txt -O fw/project.txt
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_leds_switches$ git difftool fw/project.txt
    
    Edit the pre-synthesis TCL script to remove the auto-incrementing version/revision functionality and add in the necessary GIT queries to obtain the identification fields and determine if the build source is clean (no pending commits or pushes).
    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.1
    # Date .......... 2 October 2022
    # Description ...
    #   Script to read product/project information from a text file, obtain GIT
    # repository information and set the generics/parameters on the top level
    # module.
    #
    
    # Read Product/Project Information
    if { [catch {set id_file [open "../../../project.txt" r]} msg] } {
      set id_description "?"
      set id_company "?"
      set id_author "?"
      set id_version "?"
      puts "ERROR :-"
      puts $msg
    } {
      gets $id_file id_description
      gets $id_file id_company
      gets $id_file id_author
      gets $id_file id_version
      close $id_file
    }
    
    # Get GIT timestamp
    set dfmt "%d-%b-%Y - %H:%M:%S"
    if { [catch {set id_timestamp [exec git log -1 --pretty=%cd --date=format:$dfmt]} msg] } {
      set id_timestamp "00-Xxx-0000 - 00:00:00"
      puts "ERROR :-"
      puts $msg
    }
    
    # Get GIT hash
    if { [catch {set id_hash [exec git log -1 --pretty=%H]} msg] } {
      set id_hash "0000000000000000000000000000000000000000"
      puts "ERROR :-"
      puts $msg
    }
    
    # Get GIT status
    if { [catch {set status [exec git status -s]} msg] } {
      append id_version " (undefined)"
      puts "ERROR :-"
      puts $msg
    } {
      if {$status ne ""} {
        append id_version " (unstaged)"
      } {
        if { [catch {set status [exec git branch -r --contains $id_hash]} msg] } {
          append id_version " (undefined)"
          puts "ERROR :-"
          puts $msg
        } {
          if {$status eq ""} {
            append id_version " (unpushed)"
          }
        }
      }
    }
    
    # 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 id_hash        [space_replace $id_hash]
    
    # 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\" \
      id_hash=\"$id_hash\" \
    " [current_fileset]
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0018/zedboard_leds_switches/fw/src/script/pre_synth.tcl -O fw/src/script/pre_synth.tcl
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_leds_switches$ git difftool fw/src/script/pre_synth.tcl
    
    Remove the post-bitstream hook from Vivado's build process by navigating Tools » Settings.... In the Settings dialog select Bitstream under Project Settings and highlight the previously entered TCL script ~/projects/zedboard_leds_switches/fw/src/script/post_bit.tcl. Press the Delete key to remove it and then click Apply followed by OK.

    Remove the post-bitstream TCL script from the repository.
    steve@Desktop:~/projects/zedboard_leds_switches$ git rm fw/src/script/post_bit.tcl
    
    For the modified script to work properly untracked files need to be kept away from the status command. To do this a blanket GIT ignore can be added that ignores everything GIT isn't tracking.
    steve@Desktop:~/projects/zedboard_leds_switches$ subl .gitignore
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_leds_switches$ wget https://spacewire.co.uk/tutorial/shared/repos/0018/zedboard_leds_switches/.gitignore -O .gitignore
    
    Just like the bitstream, the hardware platform can be generated from source, hence it is prudent at this point to remove it from the repository.
    steve@Desktop:~/projects/zedboard_leds_switches$ git rm fw/system_wrapper.xsa
    

    9. Check build hook script is working as expected

    Check the current status of the GIT repository.
    steve@Desktop:~/projects/zedboard_leds_switches$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            new file:   .gitignore      
            deleted:    fw/src/script/post_bit.tcl      
            deleted:    fw/system_wrapper.xsa      
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)      
            modified:   fw/project.txt      
            modified:   fw/src/design/axi_identification.v      
            modified:   fw/src/diagram/system/hdl/system_wrapper.sv      
            modified:   fw/src/diagram/system/system.bd      
            modified:   fw/src/script/pre_synth.tcl      
    
    Examine the last log entry for the GIT repository.
    steve@Desktop:~/projects/zedboard_leds_switches$ git log -1
    commit 42e2ca1aceecb80a55942e7540060b3a1b75d17f (HEAD -> master, tag: v3.0, origin/master, origin/HEAD)
    Author: Steve Haywood <steve@spacewire.co.uk>
    Date:   Sun Jan 16 12:48:44 2022 +0000
    
        Added AXI Identification to the design.
    
    Run synthesis to see what values are placed in the identification parameters by the pre_synth TCL script. Click on Run Synthesis under the SYNTHESIS heading inside the Flow Navigator section. Missing Image! Examine the Log tab at the bottom of the Vivado cockpit window, scrolling down a little to see the parameter report. Missing Image! The id_timestamp & id_hash are as shown in the log of the last GIT commit. The id_version is as entered in the project.txt file but is appended with (unstaged) to illustrate the bitstream will NOT be produced from files committed in the local repository.

    Commit the files.
    steve@Desktop:~/projects/zedboard_leds_switches$ git commit -am "Updated Firmware Identification fields to use static information from GIT."
    steve@Desktop:~/projects/zedboard_leds_switches$ git tag -a v4.0 -m "ZYNQ, GPIO, Register Bank & Identification (timestamp & hash from GIT)"
    
    Rerun GIT status & log.
    steve@Desktop:~/projects/zedboard_leds_switches$ git status
    On branch master
    Your branch is ahead of 'origin/master' by 1 commit.
      (use "git push" to publish your local commits)
    
    nothing to commit, working tree clean
    
    steve@Desktop:~/projects/zedboard_leds_switches$ git log -1
    commit cc42e7ba8be329a22b17f7a78f3d28d9784f59a0 (HEAD -> master, tag: v4.0)
    Author: Steve Haywood <steve@spacewire.co.uk>
    Date:   Sun Oct 23 09:25:16 2022 +0100
    
        Updated Firmware Identification fields to use static information from GIT
    
    Reset the synthesis run by right clicking on Run Synthesis under SYNTHESIS and selecting Reset Synthesis Run,. Missing Image! Run synthesis again. Missing Image! Examine the Log tab again. Missing Image! The id_timestamp & id_hash are as shown in the log of the last GIT commit (now updated). The id_version is as entered in the project.txt file but is appended with (unpushed) to illustrate the bitstream will NOT be produced from files pushed to the remote repository.

    Push the files to the remote repository.
    steve@Desktop:~/projects/zedboard_leds_switches$ git push
    
    Again, reset the synthesis run, run the synthesis and check the parameter report in the log tab. Missing Image! Bingo! The id_timestamp & id_hash are as shown in the log of the last GIT commit. The id_version is as entered in the project.txt and is clean (no appended text) illustrating the bitstream will be produced from source safely held in the remote repository.

    A generated bitstream (.bit) contains a 112 byte header that contains various pieces of dynamic information such as the build timestamp. A secondary bitstream (.bin) file can also be created that doesn't contain this header. To enable the creatation of the alternative bitstream navigate Tools » Settings... from the main menu and select Bitstream under Project Settings. Check the -bin_file option and click OK to commit the changes. Missing Image! Generate the programmable logic bitstream by clicking on Generate Bitstream under the PROGRAM AND DEBUG heading inside the Flow Navigator section. Missing Image! Obtain the checksum of the bitstream by using either system_wrapper.bit (less the header) or system_wrapper.bin.
    steve@Desktop:~/projects/zedboard_leds_switches$ dd if=fw/vivado/project.runs/impl_1/system_wrapper.bit skip=112 bs=512 iflag=skip_bytes | md5sum
    7901+1 records in
    7901+1 records out
    4045564 bytes (4.0 MB, 3.9 MiB) copied, 0.00829275 s, 488 MB/s
    aa330b5b405de83453b6bdb36abf8197  -
    steve@Desktop:~/projects/zedboard_leds_switches$ md5sum fw/vivado/project.runs/impl_1/system_wrapper.bin
    aa330b5b405de83453b6bdb36abf8197  fw/vivado/project.runs/impl_1/system_wrapper.bin
    
    Quit Vivado and delete (or rename) the project folder.
    steve@Desktop:~/projects/zedboard_leds_switches$ cd ..
    steve@Desktop:~/projects$ rm -rf zedboard_leds_switches
    
    Clone the GIT repository of the project, checkout the version as shown by the hash held in the Firmware and rebuilt the project from scratch. The hardware platform is automatically exported by the build script after the bitstream is generated.
    steve@Desktop:~/projects$ git clone git@192.168.2.20:zedboard_leds_switches.git
    steve@Desktop:~/projects$ cd zedboard_leds_switches
    steve@Desktop:~/projects/zedboard_leds_switches$ git checkout cc42e7ba8be329a22b17f7a78f3d28d9784f59a0
    steve@Desktop:~/projects/zedboard_leds_switches$ create_vivado_project.sh build
    
    Obtain the checksum of the bitstream by using system_wrapper.bit (less the header).
    steve@Desktop:~/projects/zedboard_leds_switches$ dd if=fw/vivado/project.runs/impl_1/system_wrapper.bit skip=112 bs=512 iflag=skip_bytes | md5sum
    7901+1 records in
    7901+1 records out
    4045564 bytes (4.0 MB, 3.9 MiB) copied, 0.00829275 s, 488 MB/s
    aa330b5b405de83453b6bdb36abf8197  -
    
    Lovely Jubbly! The bitstreams from the previous build and the new build are identical.
    #### Part 3 - OS Development ####

    10. Create a PetaLinux build script

    Create a PetaLinux build script similar to the pre-synthesis TCL script. This script will query GIT and update the project information file with the timestamp & hash of the last commit, the one that is currently being used to build the PetaLinux project.
    steve@Desktop:~/projects/zedboard_leds_switches$ cd ~/projects/common
    
    steve@Desktop:~/projects/common$ subl other/src/script/petalinux-build-id.sh
    

    petalinux-build-id.sh

    #!/bin/bash
    
    #
    # File .......... petalinux-build-id.tcl
    # Author ........ Steve Haywood
    # Version ....... 1.0
    # Date .......... 2 October 2022
    # Description ...
    #   Script to read project information file, obtain GIT repository information,
    # append project information file with GIT information, build PetaLinux project
    # and then restore project information file.
    #
    #
    
    # Set identification file
    project="project-spec/meta-user/recipes-apps/website/files/project.txt"
    
    # Only perform bulk of actions if identification file
    if [ -f "$project" ]; then
    
      # Read project information
      readarray -t ids < project-spec/meta-user/recipes-apps/website/files/project.txt
    
      # Display identification information
      echo -e '\033[1mNOTE\033[0m: Building PetaLinux with the following identification'
      echo "Description ... ${ids[0]}"
      echo "Company ....... ${ids[1]}"
      echo "Author ........ ${ids[2]}"
    
      # Get GIT timestamp
      id_timestamp=$(git log -1 --pretty=%cd --date=format:"%d-%b-%Y - %H:%M:%S")
      if [ $? -ne 0 ]; then
        id_timestamp="00-Xxx-0000 - 00:00:00"
      fi
    
      # Get GIT hash
      id_hash=$(git log -1 --pretty=%H)
      if [ $? -ne 0 ]; then
        id_hash="0000000000000000000000000000000000000000"
      fi
    
      # Get GIT status
      id_append=""
      status=$(git status -s)
      if [ $? -ne 0 ]; then
        id_append="(undefined)"
      else
        if [ ! -z "$status" ]; then
        	id_append="(unstaged)"
        else
          status=$(git branch -r --contains ${id_hash})
          if [ $? -ne 0 ]; then
            id_append="(undefined)"
          else
            if [ -z "$status" ]; then
              id_append="(unpushed)"
            fi
          fi
        fi
      fi
    
      # Display identification information
      echo "Version ....... ${ids[3]} ${id_append}"
      echo "Timestamp ..... $id_timestamp"
      echo "Hash .......... $id_hash"
    
      # Modify project information file
      fname="$project"
      echo ${ids[0]}               > ${fname} # Description
      echo ${ids[1]}              >> ${fname} # Company
      echo ${ids[2]}              >> ${fname} # Author
      echo ${ids[3]} ${id_append} >> ${fname} # Version
      echo ${id_timestamp}        >> ${fname} # Timestamp
      echo ${id_hash}             >> ${fname} # Hash
    
      # Build PetaLinux
      $(which petalinux-build)
      jobs -l
      wait
    
      # Restore project information file
      echo ${ids[0]}  > ${fname} # Description
      echo ${ids[1]} >> ${fname} # Company
      echo ${ids[2]} >> ${fname} # Author
      echo ${ids[3]} >> ${fname} # Version
    
    else
    
      # Build PetaLinux
      $(which petalinux-build)
    
    fi
    
    Direct download available here :-
    steve@Desktop:~/projects/common$ wget https://spacewire.co.uk/tutorial/shared/repos/0019/common/other/src/script/petalinux-build-id.sh -O other/src/script/petalinux-build-id.sh
    
    Make the script executable.
    steve@Desktop:~/projects/common$ chmod +x other/src/script/petalinux-build-id.sh
    
    Make life easier by using an alias for petalinux-build such that it calls petalinux-build-id, add this within the shell initialization script.
    steve@Desktop:~/projects/common$ subl ~/.bashrc

    .bashrc (partial)

    1. # Aliases
    2. alias petalinux-build='~/projects/common/other/src/script/petalinux-build-id.sh'

    11. Commit new & updated files

    Check the current status of the GIT repository.
    steve@Desktop:~/projects/common$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            other/src/script/petalinux-build-id.sh      
    
    nothing added to commit but untracked files present (use "git add" to track)
    
    Looks good.

    Commit the updated Common files and push the commit up to the remote repository.
    steve@Desktop:~/projects/common$ git add -A
    steve@Desktop:~/projects/common$ git commit -a -m "Added PetaLinux build script that updates the project information file with timestamp & hash."
    steve@Desktop:~/projects/common$ git push
    steve@Desktop:~/projects/common$ git tag -a v2.0 -m "Common files update"
    steve@Desktop:~/projects/common$ git push origin v2.0
    

    12. Create project information file

    Create a project information file for the PetaLinux project just like the one used for for the Vivado project. Set the version to 10.0 to align with the next GIT tag that will be used.
    Steve@Desktop:~/projects/common$ cd ~/projects/zedboard_linux
    
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    

    project.txt

    Zedboard PetaLinux Example Design
    SpaceWire UK
    Steve Haywood
    10.0
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0021/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt -O os/petalinux/project-spec/meta-user/recipes-apps/website/files/project.txt
    

    13. Modify BitBake recipe

    Modify the BitBake recipe to include the new project information file.
    steve@Desktop:~/projects/zedboard_linux$ subl os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    

    website.bb

    #
    # This file is the website recipe.
    #
    
    SUMMARY = "Simple website application"
    SECTION = "PETALINUX/apps"
    LICENSE = "MIT"
    LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
    
    SRC_URI = "file://index.html"
    SRC_URI += "file://uptime.js"
    SRC_URI += "file://zedboard.png"
    SRC_URI += "file://styles.css"
    SRC_URI += "file://cgi-bin/index.cgi"
    SRC_URI += "file://cgi-bin/uptime.cgi"
    SRC_URI += "file://amber.gif"
    SRC_URI += "file://green.gif"
    SRC_URI += "file://red.gif"
    SRC_URI += "file://project.txt"
    
    FILES_${PN} += "/srv/www"
    
    S = "${WORKDIR}"
    
    do_install() {
         install -d ${D}/srv/www
         install -m 0644 ${S}/index.html ${D}/srv/www/index_original.html
         install -m 0644 ${S}/uptime.js ${D}/srv/www
         install -m 0644 ${S}/zedboard.png ${D}/srv/www
         install -m 0644 ${S}/styles.css ${D}/srv/www
         install -m 0644 ${S}/amber.gif ${D}/srv/www
         install -m 0644 ${S}/green.gif ${D}/srv/www
         install -m 0644 ${S}/red.gif ${D}/srv/www
         install -m 0644 ${S}/project.txt ${D}/srv/www
         install -d ${D}/srv/www/cgi-bin
         install -m 0755 ${S}/cgi-bin/index.cgi ${D}/srv/www/cgi-bin
         install -m 0755 ${S}/cgi-bin/uptime.cgi ${D}/srv/www/cgi-bin
    }
    
    Direct download available here :-
    steve@Desktop:~/projects/zedboard_linux$ wget https://spacewire.co.uk/tutorial/shared/repos/0020/zedboard_linux/os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb -O os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    
    Check out the changes.
    steve@Desktop:~/projects/zedboard_linux$ git difftool os/petalinux/project-spec/meta-user/recipes-apps/website/website.bb
    

    14. Update website to display new PetaLinux information & Firmware hash

    Update the existing HTML to include a new PetaLinux Information section & add the GIT hash entry in the Firmware Information section.
    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">Operating System Information <button onclick="read_os_ids()">Read ID</button></th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td style="text-align:right">Description :</td>
    <td style="text-align:left" id="oid_0">Unknown</td>
    <td><img id="osid_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="oid_1">Unknown</td>
    <td><img id="osid_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="oid_2">Unknown</td>
    <td><img id="osid_2" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Version :</td>
    <td style="text-align:left" id="oid_3">Unknown</td>
    <td><img id="osid_3" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Timestamp :</td>
    <td style="text-align:left" id="oid_4">Unknown</td>
    <td><img id="osid_4" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    <tr>
    <td style="text-align:right">Hash :</td>
    <td style="text-align:left" id="oid_5">Unknown</td>
    <td><img id="osid_5" style="vertical-align:middle" src="../amber.gif" title="Unknown!" alt="Missing Image!"></td>
    </tr>
    </tbody>
    </table>
    <table>
    <thead>
    <tr><th colspan="3">Firmware Information <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">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">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>
    <tr>
    <td style="text-align:right">Hash :</td>
    <td style="text-align:left" id="id_5">Unknown</td>
    <td><img id="sid_5" 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/0020/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
    

    15. Update Javascript to deal with new PetaLinux information & Firmware hash

    Update the existing Javascript to include code for reading and displayed the PetaLinux Information, and reading & displaying the GIT hash string in the Firmware Information section.
    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;
    
    // Download OS information file & display result
    async function read_os_ids() {
      let response = await fetch("/project.txt");
      if (response.status == 200) {
        let ids = await response.text();
        fields = ids.split(/\r?\n/);
      }
      for (var i = 0; i < 6; i++) {
        const now = Date.now();
        const txt_obj = document.getElementById("oid_" + i);
        const img_obj = document.getElementById("osid_" + i);
        if (response.status == 200) {
          if (i < fields.length && fields[i] != "") {
            img_obj.src = "../green.gif?" + now;
            img_obj.title = "Last file fetch successful";
            txt_obj.innerHTML = fields[i];
          } else {
            img_obj.src = "../red.gif?" + now;
            img_obj.title = "Missing field information";
            txt_obj.innerHTML = "Unknown";
          }
        } else {
          img_obj.src = "../red.gif?" + now;
          img_obj.title = "Last file fetch failed";
          txt_obj.innerHTML = "Unknown";
        }
      }
    }
    
    // Peek all strings
    function read_ids() {
      read_id(0x000, 0);
      read_id(0x080, 1);
      read_id(0x0C0, 2);
      read_id(0x100, 3);
      read_id(0x120, 4);
      read_id(0x140, 5);
    }
    
    // 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/0020/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
    

    16. 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!

    17. Commit new & updated files

    Check the current status of the GIT repository.
    steve@Desktop:~/projects/common/os/petalinux$ git status
    On branch master
    Your branch is up-to-date with 'origin/master'.
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
            modified:   project-spec/meta-user/recipes-apps/website/files/cgi-bin/index.cgi      
            modified:   project-spec/meta-user/recipes-apps/website/files/uptime.js      
            modified:   project-spec/meta-user/recipes-apps/website/website.bb      
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            project-spec/meta-user/recipes-apps/website/files/project.txt      
    
    no changes added to commit (use "git add" and/or "git commit -a")
    
    Looks good.

    Commit the updated PetaLinux files, create an annotated tag and push the commit & tag up to the remote repository.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git add -A
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git commit -a -m "Updated webpage to include PetaLinux ID table & add hash field to Firmware ID table."
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git push
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git tag -a v10.0 -m "PetaLinux, Peek/Poke, LED Runner, Webserver, Peek/Poke CGI, PL Access, Style Sheet, Register Bank & ID Strings (PetaLinux & Firmware hash) with XSA from zedboard_leds_switches v4.0"
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git push origin v10.0
    

    18. Build & package PetaLinux

    Rebuild PetaLinux to include the updates.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ source ~/.bashrc
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    
    Note that the petalinux-build command now diverts off to petalinux-build-id.sh so the following message should be displayed after calling petalinux-build.
    NOTE: Building PetaLinux with the following identification
    Description ... Zedboard PetaLinux Example Design
    Company ....... SpaceWire UK
    Author ........ Steve Haywood
    Version ....... 10.0 (unstaged)
    Timestamp ..... 23-Oct-2022 - 10:04:44
    Hash .......... 980d0a89f629f246e18f79df9f69bb7d35c0a185
    
    Hold the phone! Stop the build process with Ctrl-C. Somehow the build is using unstaged files. Forget to add the new project.txt file!

    Add the missing file and perform the necessary GIT finger work.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git add project-spec/meta-user/recipes-apps/website/files/project.txt
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git commit -am "Updated webpage to include PetaLinux ID table & add hash field to Firmware ID table."
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git tag -d v10.0
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git tag -a v10.0 -m "PetaLinux, Peek/Poke, LED Runner, Webserver, Peek/Poke CGI, PL Access, Style Sheet, Register Bank & ID Strings (PetaLinux & Firmware hash) with XSA from zedboard_leds_switches v4.0"
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ git push origin v10.0
    
    Attempt to rebuild PetaLinux again.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-build
    
    This time the build should be deemed as using fully staged and pushed files with no unsavoury comments after the version number.
    NOTE: Building PetaLinux with the following identification
    Description ... Zedboard PetaLinux Example Design
    Company ....... SpaceWire UK
    Author ........ Steve Haywood
    Version ....... 10.0
    Timestamp ..... 23-Oct-2022 - 10:08:02
    Hash .......... 9bea7fd2705efc6da3b975e0e05b28ec57872afd
    
    Package up PetaLinux ready for deployment.
    steve@Desktop:~/projects/zedboard_linux/os/petalinux$ petalinux-package --prebuilt --force
    
    #### Part 4 - Hardware Deployment ####

    19. 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.

    20. 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
    

    21. Deploy firmware & software on Zedboard

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

    22. 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 buttons in both the Operating System Information & Firmware Information sectons to read the Identification information from the OS filesystem & PL address space. All being well the following should be displayed in the Operating System Information & Firmware Information sections.



    Seems silly not to use the GIT tag for the Version since they are in lockstep, but as seen previously, a GIT tag is not immutable unlike a GIT timestamp & GIT hash.

    By including the hashes and marking any unclean builds with either unstaged or unpushed means there is always a direct link back to the original files that created the builds.