659 lines
17 KiB
Text
659 lines
17 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "uINjDJNf39eD"
|
|
},
|
|
"source": [
|
|
"# Semicustom digital design crashbook\n",
|
|
"\n",
|
|
"## Sources\n",
|
|
"\n",
|
|
"### RTL description (Verilog)\n",
|
|
"\n",
|
|
"The OpenROAD workflow takes the circuit's RTL description as an input. For instance, it can be a three bits XOR gate.\n",
|
|
"\n",
|
|
"\n",
|
|
"<blockquote><details>\n",
|
|
"\n",
|
|
"<summary>\n",
|
|
" \n",
|
|
"#### ↕️ Types in Verilog\n",
|
|
"\n",
|
|
"</summary>\n",
|
|
" \n",
|
|
"```verilog\n",
|
|
"// Three scalar nets\n",
|
|
"wire op_b, op_a, result;\n",
|
|
"// One 16-bit net\n",
|
|
"wire [15:0] word_bus;\n",
|
|
"// 1K-array of 8-bit nets\n",
|
|
"wire [7:0] byte_array [0:1023];\n",
|
|
"```\n",
|
|
" \n",
|
|
"</details></blockquote>\n",
|
|
"\n",
|
|
"<blockquote><details>\n",
|
|
"\n",
|
|
"<summary>\n",
|
|
" \n",
|
|
"#### ↕️ Assignation (non-blocking) in Verilog\n",
|
|
"\n",
|
|
"</summary>\n",
|
|
" \n",
|
|
"```verilog\n",
|
|
"// 16-bit, hexadecimal constant\n",
|
|
"assign address = 16'hCAFE;\n",
|
|
"// Unsized, decimal constant\n",
|
|
"assign counter = 'd42;\n",
|
|
"// 1-bit, binary constant\n",
|
|
"assign answer = 1'b1;\n",
|
|
"\n",
|
|
"// Ternary assignation\n",
|
|
"assign muxed = which ? source_1 : source_2;\n",
|
|
"\n",
|
|
"// Concatenation\n",
|
|
"assign padded_packet = {5'b00000,body,suffix};\n",
|
|
"// Replication\n",
|
|
"assign odd_mask = {10{2'b10}};\n",
|
|
"\n",
|
|
"// Indexing\n",
|
|
"assign one_bit = bus[4];\n",
|
|
"assign bits = bus[15:12];\n",
|
|
"```\n",
|
|
" \n",
|
|
"</details></blockquote>\n",
|
|
"\n",
|
|
"<blockquote><details>\n",
|
|
"\n",
|
|
"<summary>\n",
|
|
" \n",
|
|
"#### ↕️ Operators in Verilog\n",
|
|
"\n",
|
|
"</summary>\n",
|
|
" \n",
|
|
"```verilog\n",
|
|
"// Addition, substraction, negation\n",
|
|
"assign sum = op_a + op_b; assign sub = op_a + op_b; assign opp = -op_a\n",
|
|
"// Multiplication, division, modulo\n",
|
|
"assign prod = op_a * op_b; assign div = op_a / op_b; assign rem = op_a & op_b\n",
|
|
" \n",
|
|
"// Bitwise not, or, and, xor\n",
|
|
"assign n = ~m; assign a = b | c; assign d = e & f; assign x = y ^ z\n",
|
|
"\n",
|
|
"// Logical not, and, or\n",
|
|
"assign ans = !v; assign ans = v || w; assign ans = v && w;\n",
|
|
"// Logical equality, difference\n",
|
|
"assign ans = v == w; assign ans = v != w;\n",
|
|
"// Relations (strictly) greater, (strictly) lower than\n",
|
|
"assign sg = a > b; assign gt = a >= b; assign sl = a < b; assign lt = a <= b;\n",
|
|
" \n",
|
|
"// Left, right shift by n bits\n",
|
|
"assign l << n; assign r >> n;\n",
|
|
"// Left, right arithmetic shift by n bits\n",
|
|
"assign l <<< n; assign r >>> n;\n",
|
|
"```\n",
|
|
" \n",
|
|
"</details></blockquote>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "gpgkIYB739Ii"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile v/xor3.v\n",
|
|
"module xor3(\n",
|
|
" input wire a,\n",
|
|
" input wire b,\n",
|
|
" input wire c,\n",
|
|
" output wire out\n",
|
|
");\n",
|
|
" assign out = a ^ b ^ c;\n",
|
|
"endmodule"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "hp8h5vH8TUXr"
|
|
},
|
|
"source": [
|
|
"### Configuration file (JSON)\n",
|
|
"\n",
|
|
"A configuration file should be provided. It describes constraints and strategies applied during synthesis and implementation of the circuit."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"id": "rbT-vP0h0enK"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile build/config.json\n",
|
|
"{\n",
|
|
" \"DESIGN_NAME\": \"xor3\",\n",
|
|
" \"VERILOG_FILES\": \"dir::../v/xor3.v\",\n",
|
|
" \"CLOCK_TREE_SYNTH\": false,\n",
|
|
" \"CLOCK_PORT\": null,\n",
|
|
" \"FP_SIZING\": \"absolute\",\n",
|
|
" \"DIE_AREA\": \"0 0 35 45\",\n",
|
|
" \"FP_PDN_AUTO_ADJUST\": false,\n",
|
|
" \"FP_PDN_VOFFSET\": 0,\n",
|
|
" \"FP_PDN_HOFFSET\": 0,\n",
|
|
" \"DIODE_INSERTION_STRATEGY\": 3\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Workflow\n",
|
|
"The provided `flow.tcl` is a script describing the OpenROAD workflow. A _GDS_ file will be generated using the RTL circuit description, the PDK and the configuration file."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"colab": {
|
|
"base_uri": "https://localhost:8080/"
|
|
},
|
|
"id": "VP60fdObiP15",
|
|
"outputId": "41aa85e4-c663-4778-d448-928dbe474b11"
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%env PDK=sky130A\n",
|
|
"!flow.tcl -design build"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "luguFgZ43AeL"
|
|
},
|
|
"source": [
|
|
"## Output products\n",
|
|
"\n",
|
|
"### Display layout\n",
|
|
"\n",
|
|
"The implemented layout can be retrieved as follows:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"colab": {
|
|
"base_uri": "https://localhost:8080/",
|
|
"height": 650
|
|
},
|
|
"id": "WOnhdtp3ivRi",
|
|
"outputId": "b4bd26f8-d3da-47b7-e321-99272b0591ef",
|
|
"scrolled": false
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import glob\n",
|
|
"import gdstk\n",
|
|
"import IPython.display\n",
|
|
"\n",
|
|
"gdsii = sorted(glob.glob(\"./build/runs/*/results/final/gds/*.gds\"))[-1]\n",
|
|
"top = gdstk.read_gds(gdsii).top_level()\n",
|
|
"top[0].write_svg('svg/inverter.svg')\n",
|
|
"IPython.display.SVG('svg/inverter.svg')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "NW_7YdgTZYQK"
|
|
},
|
|
"source": [
|
|
"### Reporting\n",
|
|
"\n",
|
|
"Many reports are available under:\n",
|
|
"\n",
|
|
"```\n",
|
|
"freeechips/semicustom/runs/RUN_YYYY.MM.DD_HH.MM.SS/reports/.\n",
|
|
"```\n",
|
|
"\n",
|
|
"An overview of the main figures can be retrieved as well:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"colab": {
|
|
"base_uri": "https://localhost:8080/",
|
|
"height": 1000
|
|
},
|
|
"id": "OWAwQI3fZC4W",
|
|
"outputId": "d50dcf9f-30cd-42a3-dab5-12992bc9dca2",
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import glob\n",
|
|
"import pandas as pd\n",
|
|
"pd.options.display.max_rows = None\n",
|
|
"\n",
|
|
"pd.read_csv(sorted(glob.glob(\"./build/runs/*/reports/metrics.csv\"))[-1]).T"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# To have fun going further...\n",
|
|
"\n",
|
|
"## Sequential circuits in Verilog\n",
|
|
"\n",
|
|
"The tutorial above focuses on combinational circuits. Sequential circuits can obviously be described in Verilog as well. Sequential blocks feature an `always` block. Refer to the vendor's reference to get the sensitivity list's syntax (e.g. to determine if reset is synchronous or asynchronous).\n",
|
|
"\n",
|
|
"<blockquote><details>\n",
|
|
"\n",
|
|
"<summary>\n",
|
|
" \n",
|
|
"#### ↕️ Sequential structures in Verilog\n",
|
|
"\n",
|
|
"</summary>\n",
|
|
"\n",
|
|
"Nets which store data are declared as `reg`s:\n",
|
|
" \n",
|
|
"```verilog\n",
|
|
"// Three scalar register\n",
|
|
"reg op_b, op_a, result;\n",
|
|
"// One 16-bit register\n",
|
|
"reg [15:0] word_bus;\n",
|
|
"// 1K-array of 8-bit registers\n",
|
|
"reg [7:0] byte_array [0:1023];\n",
|
|
"```\n",
|
|
"\n",
|
|
"The following structure updates the memory points:\n",
|
|
" \n",
|
|
"```verilog\n",
|
|
"always @(posedge clk or negedge rst) begin\n",
|
|
" if (!rst) begin \n",
|
|
" counter <= 0;\n",
|
|
" end else begin\n",
|
|
" counter <= counter + 1;\n",
|
|
" end\n",
|
|
"end\n",
|
|
"```\n",
|
|
" \n",
|
|
"</details></blockquote>\n",
|
|
"\n",
|
|
"Advanced structures like the `case` structure can be used to describe finite state machines. FSM decoders can be described in an abstract way using `always` blocks in a fully combinational way:\n",
|
|
"\n",
|
|
"<blockquote><details>\n",
|
|
"\n",
|
|
"<summary>\n",
|
|
" \n",
|
|
"#### ↕️ Advanced structures in Verilog\n",
|
|
"\n",
|
|
"</summary>\n",
|
|
"\n",
|
|
"```verilog\n",
|
|
" \n",
|
|
"/*\n",
|
|
" * Case structure\n",
|
|
" */\n",
|
|
" \n",
|
|
"case (state)\n",
|
|
"\n",
|
|
" 3'b000: idle_led <= 1'b1;\n",
|
|
" \n",
|
|
" 3'b001,\n",
|
|
" 3'b010: work_led <= 1'b1;\n",
|
|
" \n",
|
|
" 3'b011: begin\n",
|
|
" muxed <= spi_1;\n",
|
|
" work_led <= 1'b1;\n",
|
|
" end\n",
|
|
" \n",
|
|
" default: begin\n",
|
|
" muxed <= 0;\n",
|
|
" work_led <= 1'b0; \n",
|
|
" idle_led <= 1'b1;\n",
|
|
" end\n",
|
|
"\n",
|
|
"endcase\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * Combinational always structure\n",
|
|
" * To describe priorities in a procedural fashion,\n",
|
|
" * use blocking `<=` assignations instead of\n",
|
|
" * non-blocking `=` assignations.\n",
|
|
" */\n",
|
|
" \n",
|
|
"always @( * ) begin\n",
|
|
" flag <= 1'b0;\n",
|
|
" if (error) begin \n",
|
|
" flag <= 1'b1;\n",
|
|
" end\n",
|
|
"end\n",
|
|
"```\n",
|
|
"\n",
|
|
"</details></blockquote>\n",
|
|
" \n",
|
|
"The counter example can be synthetized:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile v/cnt.v\n",
|
|
"module cnt(\n",
|
|
" input wire clk,\n",
|
|
" input wire rst,\n",
|
|
" output wire [7:0] count\n",
|
|
");\n",
|
|
"\n",
|
|
" reg [7:0] counter;\n",
|
|
" assign count = counter;\n",
|
|
"\n",
|
|
" always @(posedge clk or negedge rst) begin\n",
|
|
" if (!rst) begin \n",
|
|
" counter <= 0;\n",
|
|
" end else begin\n",
|
|
" counter <= counter + 1;\n",
|
|
" end\n",
|
|
" end\n",
|
|
"\n",
|
|
"endmodule"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"For sequential circuits, one must specify to synthesize the clock tree as well:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile build/config.json\n",
|
|
"{\n",
|
|
" \"DESIGN_NAME\": \"cnt\",\n",
|
|
" \"VERILOG_FILES\": \"dir::../v/cnt.v\",\n",
|
|
" \"CLOCK_TREE_SYNTH\": true,\n",
|
|
" \"CLOCK_PORT\": \"clk\",\n",
|
|
" \"FP_SIZING\": \"absolute\",\n",
|
|
" \"DIE_AREA\": \"0 0 40 50\",\n",
|
|
" \"FP_PDN_AUTO_ADJUST\": false,\n",
|
|
" \"FP_PDN_VOFFSET\": 0,\n",
|
|
" \"FP_PDN_HOFFSET\": 0,\n",
|
|
" \"DIODE_INSERTION_STRATEGY\": 3\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The flow can be started:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"%env PDK=sky130A\n",
|
|
"!flow.tcl -design build"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The cell above can be reused to display the lyaout."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## High-Level Synthesis (HLS)\n",
|
|
"\n",
|
|
"RTL description of circuits does not follow an imperative programming paradigm. It is a description language that produces highly parallelized designs.\n",
|
|
"\n",
|
|
"High-Level Synthesis provides an imperative language and a compiler that synthesizes the imperative instructions into RTL. For instance, _XLS_ provides a _Rust_-like language:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile xls/x/find_max.x\n",
|
|
"\n",
|
|
"//\n",
|
|
"// Input: an array of 32-bit unsigned integers (u32) of parametrized length N\n",
|
|
"// Output: the largest element of the array\n",
|
|
"//\n",
|
|
"pub fn find_max<N: u32>(array: u32[N]) -> u32 {\n",
|
|
" let max: u32 = u32:0;\n",
|
|
" for (i, max): (u32, u32) in range(u32:0,N) {\n",
|
|
" if (array[i] > max) {array[i]} else {max}\n",
|
|
" }(max)\n",
|
|
"}\n",
|
|
"\n",
|
|
"//\n",
|
|
"// Input: an array of 32-bit unsigned integers (u32) of parametrized length 4\n",
|
|
"// Output: the largest element of the array\n",
|
|
"//\n",
|
|
"pub fn find_max_impl(array: u32[4]) -> u32 {\n",
|
|
" find_max<u32:4>(array)\n",
|
|
"}\n",
|
|
" \n",
|
|
"#[test]\n",
|
|
"fn find_max_impl_test() {\n",
|
|
" let _= assert_eq(find_max_impl(u32[4]:[45,3,15,6]), u32:45);\n",
|
|
" let _= assert_eq(find_max_impl(u32[4]:[3,45,15,6]), u32:45);\n",
|
|
" let _= assert_eq(find_max_impl(u32[4]:[15,3,45,6]), u32:45);\n",
|
|
" let _= assert_eq(find_max_impl(u32[4]:[6,3,15,45]), u32:45);\n",
|
|
"}\n",
|
|
"\n",
|
|
"#[test]\n",
|
|
"fn find_max_test() {\n",
|
|
" let _= assert_eq(find_max<u32:1>(u32[1]:[39]), u32:39);\n",
|
|
" let _= assert_eq(find_max<u32:2>(u32[2]:[4,90]), u32:90);\n",
|
|
" let _= assert_eq(find_max<u32:3>(u32[3]:[7,21,15]), u32:21);\n",
|
|
" let _= assert_eq(find_max<u32:8>(u32[8]:[1,3,45,1,5,56,0,34]), u32:56);\n",
|
|
"}"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Testing, parsing and linting can be performed prior to RTL synhesis:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"!interpreter_main xls/x/find_max.x"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Now that the imperative instructions are tested, the RTL design can be synthesized by _XLS_:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"XLS_DESIGN_NAME = 'find_max_impl'\n",
|
|
"XLS_DESIGN_FILE = 'find_max'\n",
|
|
"!ir_converter_main --top={XLS_DESIGN_NAME} xls/x/{XLS_DESIGN_FILE}.x > xls/ir/{XLS_DESIGN_FILE}.ir\n",
|
|
"!opt_main xls/ir/{XLS_DESIGN_FILE}.ir > xls/ir/{XLS_DESIGN_FILE}_opt.ir\n",
|
|
"!codegen_main --generator=combinational xls/ir/{XLS_DESIGN_FILE}_opt.ir > v/{XLS_DESIGN_FILE}.v\n",
|
|
"!cat v/{XLS_DESIGN_FILE}.v"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Verilog simulation\n",
|
|
"\n",
|
|
"A test bench can be written to simulate the `xor3` circuit described above:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%writefile tb/xor3_tb.v\n",
|
|
"module xor3_tb;\n",
|
|
"\n",
|
|
" wire value;\n",
|
|
" reg w1, w2, w3;\n",
|
|
" initial begin\n",
|
|
" $dumpfile(\"tb/xor3_tb.vcd\");\n",
|
|
" $dumpvars(0,xor3_tb);\n",
|
|
" # 0 w1 = 0; w2 = 0; w3 = 0;\n",
|
|
" # 5 w1 = 0; w2 = 0; w3 = 1;\n",
|
|
" # 5 w1 = 0; w2 = 1; w3 = 0;\n",
|
|
" # 5 w1 = 0; w2 = 1; w3 = 1;\n",
|
|
" # 5 w1 = 1; w2 = 0; w3 = 0;\n",
|
|
" # 5 w1 = 1; w2 = 0; w3 = 1;\n",
|
|
" # 5 w1 = 1; w2 = 1; w3 = 0;\n",
|
|
" # 5 w1 = 1; w2 = 1; w3 = 1;\n",
|
|
" # 5 $finish;\n",
|
|
" end\n",
|
|
"\n",
|
|
" xor3 xor3_i (w1, w2, w3, value);\n",
|
|
"\n",
|
|
" initial\n",
|
|
" $monitor(\"At time %t, value = %h (%0d)\",\n",
|
|
" $time, value, value);\n",
|
|
"endmodule"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The simulation can then be performed using _Icarus Verilog_:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"!iverilog -o tb/xor3_tb tb/xor3_tb.v v/xor3.v\n",
|
|
"!vvp tb/xor3_tb"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The waveforms are dumped in a `.vcd` file, located under:\n",
|
|
"\n",
|
|
"```\n",
|
|
"tb/xor3_tb.vcd\n",
|
|
"```\n",
|
|
"\n",
|
|
"It can be opened with https://vc.drom.io/ for instance."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Some ideas...\n",
|
|
"\n",
|
|
"Here are a couple ideas to spend a good time exploring those beautiful pieces of software:\n",
|
|
"\n",
|
|
" * Play with RTL2GDS configuration parameters and observe the impact on the layout.\n",
|
|
" * Simulate the `cnt` sequential circuit.\n",
|
|
" * Simulate the `find_max_impl` circuit synthesized using HLS.\n",
|
|
" * Try to make _Klayout_ GUI work (no warranty).\n",
|
|
"\n",
|
|
" **Do not start from scratch! Use the provided examples, examples you can find on the internet and the documentation to adapt from them!**\n",
|
|
" \n",
|
|
" > Good luck and read the docs. 😉\n",
|
|
" \n",
|
|
"## More food for the brain\n",
|
|
"\n",
|
|
" * GDS2RTL flow configuration parameters: https://armleo-openlane.readthedocs.io/en/latest/docs/source/configuration.html\n",
|
|
" * DSLX language reference: https://google.github.io/xls/dslx_reference/"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# References\n",
|
|
"Inspired from:\n",
|
|
"“Silicon Notebooks.” CHIPS Alliance, Apr. 08, 2023. Accessed: Apr. 10, 2023. [Online]. Available: https://github.com/chipsalliance/silicon-notebooks/blob/b65134a43b01ae31423f7ee87110740b2257ac42/digital-inverter-openlane.ipynb (Apache License 2.0)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"colab": {
|
|
"name": "digital-inverter-openlane.ipynb",
|
|
"provenance": []
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3 (ipykernel)",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.7.16"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 1
|
|
}
|