(ZH-EN Translation by DeepSeek-V3-0324)
Background
Recently, the national key lab planned to initiate work on intelligent circuit design, and while preparing materials, we often needed circuit schematics as examples. However, there were no readily available tools to conveniently draw these schematics. Existing schematic drawing tools require manually placing each component and connecting each wire one by one. Even drawing a small-scale example involves tedious operations. For instance, a 4-bit adder contains hundreds of transistors, and manually placing components for larger examples becomes nearly as labor-intensive as designing a 4004 processor from scratch. Moreover, real synthesis and placement and routing (PNR) tools are not only cumbersome to invoke but also produce visually unappealing diagrams. As a result, I have always resorted to writing my own scripts to generate these diagrams.
Initially, I used Python with PIL (Python Imaging Library) to draw the schematics. I wrote a few basic functions to draw transistors and logic gates at specified positions, but the placement and routing still had to be designed manually, resulting in low efficiency. I then decided to add simple placement and routing logic to the script to automate the entire workflow from input circuit netlists to output schematics. Here is the final result:
For computationally intensive tasks like placement and routing, I immediately abandoned Python in favor of C++ to avoid waiting an entire day to generate a single diagram. The routing algorithm reused a maze routing implementation I had previously written for another project. I then pasted the routing code into DeepSeek and asked it to complete the placement code. DeepSeek-V3-0324 demonstrated impressive coding capabilities, producing functional and complete code that worked right away. With the help of the large language model, along with some research and clever ideas, abandoning PIL did not cause much pain. DeepSeek provided code for outputting PNGs, extracting BDF bitmap fonts, and helped debug a bug in the diagonal line rasterization algorithm.
Thus, I quickly implemented this ~800-line automated circuit placement and routing program that takes BLIF as input and outputs PNGs. It is entirely self-contained, with dependencies limited to the C++ Standard Template Library (STL). Even as someone who has been programming since childhood, this coding experience was entirely new to me, showcasing how large language models are reshaping the programming experience. Without such models, reading RFCs for PNG, BDF, DEFLATE, and writing low-level functions would have been extremely painful, and I would never have attempted it.
Abandoning Python and returning to C++ has never been easier, and presumably, the same applies to abandoning CUDA. In this era, ecosystem barriers are being weakened across the board—an exciting trend!
Code
The code uses C++26. Currently, only GCC 15.0 can compile it, but since 15.0 has not yet been released, I compiled the latest GCC from source.
This is my first extensive use of the ranges library, and the experience has been great—it often saves a lot of unnecessary verbosity. The current ranges implementation still lacks some features, such as a reverse drop (removing elements from the end of a range). It’s also inconvenient to determine whether a loop iteration is the first or last, making the old-fashioned way of writing loops sometimes preferable.
The C++ standard also lacks some key containers, such as a trie—why hasn’t this been added yet? The performance of some older containers has long been criticized, and it’s time for a refresh. The latest standard provides flat_map, but in my testing, it showed no performance advantage in my use case. I didn’t see an unordered_flat_map. Hopefully, these new features will avoid repeating the mistakes of regex.
Expand code
Here’s an analysis of the code by DeepSeek-R1:
Expand analysis
This program is a tool for generating standard CMOS circuit schematics, supporting BLIF format input and PNG image output. Below is an interpretation of its core functionality and workflow:
Input Parsing (
parsefunction)
- Functionality: Parses the BLIF file to extract circuit input/output ports and logic gate information.
- Key Data Structures:
inputs/outputs: Store input/output port IDs.gates: A hash table storing logic gates, with gate IDs as keys and gate types (e.g., NOT, NAND) and their connected ports as values.names: Maps gate type names to unique IDs, supporting dynamic expansion.- Processing Logic:
- Parses
.inputs,.outputs, and.subcktsections line by line to extract port and subcircuit information.- Uses the
parse_namefunction to convert string-type gate names to integer IDs for subsequent processing.Placement Optimization (
placefunction)
- Algorithm: Uses simulated annealing for placement optimization, aiming to minimize routing cost and area.
- Key Parameters:
initial_temperature: Controls the probability of accepting worse solutions early in the algorithm.cooling_rate: Gradually reduces temperature to converge to an optimal solution.area_costandcross_track_penalty: Penalize area usage and cross-track routing, respectively.- Workflow:
- Initial Placement: Randomly generates gate positions, ensuring no overlaps.
- Iterative Optimization: Adjusts gate positions, calculates new layout costs, and accepts or rejects solutions based on the Metropolis criterion.
- Termination: Stops when temperature drops below a threshold or after a timeout.
Routing (
routefunction)
- Objective: Generates paths for connections between logic gates while avoiding conflicts.
- Key Steps:
- Obstacle Marking: Uses the
maskarray to mark non-routable areas (e.g., space around gates).- Path Search: Employs BFS to search backward from target ports to source ports, recording paths.
- Cost Calculation: Accumulates path length and cross-track penalties.
- Output:
paths: Stores coordinates of all connection paths.solders: Records solder point positions.Drawing (
draw_tstruct)
- Functionality: Renders the placement and routing results as a PNG image.
- Core Methods:
- Basic Shapes:
line,ellipsefor drawing lines and solder points.- Logic Gate Drawing: Functions like
nmos,pmos,inv,nandimplement CMOS gate rendering.- Text Labels: The
textfunction uses predefined font bitmaps to label port names.- PNG Generation: Manually constructs the PNG file format (including IHDR, IDAT, IEND chunks), combines DEFLATE compression and CRC checks, and outputs the final image.
Main Workflow (
mainfunction)
- Input Parsing: Reads the BLIF file and extracts circuit information.
- Placement Optimization: Calls
placeto generate the initial layout.- Grid Alignment: Uses
snap_to_microgridto align layout coordinates to a micro-grid.- Multiple Routing Attempts: Incrementally increases slackness (
slackness) and callsrouteuntil successful.- Image Generation: After successful routing, calls
draw_schematicto render the schematic and save it as a PNG.Key Technologies and Challenges
- Simulated Annealing: Balances global search and local optimization to avoid local minima.
- Pathfinding Algorithm: Efficiently finds feasible paths in dynamic obstacle environments.
- Low-Level Drawing: Directly manipulates pixel data and generates PNG-compliant files, handling compression and checksum details.
Summary
This program automates the generation of schematics from logical descriptions by parsing circuit definitions, optimizing placement, performing automatic routing, and rendering. Its core strength lies in combining algorithmic optimization (simulated annealing) with low-level graphics processing, making it suitable for rapid visualization of small-to-medium-scale CMOS circuits.
Usage
The input is a netlist in BLIF format. You can use the open-source tool Yosys to synthesize it from Verilog.
For example, I first asked DeepSeek to write a 4-bit adder as an example:
1 | module adder4 ( |
Next, create a standard cell library. Currently, the code only supports NOT and NAND gates, so we need to instruct Yosys to use only these two cell types.
1 | library(demo) { |
Launch Yosys and run the following commands:
1 | read_verilog adder4.v |
The last step outputs the netlist in BLIF format. Copy and save it to a file named adder4.blif.
1 | # Generated by Yosys 0.51+104 (git sha1 c08f72b80, g++ 13.3.0-6ubuntu2~24.04 -fPIC -O3) |
Compile cirschem.cpp: g++ cirschem.cpp -std=c++26 -O3
Run: ./a.out adder4.blif
After waiting about half a minute for placement and routing to complete, the output will be saved as schematic.png.
