Design a 4-Bit Counter Chip Layout

Go from Verilog RTL to a manufacturable GDSII chip layout using open-source tools.

Introduction

30 Second Summary

Every chip inside your phone, laptop, and smart watch started as lines of code that were transformed into a physical circuit layout. That transformation process is called the RTL-to-GDSII flow.

In this project, you will design a 4-bit counter in Verilog, verify it with a simulation, and run the full chip implementation flow to produce a real GDSII layout file using open-source tools.

What You'll Build

A 4-bit synchronous counter taken through the entire ASIC design pipeline, from Verilog code to a manufacturable chip layout.

By the end of this project, you'll have:

  • A working 4-bit counter designed in Verilog, simulated and verified with waveforms.
  • A gate-level netlist synthesized from your RTL using the SkyWater 130nm standard cell library.
  • A complete GDSII chip layout file generated through the full RTL-to-GDSII flow, viewable in KLayout.
  • Secret Mission: Add a max-value flag output to your counter and re-run the full flow to see how the physical chip layout changes.

Are there any prerequisites?

This project assumes familiarity with digital logic concepts like gates, flip-flops, and clocks. You should also have basic command-line experience.

You will need WSL2 (Ubuntu) and Docker already installed on your Windows machine.

Set Up the VLSI Toolchain

To take a chip design from concept to a physical layout, you need several specialized tools working together. Simulators verify your logic, synthesis tools translate code into gates, and layout viewers let you inspect the final chip.

In this step, you will install the full open-source VLSI toolchain inside your WSL2 environment. By the end, you will have simulation, synthesis, and layout tools ready to go.

In this step, get ready to:
  • Install Icarus Verilog, GTKWave, and KLayout for simulation and layout viewing.
  • Install LibreLane for the full RTL-to-GDSII ASIC flow.
  • Create your project directory and verify everything works with a test compile.
Install Icarus Verilog, GTKWave, and KLayout

These three tools handle the simulation and visualization side of chip design. You will install them from the Ubuntu package repositories.

  • Press the Windows key to open your search bar.
  • Type Ubuntu and press Enter to open your WSL2 terminal.
  • Update your package list and install the simulation tools by running this command:
sudo apt-get update && sudo apt-get install -y iverilog gtkwave klayout

What do these tools do?

  • iverilog is Icarus Verilog, an open-source Verilog simulator that compiles your hardware description into an executable simulation.
  • gtkwave is GTKWave, a waveform viewer that displays signal timing diagrams from your simulation output.
  • klayout is KLayout, a layout viewer for inspecting the physical chip design (GDSII files).
  • Verify each tool is accessible by running these version checks:
iverilog -V
gtkwave --version
klayout -v

Each command should print version information without errors.

Command not found?

If any command is not found, re-run the install command above and check for error messages during installation. Make sure you are running these commands inside your WSL2 Ubuntu terminal, not Windows PowerShell.

help me fix a missing tool in my WSL2 Ubuntu terminal

What about GTKWave and the display?

GTKWave and KLayout need a graphical display. Windows 11 includes WSLg, which handles this automatically. If you are on Windows 10, you will need an X11 server like VcXsrv.

Install LibreLane

LibreLane is the tool that takes your Verilog code through the full ASIC implementation flow. It automates synthesis, placement, routing, and GDSII generation using open-source tools inside a Docker container.

  • Install the system-level Python packages that LibreLane requires by running:
sudo apt-get install -y build-essential python3 python3-venv python3-pip python3-tk curl make git

Why all these packages?

LibreLane requires Python 3.10 or higher along with pip, venv, and Tkinter. The other packages (build-essential, curl, make, git) support compiling and downloading dependencies during the flow.

  • Verify your Python version by running:
python3 --version

✔️ I see version 3.10 or higher

Your Python version is compatible with LibreLane. Continue to the next instruction.

ⓧ I see a version lower than 3.10

Your WSL2 Ubuntu distribution ships an older Python. You need to install Python 3.10 or higher using the deadsnakes PPA.

  • Add the deadsnakes PPA and install a newer Python by running:
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install -y python3.12 python3.12-venv python3.12-dev
  • Use update-alternatives to set the new version as the default python3:
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
  • Confirm the update by running python3 --version again. You should now see 3.12 or higher.

