Skip to content
VLSI Mentor

Linux & Bash · Part II · Module 23 · Navigating RTL and Verification Repositories

Anatomy of a Design Repository

A design repository looks arbitrary until you know that almost every one is built from the same seven kinds of directory. This chapter names them, gives the four commands that identify which is which in an unfamiliar tree, and does it against a real repository that compiles and runs.

Foundation20 min readLinuxPart IIRTLRepositoryNavigation

Part II · Module 23 · Chapter 23.1 · Anatomy of a Design Repository

The first day on a new project is usually spent opening directories to find out what is in them. It does not need to be, because design repositories are far more similar than their directory names suggest.

Everything in this module uses one repository, chip_a, which exists and compiles. Its tree is small enough to print and real enough to be worth reading.

1. The Tree

tree is a convenient way to see a directory structure and is not installed everywhere:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ command -v tree
$ echo $?
1

So the portable way is find with a depth limit, which is Chapter 14.6's tool asked a structural question:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ find . -maxdepth 2 -type d ! -path '*/.git*' | sort
.
./constraints
./cov
./csrc
./docs
./filelists
./ip
./ip/vendor_axi
./rtl
./rtl/axi
./rtl/core
./rtl/uart
./run_2026_10_01
./run_2026_10_07
./scripts
./simv.daidir
./tb
./tb/agents
./tb/env
./tb/tests
./third_party
./third_party/cde
./work

Fifteen top-level directories, which is typical. Laid out as a tree, the top two levels:

The top of a design repository

The chip_a repository as a tree. The root has rtl and tb, which are authored; filelists, the authored flow description; ip, which is imported; and work, which is generated. Below rtl is one block directory, axi; below tb is tests, which holds the top-level module.axione blockrtlauthoredteststhe top leveltbauthoredfilelistsauthored flowipimportedworkgeneratedchip_athe repository
The root and its directories, with each one labelled by class rather than by purpose. The three classes are the information: authored directories are yours to change, imported ones you read, and generated ones you may delete. A tree this shallow is deliberate — the shape at the top is what you need on day one, and the depth below it is what the rest of the module navigates.

And in full, with what each directory is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
chip_a/
├── rtl/            authored   the design itself
│   ├── axi/
│   ├── core/
│   └── uart/
├── tb/             authored   testbench, agents, tests
│   ├── agents/
│   ├── env/
│   └── tests/
├── constraints/    authored   timing and physical constraints (.sdc)
├── filelists/      authored   which files the tools compile
├── scripts/        authored   how the flow is run
├── docs/           authored   specifications
├── ip/             imported   another team's or a vendor's blocks
│   └── vendor_axi/
├── third_party/    imported   external code
│   └── cde/
├── work/           generated  compiler output
├── simv.daidir/    generated  simulator database
├── csrc/           generated  generated C sources
├── cov/            generated  coverage databases
├── run_2026_10_01/ generated  one run's directory
├── run_2026_10_07/ generated  another run's directory
└── README.md       authored

The three words in that middle column are the whole chapter. Everything else follows from knowing which of the three a directory is, and Chapter 23.2 is entirely about proving it rather than guessing.

2. The Seven Kinds

Seven kinds of directory, and what each one means for you

Seven kinds of directory in a design repository, in three rows. The top row is authored and editable: design sources, verification sources, and the flow description of filelists, scripts and constraints. The middle row is read-only: imported IP from another team or vendor. The bottom row is generated and deletable: build output, run directories, and result databases.Design sourcesyours to changeVerification sourcesyours to changeFilelists, scripts,constraintshow the tools are drivenImported blocksread, never edit in placeSpecificationsauthored, rarely currentExternal codesomeone else owns itBuild outputdeletable, rebuildableRun directoriesone per simulationResult databasescoverage, waveforms, logs12
Almost every design repository is built from these seven, under local names. The top row is what you read and change; the middle row is what you read but do not change; the bottom row is what you may delete. Which row a directory is in decides whether you may edit it, whether your searches should include it, and whether losing it costs you anything — which is why the classification matters more than the names.
KindUsual namesYou may editSearches should include it
Design sourcesrtl, src, hdl, designYesAlways
Verification sourcestb, verif, test, uvmYesAlways
Flow descriptionfilelists, scripts, constraints, flow, synYesUsually
Imported blocksip, lib, imports, vendorNo — change upstreamOften, read-only
Specificationsdocs, doc, specYesRarely
Build outputwork, csrc, simv.daidir, xcelium.d, objNoNever
Resultscov, logs, runs, results, run_*NoNever

