VLSI

From DiLab
Revision as of 17:34, 19 June 2026 by Leo (talk | contribs) (Expected results)
Jump to: navigation, search

Open source VLSI design notes.

From Verilog/VHDL to GDSII for SKY or IHP technologies.

Prerequisites

These notes assume the host has Linux, e.g. Ubuntu 24.04 set up. All tools will be running under it.

Open Tools

Essential OSS HW design tools

  • gtkwave - Waveform viewer
  • iverilog - Icarus Verilog compiler
  • Verilator - compile RTL to C++, faster simulations
  • Yosys - RTL to gate level netlist
  • SymbiFlow - Toolchain to FPGA
  • Magic VLSI - transistor level layout design editor
  • KLayout - viewer and editor of GDSII files
  • OpenRoad - Automates floorplanning, placement, routing and timing.
  • OpenLane - Automated design flow, from verilog to GDSII, uses the tools above.

Open Technology PDKs

Process development kits (PDK) available for OSS VLSI:

  • IHP SG13G2 PDK
    • This is a 130nm BiCMOS process from the Leibniz Institute for High Performance Microelectronics, which generally offers higher performance (faster transitions) than Sky130.
    • ReadTheDocs

Other, less popular options:

  • GlobalFoundries 180nm MCU (GF180MCU)
    • A mature 180nm CMOS process with 5 layers of metal, widely used for analog and mixed-signal design. It is fully supported by Efabless for open-source shuttle programs.
  • ASAP7 (Arizona State Academic Process)
    • A 7nm predictive PDK used exclusively for academic research and educational purposes. It is often used for evaluating next-generation PnR flows (e.g., using Synopsys tools).
  • SCMOS (Scalable CMOS)
    • An older "Lambda-based" design rule set used before modern open foundry efforts, helpful for learning layout concepts, though not used for modern, high-performance silicon fabrication.

Tapeout

TinyTapeout: from idea/design to chip/PCB

Setup Openline2

9https://openlane2.readthedocs.io/en/latest/getting_started/installation_overview.html The advised path] is to setup NIX environment and then run openline2 from there, rather than using a dockerized version.

Install Nix

Set up the Nix environment:

sudo apt-get install -y curl

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm --extra-conf "
   extra-substituters = https://openlane.cachix.org
   extra-trusted-public-keys = openlane.cachix.org-1:qqdwh+QMNGmZAuyeQJTH9ErW57OWSvdtuwfBKdS254E=
"

Make sure to close all terminals after you’re done with this step.

If have Nix already

nix-env -f "<nixpkgs>" -iA cachix
sudo env PATH="$PATH" cachix use openlane
sudo pkill nix-daemon

Install OpenLane 2 after Nix

git clone https://github.com/efabless/openlane2
nix-shell
openlane --smoke-test

Other tools

There are many other tools you could setup separately, just know that openlane2 expects and is sensitive to the versions of the tools, and may not work if your tool is older or newer.

My local setup

This is how I use the Openlane2 tools after the setup.

One important note is that they generate MANY intermediate and log files in the "runs" directory under your project, every time you run openlane. Usually my project is mapped toa cloud drive such as Dropbox, and spamming it with tons of files may create confusion when receiving an email later that someone has deleted 2000+ files from your account. Therefore I save the runs locally, outside the project directory. To achieve that, I run the following commands:

   cd git.local/openlane2
   nix-shell
   openlane --smoke_test               # Sanity test for the tools (optional)
   cd your/project/

   run_openlane_local.sh config.json   # will save run logs under work.local/... (recommended)
       or
   opennlane config.json               # will save the run logs in the project directory work/... (cloud?!)

Setting up a new project

A project is usually kept in one directory. It may contain:

  • config.json file with the project options. Potentially you could have several configs, for different technologies, although probably berret to have a separate project for each.
  • src/ directory with the Verilog or VHDL files
  • runs/ directory with many intermediate and log files as populated by the openlane2 tool(s).
    • The runs/ has a RUN<timestamp> folder for each openalne run. Each of those will have the following:
      • Folders from 01-... and up to 74-... or more with files according to the respective run steps
      • final/ directory with the final results, including the magic and klayout and gds files with the final layout, spice files, etc.
      • tmp - temporary directory.
      • flow, warning and error logs.

The config.json file

Technically other formats such as yaml are also supported, but lets stick to json. Here is an example of a simple config.json file for SKY PDK:

   {
       "DESIGN_NAME": "counter",
       "VERILOG_FILES": ["src/counter.v"],
       "CLOCK_PORT": "clk",
       "CLOCK_PERIOD": 10.0,
       "FP_CORE_UTIL": 40,
       "RT_MAX_LAYER": "met4"
   }