ⓧ Command not found

This is unusual after running the apt install above. Try re-running the install command:

sudo apt-get install -y python3 python3-venv python3-pip
  • Then run python3 --version again.
  • Install LibreLane via pip by running:
python3 -m pip install librelane

What is LibreLane?

LibreLane is the actively maintained successor to OpenLane. It orchestrates the full RTL-to-GDSII flow using Yosys for synthesis, OpenROAD for placement and routing, and Magic for DRC/LVS checks.

The --dockerized flag tells LibreLane to run all heavy tools inside a Docker container, so you do not need to install them individually.

  • Run the LibreLane smoke test in dockerized mode to verify everything works:
python3 -m librelane --dockerized --smoke-test

What does the smoke test do?

The smoke test runs a minimal design through every built-in step of the ASIC flow inside Docker. It verifies that synthesis, placement, routing, and all other tools are functioning correctly.

This command downloads the LibreLane Docker image on first run, which may take 5-10 minutes depending on your internet speed. Be patient while it downloads.

The smoke test should finish successfully. You will see progress messages for each flow step.

Smoke test failing?

Make sure Docker Desktop is running on your Windows host. The Docker engine must be accessible from within WSL2.

Verify Docker is working by running docker --version in your WSL2 terminal. If it returns a version number, Docker is accessible. If not, open Docker Desktop on Windows and ensure the WSL2 integration is enabled in Settings > Resources > WSL Integration.

help me fix the LibreLane smoke test failing in WSL2

Create your project directory and verify the toolchain

With all tools installed, you need a dedicated project folder to hold your Verilog source files and configuration.

  • Create your project directory with a src/ subdirectory by running these commands:
mkdir -p ~/counter_project/src
cd ~/counter_project

What is this directory structure for?

The counter_project/ folder is your main project directory. The src/ subdirectory will hold your Verilog source files. LibreLane will look for your design files in this location.

  • Confirm the directories exist by running:
ls ~/counter_project

You should see src listed in the output.

  • Verify Icarus Verilog can compile a quick test by creating a minimal Verilog file:
echo 'module hello; initial begin $display("Hello, VLSI!"); $finish; end endmodule' > /tmp/hello.v
iverilog -o /tmp/hello /tmp/hello.v
vvp /tmp/hello

You should see Hello, VLSI! printed in your terminal. This confirms Icarus Verilog can compile and simulate Verilog code.

  • Clean up the test files by running:
rm /tmp/hello.v /tmp/hello

Not seeing the expected output?

If iverilog reports a syntax error, make sure you copied the echo command exactly as shown (including the single quotes around the Verilog code).

If vvp is not found, re-run the sudo apt-get install -y iverilog command from the first substep.

help me fix my Icarus Verilog test compile in WSL2

Your VLSI toolchain is ready. Next up, you will write the actual 4-bit counter in Verilog and simulate it to verify it counts correctly.

Design the 4-Bit Counter in Verilog

Your toolchain is verified and ready. Now it's time to write the actual hardware design that will eventually become a physical chip layout.

In this step, you'll describe a 4-bit synchronous counter in Verilog, write a testbench to exercise every state, and simulate the design to prove it works before sending it through the synthesis flow.

In this step, get ready to:
  • Write the RTL module for a 4-bit counter with clock, reset, and enable.
  • Write a testbench that exercises reset and enable and generates a waveform file.
  • Compile, simulate, and verify the counter in GTKWave.
Write the RTL module

Every chip starts as an RTL (Register-Transfer Level) description. This is the behavioural description of what the hardware should do, written in a hardware description language.

  • In VS Code, create a new file at src/counter.v inside your counter_project folder.
  • Paste the following Verilog module into the file:
module counter (
    input  wire       clk,
    input  wire       rst,
    input  wire       en,
    output reg  [3:0] count
);

    // On every rising clock edge, check reset then enable
    always @(posedge clk) begin
        if (rst)
            count <= 4'b0000;
        else if (en)
            count <= count + 1;
    end

endmodule

What does this code do?