The last two rows are why Chapter 23.5 exists: a search that includes them describes the last simulation rather than the design.

3. Four Commands That Map a Repository

In order, and the whole sequence takes under a minute.

One — the shape, depth-limited. §1's find, which tells you how many kinds of thing there are before you open anything.

Two — what the files actually are. A census by extension, over the tracked files only:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ git ls-files | sed 's|.*\.||' | sort | uniq -c | sort -rn
   5 sv
   2 md
   1 txt
   1 sh
   1 sdc
   1 f

That is a SystemVerilog project with one constraints file, one script and one filelist. A real one has thousands of .sv and a long tail — but the shape of the tail is the information: .vhd alongside .sv means a mixed-language flow, .xdc means Xilinx, .sdc means a standard timing flow, .f means filelists (Module 26).

git ls-files rather than find on purpose, for the reason Chapter 22.1 §4 gave: the index holds source and nothing else, so the census is not polluted by build output. sed 's|.*\.||' keeps everything after the last dot — Chapter 13.6's greedy .*, used deliberately here because a greedy match to the last dot is exactly what an extension is.

Three — where the bulk is, which is almost never where the design is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ du -sk ./*/ | sort -rn | head -6
      12	./rtl/
       8	./work/
       4	./third_party/
       4	./tb/
       4	./simv.daidir/
       4	./scripts/

In this toy repository rtl is biggest. In a real one the order is inverted and the top five entries are all generated — which is the single fastest way to identify the generated directories in a tree whose names you do not recognise. Chapter 22.4 §4 is the idiom; here it is being used to classify rather than to free space.

Four — the entry points. The flow has to start somewhere, and it is almost always a tracked executable:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ git ls-files -s | awk '$1 ~ /755$/ { print $4 }'
scripts/run_sim.sh

git ls-files -s prints the mode, object, stage and path, so field one is the mode and field four is the path. Mode 100755 means git has recorded the execute bit — so this finds the scripts someone intended to be run, as distinct from every file that happens to be executable on your disk. That distinction matters because Chapter 22.1 §2's filesystem can hand you execute bits you did not ask for.

4. The Repository Compiles — Which Is the Point

A directory map is a guess until something uses it. Here the filelist is the authority on which files are real:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ cat filelists/chip.f
rtl/core/chip_top.sv
rtl/axi/axi_master.sv
rtl/uart/uart_tx.sv
tb/tests/tb_chip.sv

Four files out of the five tracked .sv files. And the flow:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ iverilog -g2012 -o work/sim.vvp -f filelists/chip.f
$ echo $?
0
$ vvp work/sim.vvp < /dev/null
AXI: elaborated tb_chip.dut.u_axi from rtl/axi/axi_master.sv
UART: elaborated tb_chip.dut.u_uart
TB: scope is tb_chip
TB: status=fd
TEST PASSED
tb/tests/tb_chip.sv:12: $finish called at 35 (1s)

Three things in that output are worth noticing now, because the rest of the module is built on them.

-f filelists/chip.f is how every tool in this industry is told what to read. The flag letter differs — -f, -F, -file, +incdir — but a file listing files is universal, and it is the reason a repository's filelists/ directory is as important as its rtl/.

tb_chip.dut.u_axi is a hierarchy path, and it is the thing printed in logs when something goes wrong. Walking from that string back to a file and a line is Chapter 23.4, and it is the first step of nearly every debug.

And the fifth .sv file was not compiled. There are three files in this repository defining a module called axi_master, and only one of them is in the filelist. That is Chapter 23.3, and it is the most common way an afternoon is lost in an unfamiliar tree.

5. What rtl/ Is Usually Organised By

Three schemes, and knowing which one you are looking at tells you where to put a new file.

By block, which is what chip_a uses — rtl/axi/, rtl/core/, rtl/uart/. Each directory is a functional unit, usually matching a block in the architecture document. The commonest scheme, and the easiest to navigate, because a module's name tells you its directory.

By hierarchy level — rtl/top/, rtl/subsystem/, rtl/leaf/. Rarer, and a sign that the project cares about synthesis partitioning.

And flat — every .sv in one directory. Works to a few dozen files and then stops working, which is why you mostly see it in IP rather than in chips.

A fourth arrangement that is not a scheme but appears constantly: rtl/ contains the blocks and rtl/include/ or rtl/pkg/ contains packages, interfaces and define files. Those are compiled first and often with a different flag (-y, +incdir+), which is Module 26's problem and the reason the filelist's order matters.

6. The Testbench Side

tb/ is more varied than rtl/, because verification environments are built from layers rather than blocks. The near-universal shape, which chip_a shows in miniature:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tb/
├── agents/   per-protocol drivers, monitors, sequencers
├── env/      the environment that instantiates the agents
└── tests/    the individual tests, and the top-level module

The thing worth knowing on day one is which directory holds the top-level module, because that is what the simulator elaborates and what every hierarchy path starts with. Here it is tb/tests/tb_chip.sv, and the log's TB: scope is tb_chip confirms it.

Finding it in an unfamiliar tree is a search for a module that is instantiated by nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# every module defined, minus every module instantiated -> the roots
git grep -h '^ *module ' -- '*.sv' | sed 's/^ *module  *//; s/[ (;].*//' | sort -u > /tmp/defined
git grep -hoE '^ *[a-zA-Z_][a-zA-Z0-9_]* +u_[a-zA-Z0-9_]*' -- '*.sv' |
  sed 's/^ *//; s/ .*//' | sort -u > /tmp/instantiated
comm -23 /tmp/defined /tmp/instantiated

comm -23 prints lines unique to the first file — the modules nobody instantiates, which is the set of candidate top levels. Chapter 13.8 introduced comm, and this is the first of three places in this module where it does the work a loop would otherwise do badly.

The method has an honest limitation, and it is instructive: it depends on an instance-naming convention (u_). A tree that does not follow one needs the module list checked against the filelist instead, which is the more reliable test and the subject of Chapter 23.3.

7. The Map, as a Script

Everything in §3, in the order that answers the most per command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/usr/bin/env bash
# repo-map — the first thing to run in an unfamiliar design repository
set -o nounset

cd -- "${1:-.}" || exit 2
printf '== %s\n\n' "$(pwd -P)"

printf '-- top-level directories\n'
find . -maxdepth 1 -type d ! -name . ! -name .git | sed 's|^\./|   |' | sort

printf '\n-- tracked files by extension\n'
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
  git ls-files | sed 's|.*\.||' | sort | uniq -c | sort -rn | head -10 | sed 's/^/  /'
else
  printf '   (not a git repository — using find)\n'
  find . -type f ! -path '*/.git/*' | sed 's|.*\.||' | sort | uniq -c | sort -rn | head -10 | sed 's/^/  /'
fi

printf '\n-- biggest directories\n'
du -sk ./*/ 2>/dev/null | sort -rn | head -6 | awk '{ printf "   %8.1f MB  %s\n", $1/1024, $2 }'

printf '\n-- entry points (tracked executables)\n'
git ls-files -s 2>/dev/null | awk '$1 ~ /755$/ { print "   " $4 }' || true

printf '\n-- filelists\n'
find . -name '*.f' -o -name '*.filelist' -o -name 'filelist*' 2>/dev/null |
  grep -v '/\.git/' | sed 's|^\./|   |' | sort

Five decisions worth stating:

cd -- "${1:-.}" || exit 2 — the directory is the one argument, defaulting to here, and a bad path exits rather than reporting on the wrong tree. Chapter 21.1's interface and Chapter 20.3's validation, in one line.

pwd -P in the header, so the output names where it really looked — Chapter 22.3 §4.

git rev-parse --is-inside-work-tree as the test for "is this a git repository", because it is the question git itself answers, and the find fallback means the script still works on an exported tarball.

2>/dev/null on the du, because a project area always contains something unreadable and one error per directory would bury the output — Chapter 12.3 §5.

And || true after the awk, because git ls-files fails outside a repository and the script should carry on to the filelist section rather than stopping. That is a deliberate exception to the usual rule, and it is worth a comment in real code.

Practice

  1. Run find . -maxdepth 2 -type d | sort in a repository you work in. Classify every directory as authored, imported or generated, and mark the ones you are not sure about.
  2. Run the extension census. What does the tail tell you about the flow that the top does not?
  3. Run du -sk ./*/ | sort -rn | head. Is the design in the top three?
  4. Find the entry points with git ls-files -s. Then run find . -perm -u+x -type f | head and explain why the two lists differ.
  5. Run ls -F and find one of each marker you can.
  6. Locate every filelist in your repository. How many are there, and do you know which one the regression uses?
  7. Find the top-level testbench module using §6's comm method. If it returns several, work out why.
  8. Then find it the other way — by reading the filelist — and compare the two answers.
  9. Check an arithmetic claim in a log you already have: a sum, a count, an OR of two fields. Was it right?
  10. Write repo-map and run it on three repositories. Which section was most useful, and which did you ignore?
  11. Run repo-map on a directory that is not a git repository and confirm the fallback works.
  12. Give repo-map a path that does not exist and confirm it exits 2 rather than mapping your current directory.

Check Your Understanding

  1. What are the three classes every directory in a design repository falls into?
  2. Why is the classification more useful than the directory names?
  3. What is the strongest naming signal that a directory is generated?
  4. Why use git ls-files rather than find for the extension census?
  5. What does mode 100755 in git ls-files -s tell you that the filesystem's execute bit does not?
  6. In a real repository, where is the bulk of the data, and how does that help you?
  7. What is the authority on which files are actually compiled?
  8. How do you find a top-level module, and what is the weakness of the comm method?

8. Remember

  • Three classes: authored, imported, generated. Everything else in this module follows from which one a directory is.
  • Learn the kinds, not the names. rtl/src/hdl are the same thing.
  • A directory name nobody would type is generated — dates, timestamps, tool names.
  • tree is not installed everywhere; find . -maxdepth 2 -type d is.
  • Census the extensions from git ls-files, not find, so build output cannot pollute it.
  • The tail of the census identifies the flow: .vhd means mixed-language, .sdc versus .xdc names the vendor, .f means filelists.
  • The biggest directories are the generated ones, which is how to spot them without knowing the names.
  • git ls-files -s with mode 100755 finds the intended entry points.
  • ls -F works with no git and no tree: / directory, * executable, @ symlink.
  • The filelist is the authority on what is compiled — not find, and not the directory layout.
  • A hierarchy path like tb_chip.dut.u_axi is what logs print, and walking it back to a file is the first step of most debugging.
  • Treat numbers in a log as checkable. 0xA1 | 0x5C = 0xFD took ten seconds to verify.

9. What Is Next

Chapter 23.2 stops guessing. The three-way classification this chapter asserted from directory names is instead proven, with four tests that work on a repository whose conventions you have never seen — and the one of them that is almost always decisive.

Where this fits

Part of the Linux & Bash curriculum.