Here is another for IHP PDK:

   {
       "DESIGN_NAME": "counter",
       "VERILOG_FILES": ["src/counter.v"],
       "PDK": "sg13g2",
       "CLOCK_PORT": "clk",
       "CLOCK_PERIOD": 10.0,
       "FP_CORE_UTIL": 40,
       "RT_MAX_LAYER": "Metal4"
   }

Here is a bit more with the die area:

   {
       "DESIGN_NAME": "adder_16bit",
       "VERILOG_FILES": "dir::src/adder_16bit.v",
       "CLOCK_TREE_SYNTH": false,
       "CLOCK_PORT": null,
       "FP_SIZING": "absolute",
       "DIE_AREA": "0 0 100 100",
       "PL_TARGET_DENSITY": 0.65,
       "VDD_NETS": ["vccd1"],
       "GND_NETS": ["vssd1"],
       "DIODE_INSERTION_STRATEGY": 4
   }

Usecase: Adder

This is logic only, no registers

config.json

   {
       "DESIGN_NAME": "adder_16bit",
       "VERILOG_FILES": "dir::src/adder_16bit.v",
       "CLOCK_TREE_SYNTH": false,
       "CLOCK_PORT": null,
       "FP_SIZING": "absolute",
       "DIE_AREA": "0 0 100 100",
       "PL_TARGET_DENSITY": 0.65,
       "VDD_NETS": ["vccd1"],
       "GND_NETS": ["vssd1"],
       "DIODE_INSERTION_STRATEGY": 4
   }

