Print
Category: Basics
Hits: 152

 

 

Git + Vivado (Project mode, GUI)

Author: Sherneyko Plata

 

Introduction

As the complexity of codebases and software projects increase, source code versioning has proven a valuable tool in the realm of software development. However, due to differences between FPGA vendors, and even structural changes within same vendor tool versions, FPGA project trees have usually been difficult to track.

One way to address this issue has been to use a non-project script-based (usually tcl) development environment. This is a good and recommended approach, but requires careful setup, and usually deviates from the FPGA vendor's standard procedure.

This tutorial proposes a methodology for versioning Vivado projects (from GUI) using git.

Requirements

Procedure

1. Initial setup

1.1. Directory tree creation

Create the following directory tree. All folders are empty for now

MyProject/  
├── docs  
└── fpga  
    ├── hls  
    ├── mcs  
    ├── tcl  
    ├── v  
    └── xdc  

1.2. Create a new vivado project on MyProject/fpga/ named "myvivadoprj".

fpga/  
├── myvivadoprj  
│   ├── myvivadoprj.cache  
│   │   └── wt  
│   │       └── project.wpc  
│   ├── myvivadoprj.hw  
│   │   └── myvivadoprj.lpr  
│   ├── myvivadoprj.ip_user_files  
│   ├── myvivadoprj.sim  
│   └── myvivadoprj.xpr  

  

1.3. Add your source files to the project

fpga/  
├── v  
│   └── top.v  
└── xdc  
    └── io.xdc  

 

  

1.4. Compile your project. After compilation, the project tree should look something like this

non-essential files will be shown as (...)

MyProject/  
├── docs  
└── fpga  
    ├── hls  
    ├── mcs  
    ├── myvivadoprj  
    │   ├── myvivadoprj.cache  
    │   │   └── (...)   
    │   ├── myvivadoprj.hw  
    │   │   └── (...)   
    │   ├── myvivadoprj.ip_user_files  
    │   ├── myvivadoprj.runs  
    │   │   ├── impl_1  
    │   │   │   ├── (...)   
    │   │   │   ├── top.bit  
    │   │   │   └── top.tcl  
    │   │   └── synth_1  
    │   │       ├── (...)  
    │   │       └── top.tcl  
    │   ├── myvivadoprj.sim  
    │   └── myvivadoprj.xpr  
    ├── tcl  
    ├── v  
    │   └── top.v  
    └── xdc  
        └── io.xdc  

2. Git setup

Now, we can start using git. open MyProject/ , and right click > Git bash here
we will be using git cli (command line interface).

2.1. initialization

first, we initialize our repository:

$ git init .
Initialized empty Git repository in C:/dev/git/_/MyProject/.git/

2.2. checking status

then we can check its status:

$ git status
On branch master

No commits yet

Untracked files:
(use "git add <file>..." to include in what will be committed)
		docs/
		fpga/

nothing added to commit but untracked files present (use "git add" to track)

so we're on the master branch, and our repository has no commits yet.
if youre unfamiliar with git: a commit is like a "snapshot" , and a branch is just a pointer to a snapshot.
we will be moving our branch as we move through our repository

we can then use ls to list directories and files, and find the file paths we want to add to our index (also called staging area), so we can keep track of their changes from now on.

$ls **
docs:
images.odt

fpga:
hls/  mcs/  myvivadoprj/  tcl/  v/  xdc/