This module defines a synchronous 4-bit counter with three inputs and one output.

  • clk is the clock signal. The always @(posedge clk) block means logic only executes on the rising edge of the clock.
  • rst is an active-high reset. When high, the counter snaps back to 0000.
  • en is the enable signal. The counter only increments when enable is high and reset is low.
  • count is a 4-bit register output ([3:0]), so it counts from 0 to 15 and wraps back to 0.
  • Save src/counter.v.
  • Confirm the file exists by running:
ls src/counter.v

You should see src/counter.v printed in the output.

File not found?

  • Make sure you are in the ~/counter_project/ directory. Run pwd to check.
  • Check that the src/ subdirectory exists. If not, create it with mkdir src and save the file again.

Help me figure out why my counter.v file isn't appearing in the src directory.

Write the testbench

A testbench is a separate Verilog file that wraps around your design and feeds it stimulus. It is not synthesized into hardware. It exists only to prove that your module behaves correctly before you commit to a physical layout.

  • Create a new file called counter_tb.v in the root of your counter_project folder (not inside src/).
  • Paste the following testbench code into the file:
`timescale 1ns / 1ps

module counter_tb;

    // Declare signals to drive into the counter
    reg        clk;
    reg        rst;
    reg        en;
    wire [3:0] count;

    // Instantiate the counter as "uut" (unit under test)
    counter uut (
        .clk(clk),
        .rst(rst),
        .en(en),
        .count(count)
    );

    // Generate a 10ns clock (toggle every 5ns)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // Main stimulus sequence
    initial begin
        $dumpfile("counter.vcd");
        $dumpvars(0, counter_tb);

        rst = 1;
        en  = 0;
        #20;

        rst = 0;
        en  = 1;
        #200;

        en = 0;
        #30;

        en = 1;
        #100;

        $display("Simulation complete.");
        $finish;
    end

    // Print signal values on every change
    initial begin
        $monitor("Time=%0t rst=%b en=%b count=%b (%0d)",
                 $time, rst, en, count, count);
    end

endmodule

What does this testbench do?

The testbench exercises your counter through three phases and records every signal change to a VCD (Value Change Dump) file.

  • $dumpfile("counter.vcd") and $dumpvars(0, counter_tb) tell the simulator to record all signals into a waveform file.
  • Phase 1: Reset is held high for 20ns. The counter should stay at 0.
  • Phase 2: Reset drops, enable goes high for 200ns. The counter increments on each clock edge (20 edges total, wrapping past 15 back to 0).
  • Phase 3: Enable drops for 30ns (counter holds), then goes high again for 100ns (counter resumes).
  • $monitor prints signal values to the terminal every time any signal changes, so you can also verify in text.
  • Save counter_tb.v.
  • Confirm both files exist by running:
ls src/counter.v counter_tb.v

You should see both src/counter.v and counter_tb.v listed.

Missing one of the files?

  • Double-check that counter_tb.v is in the project root (~/counter_project/), not inside src/.
  • Make sure you saved the file after pasting the code. Check with cat counter_tb.v to confirm content is present.

Help me troubleshoot why my testbench file is missing.

✔️ Awesome, I've got everything!

Great. Make sure both files are saved before moving on to compilation.

ⓧ I'd like to double check the full code

Here is the complete counter.v:

module counter (
    input  wire       clk,
    input  wire       rst,
    input  wire       en,
    output reg  [3:0] count
);

    always @(posedge clk) begin
        if (rst)
            count <= 4'b0000;
        else if (en)
            count <= count + 1;
    end

endmodule

And the complete counter_tb.v:

`timescale 1ns / 1ps

module counter_tb;

    reg        clk;
    reg        rst;
    reg        en;
    wire [3:0] count;

    counter uut (
        .clk(clk),
        .rst(rst),
        .en(en),
        .count(count)
    );

    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    initial begin
        $dumpfile("counter.vcd");
        $dumpvars(0, counter_tb);

        rst = 1;
        en  = 0;
        #20;

        rst = 0;
        en  = 1;
        #200;

        en = 0;
        #30;

        en = 1;
        #100;

        $display("Simulation complete.");
        $finish;
    end

    initial begin
        $monitor("Time=%0t rst=%b en=%b count=%b (%0d)",
                 $time, rst, en, count, count);
    end

