{ "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", "
\n", "\n", "\n", " \n", "#### ↕️ Types in Verilog\n", "\n", "\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", "
\n", "\n", "
\n", "\n", "\n", " \n", "#### ↕️ Assignation (non-blocking) in Verilog\n", "\n", "\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", "
\n", "\n", "
\n", "\n", "\n", " \n", "#### ↕️ Operators in Verilog\n", "\n", "\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", "
" ] }, { "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", "
\n", "\n", "\n", " \n", "#### ↕️ Sequential structures in Verilog\n", "\n", "\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", "
\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", "
\n", "\n", "\n", " \n", "#### ↕️ Advanced structures in Verilog\n", "\n", "\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", "
\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(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(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]:[39]), u32:39);\n", " let _= assert_eq(find_max(u32[2]:[4,90]), u32:90);\n", " let _= assert_eq(find_max(u32[3]:[7,21,15]), u32:21);\n", " let _= assert_eq(find_max(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 }