$ ls fpga/**
fpga/hls:

fpga/mcs:

fpga/myvivadoprj:
myvivadoprj.cache/  myvivadoprj.hw/  myvivadoprj.ip_user_files/  myvivadoprj.runs/  myvivadoprj.sim/  myvivadoprj.xpr

fpga/tcl:

fpga/v:
top.v

fpga/xdc:
io.xdc

2.3. adding files to our index

the goal here is to track just enough to be able to compile the project, additionally its good to track some output files (.bit,.mcs), so we dont need to compile if we just want to use our bitfile at some past point in time.

$ git add fpga/v/top.v
$ git add fpga/xdc/io.xdc
$ git add fpga/myvivadoprj/myvivadoprj.runs/synth_1/top.tcl
$ git add fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.bit
$ git add fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.tcl
$ git add fpga/myvivadoprj/myvivadoprj.xpr

we just added new files to our index. lets check the repository status:

$ git status
On branch master

No commits yet

Changes to be committed:
(use "git rm --cached <file>..." to unstage)
		new file:   fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.bit
		new file:   fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.tcl
		new file:   fpga/myvivadoprj/myvivadoprj.runs/synth_1/top.tcl
		new file:   fpga/myvivadoprj/myvivadoprj.xpr
		new file:   fpga/v/top.v
		new file:   fpga/xdc/io.xdc

Untracked files:
(use "git add <file>..." to include in what will be committed)
		docs/
		fpga/myvivadoprj/myvivadoprj.cache/
		fpga/myvivadoprj/myvivadoprj.hw/
		fpga/myvivadoprj/myvivadoprj.runs/.jobs/
		(...)
	

"Changes to be committed" are the changes we will record as a commit. They are marked in green a commit is a snapshot of the changes on the files in the index

"Untracked files" are the files we're not currently tracking, so they will NOT be recorded as part of a commit. They are marked in red

Its a good practice to review the "Untracked files" section every time we commit, so we can make sure we dont forget new files which we might like to add to our commit. So is to review the "Changed to be committed section", to make sure we didn't add some file we dont want to track. Essentially, one is in charge of his own snapshot.

2.4. committing

Then, we commit our changes.

$ git commit

This will open a command line text editor, usually "vim". Here we have to add some description to our commit. Since its our first commit, we'll just call it "first commit" If you have no experience with vim, dont panic, follow this steps:

git commit will output its results:

[master (root-commit) 024dd23] first commit
6 files changed, 819 insertions(+)
create mode 100644 fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.bit
create mode 100644 fpga/myvivadoprj/myvivadoprj.runs/impl_1/top.tcl
create mode 100644 fpga/myvivadoprj/myvivadoprj.runs/synth_1/top.tcl
create mode 100644 fpga/myvivadoprj/myvivadoprj.xpr
create mode 100644 fpga/v/top.v
create mode 100644 fpga/xdc/io.xdc

2.5. reviewing our repo state

great we did our first commit. but cli isnt always the best choice when reviewing our repository state. lets check the gui tool

$ gitk

added lines are shown in green, removed lines in red, no color means no change. since this are all new files, they are all green.

2.6. making changes to our repo

lets make some modification and repeat the process, by first adding "//my pretty comment" somewhere in "top.v" ,and checking the repo status afterwards

$ git status
On branch 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:   fpga/v/top.v
	

ok so something has changed. what exactly? lets see on gitk.
i usually close/open gitk everytime i need. also run it with "&" so it doesnt lock the cli

$ gitk&   

so we can see we removed a blank line, and added "//my pretty comment"
since this file is already being tracked, its unnecesary to specify its path again.

$ git add -u .

will add all modified tracked files in our current directory to our index its possible to replace "." to any path in the repo. wildcards "*" can be used as well

if there is some file whose changes we dont want to save in our commit, we can remove it from our index by using

$ git restore --staged fpga/v/top.v

this will not change the file itself

if there is some change we want to revert, we can use

$ git checkout fpga/v/top.v
Updated 1 path from the index

this will restore the file to the current commit state. In this case, that would delete our comment.

2.7. other useful commands

i recommend the reader to also check the following commands

git checkout <branch_name>          //switches to another branch
git checkout <filepath>             //restores a file to its latest committed state
git checkout -b <branch_name>       //creates a branch and switches to it
git merge <branch_name>             //merges a branch changes into current branch, 
git merge --ff-only <branch_name>   //same as merge, but only continues if no conflicts
git checkout --ours   <filepath>    //for merge resolution. keeps changes from current branch
git checkout --theirs <filepath>    //for merge resolution. keeps changes from branch being merged

sometimes its necessary to resolve merge conflicts manually. resolving merge conflicts takes some practice to learn. there is no silver bullet to it.

also, some files should NOT be changed piecewise. for example, .xpr,.bit, etc. for such cases use only ours/theirs.

if working with remote repositories

git clone <repo_path> <target_path> //clones a repo into a target_path
git push origin <branch_name>       //push branch to remote
git fetch                           //get branches from remote
git remote -v                       //list remotes

one does not need to be a git expert to take advantage of its capabilities, but still it takes some effort to get used to it. There are many ways to achieve the same result in git, all with different advantages. So, feel free to add/replace any commands here.

3. testing time

3.1. recompiling

now lets test our repo works as intended. Exit the project tree and clone our repo as MyProject.2

$ cd ..
$ git clone MyProject/ MyProject.2
Cloning into 'MyProject.2'...
done.

now open the cloned project on vivado

MyProject.2/fpga/myvivadoprj/myvivadoprj.xpr

and try to compile it. If it fails, then some file might be missing, so please review the vivado project file paths to find the cause.

3.2. testing on FPGA

for this example i used a PYNQ-Z2 board.

on top.v:
connected leds[2:0] to push_buttons[2:0]. So when you press the pb[2:0], leds[2:0] will turn on.
on io.xdc:
assigned pins for PYNQ-Z2.

you can also clone the example repository by running:

git clone https://gitea.squirrelnut.synology.me:5001/neyko3/git-vivado-project-mode-example.git

4. Directory tree paths description.

just some additional information about the purpose of each directory.

fpga/
	place all your fpga-related files here
  
fpga/v/		
	all verilog or systemverilog files.
	
fpga/xdc/	
	all xdc constraints
		
fpga/tcl/
	sometimes its useful to deploy our own tcl scripts to be run in some vivado compilation stage. 
	Personally I like using the "Pre-synthesis" hook in vivado to run some tcl file before compilation starts.
	for my use case, a "Pre-synthesis" tcl script
		parses a verilog header "fpga/v/parameter.vh" file, 
		and generates a "fpga/v/presynth.vh" header, with some `define directives use for conditional compilation. 		
		This way i use a single project to target different configurations of my source code, and i get to switch between configurations just by changing one line in parameter.vh before compilation
		
fpga/hls/	
	place all your vivado HLS projects here. Scan this folder from vivado ip repository when adding an HLS generated IP.
		
fpga/mcs/
	*.mcs files are generated to make a xilinx FPGA non volatile. There is an option to generate a *.prm file related to the *.mcs. 
	These prm files are very useful metadata and can simply be open using Notepad. So you can always make sure whats inside an mcs and how it was generated.
	one might use other files inside the *.mcs (not just *.bit). I find it useful to keep them here too as well.

fpga/myvivadoprj1/myvivadoprj.xpr
	-- this is not a folder. its a file --
	All our source code files must be linked to fpga/v/*, and all our constraints must be linked to fpga/xdc/*
	Do not copy source files into vivado project. 
	By not copying files into the project, its possible to share source files along different vivado projects if necessary, and our paths are more consistant
			
fpga/myvivadoprj1/myvivadoprj.srcs/ip/
	-- vivado generated IPs --
	try to keep vivado generated IPs as .xci. That way, we only need to track one file for changes. Its OK to unpack the .xci temporarely, but try not to commit the unpacked IP since its more bothersome to track changes for.
	Some IPs wont pack as .xci (such as MIG generator). In those cases, commit the whole IP dir. Hopefully it wont change much in the future anyways

5. additional notes

MyProject/.gitattributes

git tries to detect whats a binary and whats not, and will automatically skip diff binary files. (Most binary files are useless to diff, as we usally just care about the source file that generated them). however sometimes we might want to treat a text file as a binary file, if it changes frequently and we dont care about inspecting how it changes. we can also set some default beheviors for this repository on this file.

contents of my .gitattributes:

# Set default behavior
* text=auto
* eol=lf

# Set default behavior
**/synth*/*.tcl binary
**/impl*/*.tcl binary
*.mcs binary
*.prm binary
*.xml binary
	

use short relative paths

git cli reports changes relative to its working directory, so if we do "git status" from "MyProject/",
short relative paths such as "fpga/v/" make it easier to spot changes and review new or modified files.

be very selective when using "git add"

manually add every new file you want to track
do not track whole directories if possible. this leads to clutter
not all fpga/v/* source files are to be tracked.
sometimes one might prototype some module and find out its a dead end, or make some backup of some file.

avoid gitignore

i tried to use .gitignore files before but due to the unpredictable future structure of our dir tree, maybe its better to not ignore anything and just add what we need explicitly
also you wont have to wonder which .gitignore is blocking you from adding some new file.
if you do use gitignore anyways, dont force add "-f" (this defeats its purpose)

use different synth,impl runs:

the default synth,impl run names are synth_1,impl_1. But we dont need to limit ourselves to just one of each,
since every synth,impl run on vivado has its own dir under "myvivadoprj.runs/", we can have many runs.
By using multiple runs, we can compare our timing between code iterations (synth report), and furthermore
we can keep any compilation results we like, in case we make some mistake and need to backtrack changes
to make our design work correctly again. i recommed for every synth to have its own impl, and to keep the naming scheme as "synth_*,impl_*".