endmodule
Compile and simulate the design

With both files written, you can now compile them into a simulation binary and run it. Icarus Verilog uses a two-step flow: iverilog compiles your source files, then vvp executes the compiled simulation.

  • Compile both Verilog files together by running this command:
iverilog -o counter_sim src/counter.v counter_tb.v

What does this command do?

  • iverilog is the Icarus Verilog compiler.
  • -o counter_sim names the compiled output file counter_sim.
  • The two source files (src/counter.v and counter_tb.v) are passed as inputs. The compiler finds the root module (counter_tb) automatically because it is not instantiated by anything else.
  • Run the simulation to generate the waveform file:
vvp counter_sim

You should see timestamped output in your terminal showing the counter incrementing from 0 through 15 and wrapping around. The final line reads Simulation complete. followed by the simulation ending.

  • Confirm the VCD waveform file was generated:
ls counter.vcd

You should see counter.vcd listed. This file contains every signal transition from your simulation.

Seeing a syntax error during compilation?

  • Check that the backtick in `timescale 1ns / 1ps is a backtick (`` ` ``), not a single quote (').
  • Verify that counter.v uses module counter (lowercase) and the testbench instantiates it as counter uut with matching port names.
  • If you see "No top level modules", make sure both files are passed to iverilog in the same command.

Help me debug a compilation error from iverilog.

  • Open the waveform in GTKWave by running:
gtkwave counter.vcd &

What should I see in GTKWave?

GTKWave opens a graphical window. To view your signals:

  • In the top-left panel, click counter_tb to expand the module hierarchy.
  • Click uut to see the counter's signals (clk, rst, en, count).
  • Drag the signals into the Signals pane on the right to display them as waveforms.
  • You should see count incrementing from 0000 to 1111 (0 to 15) and wrapping back, pausing when en is low.

The & runs GTKWave in the background

Adding & at the end of the command lets GTKWave run without blocking your terminal. You can continue typing commands while the waveform viewer stays open.

GTKWave window not appearing?

  • GTKWave requires a graphical display. On Windows 11 with WSL2, WSLg handles this automatically. If you are on Windows 10, you need an X11 server like VcXsrv running.
  • Try running echo $DISPLAY in your terminal. If the output is empty, your display is not configured. On Windows 11, restarting your WSL2 instance usually fixes this.

Help me get GTKWave to display in WSL2.

Your counter is verified and working. Next up, you'll feed this design into the full RTL-to-GDSII flow and watch it transform into a physical chip layout.

Run the RTL-to-GDSII Flow

Your counter design is verified and working. Now it is time for the most exciting part of chip design: turning your abstract Verilog into a physical chip layout.

In this step, you will configure and run the full ASIC implementation flow using LibreLane. This takes your counter through synthesis, floorplanning, placement, clock tree synthesis, routing, and design rule checks to produce a GDSII file. That is the same file format used to manufacture real silicon chips.

In this step, get ready to:
  • Create the LibreLane configuration file that describes your design.
  • Execute the full RTL-to-GDSII flow in dockerized mode.
  • Inspect the synthesis report and view the physical chip layout in KLayout.
Create the LibreLane configuration

LibreLane needs a JSON configuration file that tells it everything about your design: the module name, source files, clock information, and which fabrication technology to target.

  • Create a config.json file in the root of your counter_project folder by running this command:
touch ~/counter_project/config.json
  • Open config.json in VS Code and paste the following configuration:
{
    "DESIGN_NAME": "counter",
    "VERILOG_FILES": ["dir::src/counter.v"],
    "CLOCK_PORT": "clk",
    "CLOCK_PERIOD": 10,
    "pdk": "sky130A",
    "STD_CELL_LIBRARY": "sky130_fd_sc_hd"
}

What does this configuration do?

  • DESIGN_NAME tells LibreLane which Verilog module is the top-level design.
  • VERILOG_FILES points to your source file. The dir:: prefix resolves paths relative to the folder containing config.json.
  • CLOCK_PORT and CLOCK_PERIOD define your clock signal name and its period in nanoseconds (10 ns = 100 MHz).
  • pdk selects the SkyWater 130nm process design kit, and STD_CELL_LIBRARY chooses the high-density standard cell library from that PDK.

What is a PDK?

A Process Design Kit (PDK) is a collection of files from a chip foundry that describes how to manufacture circuits on their process. The SkyWater 130nm PDK is an open-source PDK that enables anyone to design real chips without paying for proprietary foundry access.

The sky130_fd_sc_hd library provides pre-designed logic gates (AND, OR, flip-flops) that the synthesis tool maps your Verilog to.

  • Save config.json.
  • Confirm the file exists by running:
ls ~/counter_project/config.json

You should see the path printed back, confirming the file is in place.

File not found?

  • Make sure you created config.json directly inside ~/counter_project/, not inside the src/ subfolder.
  • Check for typos in the filename. It must be exactly config.json (lowercase).

Still stuck? Help me find my config.json file

Execute the RTL-to-GDSII flow

With your configuration ready, LibreLane can now run the entire chip implementation pipeline in a single command. The Classic flow is the default and replicates the same stages used for real chip tapeouts.

  • Navigate to your project directory by running:
cd ~/counter_project
  • Launch the full flow in dockerized mode by running this command:
python3 -m librelane --dockerized --pdk-root ~/.ciel config.json

What does this command do?

  • --dockerized runs the entire ASIC toolchain inside a Docker container so you do not need to install Yosys, OpenROAD, Magic, or Netgen locally.
  • --pdk-root ~/.ciel points LibreLane to the default PDK storage directory. If this is your first run, LibreLane will automatically download the SkyWater 130nm PDK into this folder.
  • config.json is the configuration file you just created.

The Classic flow will automatically execute synthesis (Yosys), floorplanning, placement (OpenROAD), clock tree synthesis (TritonCTS), routing (TritonRoute), and DRC/LVS checks (Magic/Netgen).

How long will this take?

On first run, LibreLane downloads the Docker image and the SkyWater PDK. This can take 5-10 minutes depending on your internet speed. Subsequent runs skip the download and complete much faster.

You will see a progress bar at the bottom of your terminal as each stage completes. Wait until you see the flow finish with no errors before continuing.

Once the flow completes, you should see output indicating all stages passed. LibreLane creates a runs/ directory inside your project folder containing all results.

Flow fails with a Docker error?

  • Make sure Docker Desktop is running. Check by running docker ps in your terminal.
  • If you see a permission denied error, confirm that WSL Integration is enabled in Docker Desktop under **Settings**, then **Resources**, then **WSL Integration**.
  • If the PDK download fails, check your internet connection and try running the command again.

Still stuck? Help me fix my LibreLane flow error

Inspect the synthesis report and chip layout

The flow has produced a wealth of output. The most important artifacts are the synthesis report (showing which logic cells your design was mapped to) and the final GDSII layout file.

  • List the run directories to find your run tag by running:
ls ~/counter_project/runs/

You should see a folder with a timestamp-based name. This is your run tag.

  • Examine the synthesis report to see which standard cells were used. Navigate to the synthesis results by running:
cat ~/counter_project/runs/*/results/synthesis/counter.stat.rpt

What should I see in the synthesis report?

The report lists the standard cells that Yosys mapped your Verilog to. You will see flip-flops (for the 4-bit register), logic gates (for the increment operation), and a total cell count. This is your design translated from behavioral code to physical gate-level components.

The exact cell names follow the sky130_fd_sc_hd naming convention. Each cell represents a pre-characterized piece of silicon that the foundry knows how to manufacture.

  • Locate the final GDSII file by running:
ls ~/counter_project/runs/*/results/final/gds/counter.gds

You should see the path to counter.gds printed. This is your chip layout file.

  • Open the GDSII file in KLayout to view the physical chip layout by running:
klayout ~/counter_project/runs/*/results/final/gds/counter.gds

What am I looking at in KLayout?

KLayout shows your counter as a physical chip layout. The colored rectangles represent different metal layers used for wiring. The small rectangular blocks are standard cells (your flip-flops and logic gates) placed in rows. The thin lines connecting them are metal routing tracks.

This is the exact data that would be sent to a foundry for manufacturing. You have taken a Verilog description all the way to a manufacturable layout using the same open-source tools and SkyWater 130nm process used for real silicon tapeouts.

  • Zoom in on different parts of the layout to explore the standard cells and routing.
  • Use **Ctrl+Shift+L** in KLayout to toggle the layer panel, which shows you each metal and via layer separately.

KLayout window not appearing?

  • KLayout requires a graphical display. If you are on Windows 11, WSLg handles this automatically. On Windows 10, you need an X11 server like VcXsrv running.
  • Alternatively, copy the .gds file to your Windows filesystem and open it with KLayout installed on Windows directly.

Still stuck? Help me open my GDSII file in KLayout

✔️ Awesome, I've got everything!

Great. Double check you have saved config.json and that your flow completed successfully with a GDSII file generated.

ⓧ I'd like to double check the full code

Here is the complete config.json file:

{
    "DESIGN_NAME": "counter",
    "VERILOG_FILES": ["dir::src/counter.v"],
    "CLOCK_PORT": "clk",
    "CLOCK_PERIOD": 10,
    "pdk": "sky130A",
    "STD_CELL_LIBRARY": "sky130_fd_sc_hd"
}

You have successfully taken your 4-bit counter from Verilog all the way to a physical chip layout. Next up: the Secret Mission challenges you to modify your design and see how changes to your RTL affect the physical layout.

Secret mission

Add a Max-Value Flag Output

Your counter counts from 0 to 15, but what if other parts of the chip need to know when it hits its maximum? Add a combinational output that flags the max value, then re-run the full ASIC flow and see how even a small logic change ripples through the physical layout.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally inside WSL2, so there are no ongoing cloud costs. Decide whether to keep your resources in place, pause Docker, or delete everything entirely.

Resources you used:

  • Project directory at ~/counter_project/ containing Verilog source, testbench, config, VCD, and GDSII output files.
  • Icarus Verilog (iverilog apt package) for simulation.
  • GTKWave (gtkwave apt package) for waveform viewing.
  • KLayout (klayout apt package) for GDSII layout viewing.
  • LibreLane (pip package) for the RTL-to-GDSII ASIC flow.
  • Docker images pulled by LibreLane for the synthesis and place-and-route tools.

Keep everything running

No action needed. Choose this if you plan to continue experimenting with your counter design or try other VLSI projects.

  • Your project files stay at ~/counter_project/.
  • All installed tools (Icarus Verilog, GTKWave, KLayout, LibreLane) remain available for future designs.
  • Docker images for LibreLane stay cached so future flow runs start faster.

Pause - I'll come back to this later

Free up Docker resources while keeping your project files and installed tools intact.

  • Stop any running Docker containers by running:
docker container prune -f
  • Your project files, installed apt packages, and LibreLane pip package all remain in place.
  • When you return, you can re-run the LibreLane flow immediately without reinstalling anything.

Delete - I don't want to use this again

Remove all project resources, tools, and Docker images to start completely fresh.

  • Delete your project directory by running:
rm -rf ~/counter_project
  • Uninstall LibreLane by running:
python3 -m pip uninstall -y librelane
  • Remove the simulation and layout viewing tools by running:
sudo apt-get remove --purge -y iverilog gtkwave klayout
  • Remove the Docker images pulled by LibreLane by running:
docker image prune -a -f

What does docker image prune -a do?

The -a flag removes all images not currently used by a running container, not just dangling ones. This clears the LibreLane toolchain images that were downloaded during the flow. If you use Docker for other projects, check your images with docker images first to make sure you only remove what you intend to.

  • Clean up leftover apt dependencies by running:
sudo apt-get autoremove -y

That's a Wrap!

That's a Wrap!

Nice work! You just took a digital design from concept to a physical chip layout using the same open-source tools and process that real chips are fabricated with.

You've learned how to:

  • Design a 4-bit synchronous counter in Verilog with clock, reset, and enable inputs.
  • Write a testbench, simulate your design, and verify correct behaviour through waveform analysis in GTKWave.
  • Run the complete RTL-to-GDSII ASIC implementation flow using LibreLane and the SkyWater 130nm PDK, producing a manufacturable chip layout viewable in KLayout.
  • Secret Mission: Added combinational logic (a max-value flag output) and observed how it changed the physical layout with additional standard cells and routing.

Ready to quiz yourself?