Verilog source (src/adder_16bit.v):

   `default_nettype none
   module adder_16bit (
       input  wire [15:0] a,
       input  wire [15:0] b,
       input  wire        cin,
       output wire [15:0] sum,
       output wire        cout
   );
       assign {cout, sum} = a + b + cin;
   endmodule

Usecase: Simple counter

This design has a few registers

config.json

   {
       "DESIGN_NAME": "counter",
       "VERILOG_FILES": ["src/counter.v"],
       "CLOCK_PORT": "clk",
       "CLOCK_PERIOD": 10.0,
       "FP_CORE_UTIL": 40,
       "RT_MAX_LAYER": "met4"
   }

Verilog source (counter.v):

   module counter (
       input wire clk,
       input wire reset,
       output reg [15:0] count
   );
       always @(posedge clk or posedge reset) begin
           if (reset)
               count <= 16'b0;
           else
               count <= count + 1'b1;
       end
   endmodule

Usecase: SRAM integration

Note, this design may still have unresolved issues (FIXME). The magic tool has some issues with the sram implementation, therefore there are a few workarounds in the config file to depend mostly on klayout for the final steps.

config.json

 {
   "DESIGN_NAME": "top_module",
   "VERILOG_FILES": [
     "dir::src/top_module.v"
   ],
   "CLOCK_PORT": "clk",
   "CLOCK_PERIOD": 10.0,

   "FP_SIZING": "absolute",
   "DIE_AREA": [0.0, 0.0, 1000.0, 1000.0],

   "VDD_NETS": ["vccd1"],
   "GND_NETS": ["vssd1"],
   "PDN_MACRO_CONNECTIONS": [
     "sram_inst vccd1 vssd1 vccd1 vssd1"
   ],
   
   "PRIMARY_GDSII_STREAMOUT_TOOL": "klayout",
   "RUN_KLAYOUT_XOR": false,
   "RUN_MAGIC_STREAMOUT": false,
   "RUN_MAGIC_DRC": false,
   "MAGIC_EXT_USE_GDS": false,
   "MAGIC_INCLUDE_GDS_POINTERS": false,

   "MACROS": {
     "sky130_sram_1kbyte_1rw1r_32x256_8": {
       "instances": {
         "sram_inst": {
           "location": [200.0, 200.0],
           "orientation": "R0"
         }
       },
       "vh": [
         "dir::src/sram_macro.v"
       ],
       "lef": [
         "dir::macro/sky130_sram_1kbyte_1rw1r_32x256_8.lef"
       ],
       "gds": [
         "dir::macro/sky130_sram_1kbyte_1rw1r_32x256_8.gds"
       ],
       "lib": {
         "*": [
           "dir::macro/sky130_sram_1kbyte_1rw1r_32x256_8_TT_1p8V_25C.lib"
         ]
       }
     }
   }
 }

Sources: src/

top_module.v

module top_module (
// `ifdef USE_POWER_PINS
//     inout vccd1,
//     inout vssd1,
// `endif
    input clk, 
    input rst_n, 
    input req, 
    input we, 
    input [7:0] addr, 
    input [31:0] data_in, 
    output reg [31:0] data_out, 
    output reg ready 
);

    wire [31:0] sram_dout; 
    
    // SRAM macro instantiation
    sky130_sram_1kbyte_1rw1r_32x256_8 sram_inst (
// `ifdef USE_POWER_PINS
//         .vccd1(vccd1),
//         .vssd1(vssd1),
// `endif
        // Port 0: RW
        .clk0(clk),
        .csb0(~req),
        .web0(~we),
        .wmask0(4'b1111), // 4-bit mask set to high to write all 32 bits
        .addr0(addr),
        .din0(data_in),
        .dout0(sram_dout),
        
        // Port 1: R (Tied off as it is unused in this design)
        .clk1(1'b0),
        .csb1(1'b1),      // Active low, 1 disables the port
        .addr1(8'b0),
        .dout1()
    );

    always @(posedge clk or negedge rst_n) begin 
        if (!rst_n) begin 
            data_out <= 32'b0; 
            ready <= 1'b0; 
        end else begin 
            ready <= req; 
            if (req && !we) begin 
                data_out <= sram_dout; 
            end 
        end 
    end 
endmodule

sram_macro.v

   (* blackbox *)
   module sky130_sram_1kbyte_1rw1r_32x256_8 (
   `ifdef USE_POWER_PINS
       inout vccd1,
       inout vssd1,
   `endif
       // Port 0: RW
       input clk0,
       input csb0,
       input web0,
       input [3:0] wmask0,
       input [7:0] addr0,
       input [31:0] din0,
       output [31:0] dout0,
       
       // Port 1: R
       input clk1,
       input csb1,
       input [7:0] addr1,
       output [31:0] dout1
   );
   endmodule

Sources: macro/

Note that there is another directory "macro/" where the files from sram PDK are placed. This folder has the following files:

   macro/sky130_sram_1kbyte_1rw1r_32x256_8.gds
   macro/sky130_sram_1kbyte_1rw1r_32x256_8.lef
   macro/sky130_sram_1kbyte_1rw1r_32x256_8.v
   macro/sky130_sram_1kbyte_1rw1r_32x256_8_TT_1p8V_25C.lib

These have to be obtained from the PDK provider. Here is more detailed documentation: Designing a chip with an OpenRAM (sky130)

Usecase: NeoRV32 MCU

NeoRV32 is an open source MCU with many peripheral options written in VHDL.

VHDL to Verilog

Openlane2 is using Yosis for synthesis, which natively supports Verilog. Therefore we need to convert to a flat Verilog file. Therefore clone the second repository recursively:

   git clone --recursive -j8 git@github.com:stnolting/neorv32-verilog.git
   cd neorv32-verilog

Before the convertion go to the VHDL file and enable/disable the features you want. Consider agressively disabling and downsizing everything to the minimum at least for the first runs so that the tools (e.g. yosys) can handle such a massive flat file.

   cd git.local/neorv32-verilog/src/
   edit* neorv32_verilog_wrapper.vhd

Go towards the end of the file and change true and false appropriately. Look for lines below the:

architecture neorv32_verilog_wrapper_rtl of neorv32_verilog_wrapper is
begin
  ...
  generic map ( -- [note] add configuration options as required

Consider the following:

  • "false" every "true" except as below
   -- Internal Instruction memory (IMEM) --
   IMEM_EN             => true,        -- implement processor-internal instruction memory
   IMEM_SIZE           => 16*16,       -- size of processor-internal instruction memory in bytes
   -- Internal Data memory (DMEM) --
   DMEM_EN             => true,        -- implement processor-internal data memory
   DMEM_SIZE           => 8*32,        -- size of processor-internal data memory in bytes
   -- CPU Caches --
   ICACHE_EN           => true,        -- implement instruction cache (i-cache)
   ICACHE_NUM_BLOCKS   => 4,           -- i-cache: number of blocks (min 1), has to be a power of 2
   DCACHE_EN           => true,        -- implement data cache (d-cache)
   DCACHE_NUM_BLOCKS   => 4,           -- d-cache: number of blocks (min 1), has to be a power of 2
   CACHE_BLOCK_SIZE    => 64,          -- i-cache/d-cache: block size in bytes (min 4), has to be a power of 2
...
   IO_UART0_EN         => true,        -- implement primary universal asynchronous receiver/transmitter (UART0)?
... 
   IO_PWM_NUM          => 0,           -- number of PWM channels to implement (0..32)


Install the ghdl for conversion unless already installed, and start the conversion process. Move the Verilog file to your project sourde directory when done:

   sudo apt-get install ghdl
   make check
   make convert
   mv src/neorv32_verilog_wrapper.v <your-project-dir>/src/

config.json

Some comments about the config file:

  • Make sure to edit the vhdl and enable/disable only the components that you need. Or the tools may crash even with extra stack size on such a large flat file.
  • "RUN_LINTER": false - othrvise here be errors on such a large flat file.
  • You may need to specify a lot of size on the die:
   "FP_SIZING": "absolute",
   "DIE_AREA": [0, 0, 1000, 1000],
   "CORE_AREA": [15, 15, 985, 985]
  • Alternatively let the tools decide the size:
   "FP_SIZING": "relative",
   "FP_CORE_UTIL": 35,

The config.json file:

   {
       "DESIGN_NAME": "neorv32_verilog_wrapper",
       "VERILOG_FILES": [
           "dir::src/neorv32_verilog_wrapper.v"
       ],
       "CLOCK_PORT": "clk_i",
       "CLOCK_PERIOD": 25.0,
       "PDK": "sky130A",
       "STD_CELL_LIBRARY": "sky130_fd_sc_hd",
       "FP_SIZING": "relative",
       "FP_CORE_UTIL": 35,
       "PL_TARGET_DENSITY_PCT": 40,
       "SYNTH_STRATEGY": "AREA 0",
       "VDD_NETS": [
           "vccd1"
       ],
       "GND_NETS": [
           "vssd1"
       ],
       "RUN_ANTENNA_REPAIR": true,
       "RUN_HEURISTIC_DIODE_INSERTION": true,
       "RUN_LINTER": false,

       "_comment": 
       {
       "FP_SIZING": "absolute",
       "DIE_AREA": [0, 0, 1000, 1000],
       "CORE_AREA": [15, 15, 985, 985]
       }
   }

Expected results

If all goes well, after an hour or so you may see the final remarks of the design in the terminal before it finishes. A few important bits there:

  1. . DRC & LVS (Passed ✅)
      • DRC (Design Rule Check): The geometry of your standard cells, power straps, and routing wires strictly obeys the Sky130 foundry's physical manufacturing rules (minimum spacing, width, etc.).
      • LVS (Layout vs. Schematic): The final geometric layout perfectly matches your synthesized Verilog netlist. No unexpected shorts or open circuits were created during routing.
  2. . Antenna Violations (Failed 𐄂 - 8 Violations)
    • During plasma etching in fabrication, long metal wires act like antennas, collecting electrostatic charge that can blow out the thin gate oxides of connected transistors.
    • OpenLane attempted to fix these automatically (since we enabled RUN_ANTENNA_REPAIR), usually by jumping to higher metal layers or inserting "tie-down" diodes near the gates.
    • 8 violations simply mean a few nets are still too long relative to their connected gate area. In a real tape-out, you would manually inspect these in the GUI and force diode insertion or add routing blockages.
  3. . Max Slew & Max Cap Violations (Warnings)
    • These are electrical rule violations indicating that some signals are transitioning too slowly (slew) or driving too much load (capacitance).
    • Because we relied on OpenLane's generic fallback constraints rather than providing a custom .sdc (Synopsys Design Constraints) file, the timing-driven routing and buffer insertion heuristics were not strictly optimized for your specific 25ns clock period across all extreme temperature/voltage corners.
    • For a functional test run, these are completely safe to ignore.

OpenRoad

Now it is time to look at the generated layout.

# Force software-based OpenGL rendering
export LIBGL_ALWAYS_SOFTWARE=1

# Force Qt to use the X11 backend (bypassing native Wayland issues)
export QT_QPA_PLATFORM=xcb 

openroad -gui

Then from the TCL commands console at the bottom of the OpenROAD GUI open your new design:

 read_db runs/RUN_.../final/odb/neorv32_verilog_wrapper.odb

There in the tool you can proceed to the Display Control panel on the left to hide standard cells and inspect the routing layers.

  • Under the Visibility tab, uncheck Instances (or specifically StdCells) to hide the internal logic gates.
  • Expand the Layers menu. Here, you can toggle individual routing layers (met1, met2, met3, met4, met5) and vias to visually trace the physical connections across the die.

To jump directly to the physical wires that caused the antenna failures:

  • In the top menu bar, navigate to Tools -> DRC Viewer.
  • Click Load and navigate to the antenna report generated during your run. This is typically located at:

runs/RUN_.../reports/routing/antenna.rpt (or an equivalent .json or .lyrdb file in the signoff/routing reports directory).

  • The DRC Viewer will open a list of the violations, if any.
  • Click on any violation in the list. The main camera will instantly pan, zoom, and highlight the exact wire segment and layer where the charge accumulation risk occurs.