VLSI
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:
- SKY 130 PDK - Process Design Kit by Google and Efabless
- 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 runs/ has a RUN<timestamp> folder for each openalne run. Each of those will have the following:
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.