Index
- 0. What this lesson will make you capable of
- 1. Before Factory: what problem are we solving?
- 2. Basic vocabulary (remove jargon)
- 3. The simplest “factory” (1 function)
- 4. Why the naïve simulator becomes spaghetti
- 5. Factory Method vs Abstract Factory (clear difference)
- 6. Step-by-step EDA design (what varies? what stays stable?)
- 7. Build the interfaces (Parser/Simulator/Reports/Debugger)
- 8. Build concrete implementations (Verilog/VHDL/SV)
- 9. Build factories (one place decides the concrete types)
- 10. Build a registry (plugin-ready)
- 11. Client code (Simulator) becomes clean
- 12. Ownership & RAII (the most important advanced part)
- 13. Thread-safety (real EDA workloads)
- 14. Performance (caching, pooling, object reuse)
- 15. Mixed-language & config selection
- 16. Testing (unit + integration + contract tests)
- 17. Common pitfalls (staff-level warnings)
- 18. Synopsys Senior/Staff interview answers
- 19. Final 30-second summary (memorize)
0. What this lesson will make you capable of
After reading this, you should be able to:
- Explain why factories exist in real tools (not “because GoF said so”).
- Implement a clean EDA-style factory architecture: Parser + Simulator + Reports + Debugger.
- Add a new language (e.g., UPF, SVA) without touching core simulation flow.
- Make ownership and lifetime obvious and safe using
std::unique_ptr. - Discuss plugin systems, ABI concerns, and thread safety like a Senior/Staff engineer.
- Answer interview questions with reasoning (not mugged-up definitions).
1. Before Factory: what problem are we solving?
1.1 Real EDA situation (VCS/Xcelium type)
You are building something like a simulator pipeline:
- Read HDL source files
- Parse them into an internal representation (AST / IR)
- Validate and elaborate (connect modules, parameters, generate blocks)
- Simulate (event scheduling, delta cycles)
- Produce outputs (waveforms, coverage, reports)
- Debug (breakpoints, signal inspection)
The hard part: you don’t support one language or one mode.
You support many combinations:
- Verilog
.v - SystemVerilog
.sv,.svh - VHDL
.vhd - Power intent
.upf - Assertions (SVA)
- Coverage on/off
- Fast mode vs debug mode
- Multi-core vs single-core
1.2 The core pain
If the main simulator has code like:
- “if Verilog then create VerilogParser”
- “else if VHDL then create VHDLParser”
- “else if …”
then every time you add a new language or mode you must modify core code.
That makes the codebase:
- fragile
- hard to test
- hard to evolve
- hard to merge (everyone edits the same central file)
Factories exist to prevent this.
2. Basic vocabulary (remove jargon)
2.1 What is an “object creation decision”?
It means: deciding which concrete class to construct.
Example:
- Should we do
VerilogParserorVHDLParser? - Should we use
FastSimulatororDebugSimulator?
That “decision” is the thing we want to isolate.
2.2 What is “coupling”?
Coupling means your code depends on specific concrete types.
If Simulator directly writes:
then Simulator is tightly coupled to Verilog-specific types. That becomes painful when you add new languages.
2.3 What is “polymorphism” here?
Polymorphism means: we program to an interface like Parser,
but at runtime we can use VerilogParser or VHDLParser.
2.4 What is OCP (Open/Closed Principle)?
- Open for extension (you can add new behavior)
- Closed for modification (you should not change old core code every time)
Factories help achieve OCP.
3. The simplest “factory” (1 function)
Before fancy patterns, here is the simplest factory:
Why this already helps
Now the rest of the system depends only on Parser, not concrete types.
But this is still limited:
- You still have a central if/else for every addition.
- You only create ONE product (Parser), but EDA needs families.
So we go further.
4. Why the naïve simulator becomes spaghetti
Here is the “bad” design:
What breaks at scale?
- Every new language forces edits in
Simulator. - The
Simulatormust include headers of every language component. - Unit testing becomes hard (you can’t easily swap with mocks).
- The core file becomes a merge-conflict magnet.
- Plugins become almost impossible (you can’t compile plugin types into core).
So the goal is:
Make
Simulatorunaware of concrete languages.
That’s exactly the Factory idea at scale.
5. Factory Method vs Abstract Factory (clear difference)
5.1 Factory Method (creates one product)
Example: only create a Parser.
- Useful when you vary one thing
- Too small for EDA
5.2 Abstract Factory (creates a family of products)
EDA needs multiple components that must match:
- Parser + Simulator + Debugger + Reports
If you mix them incorrectly, you get nonsense. Example: VerilogParser feeding a VHDLSimulator.
Abstract Factory ensures a coherent family.
6. Step-by-step EDA design (what varies? what stays stable?)
6.1 “What varies” (different per language/mode)
- Grammar
- AST/IR shape
- Validation rules
- Simulation engine semantics
- Debugger hooks
- Report formats
6.2 “What stays stable” (the pipeline)
The pipeline is stable:
- Parse
- Validate
- Simulate
- Report
- Debug (optional)
So we build stable interfaces for each stage.
7. Build the interfaces (Parser/Simulator/Reports/Debugger)
Why interfaces?
Because Simulator should depend only on these abstract contracts.
Jargon explained
- interface: an abstract base class with virtual methods.
- contract: what the functions mean (e.g.
validate()must check syntax/semantic rules). - unique_ptr: a smart pointer with single ownership (it deletes automatically).
8. Build concrete implementations (Verilog/VHDL/SV)
9. Build factories (one place decides the concrete types)
Factory interface (Abstract Factory)
Concrete factories
10. Build a registry (plugin-ready)
What is a registry?
A registry is a map:
- extension → language name
- language name → factory
11. Client code (Simulator) becomes clean
Now Simulator becomes “pipeline-only”:
Notice:
- No
if (ext == ...)here - No mention of Verilog/VHDL types here
- No raw
new/deletehere
This is what “clean architecture” means.
12. Ownership & RAII (the most important advanced part)
Why raw pointers are dangerous in factories
If factory returns Parser*, who deletes it?
- Simulator?
- Registry?
- Factory?
If that is unclear, you get:
- leaks
- double deletes
- use-after-free bugs
The Staff-level fix
Use return types to encode ownership:
std::unique_ptr<T>: caller owns itstd::shared_ptr<T>: shared ownership- raw pointer: only for “non-owning observation” (rare, must be documented)
That’s why our factory returns unique_ptr.
13. Thread-safety (real EDA workloads)
Registries are read-heavy. Use shared mutex:
- many readers can query simultaneously
- writers (register) are rare
14. Performance (caching, pooling, object reuse)
Default: create per run (simple, correct).
Then optimize only if needed:
- cache grammar tables inside parsers
- pool large allocators
- reuse symbol tables
Staff-level statement:
correctness first, then profile, then optimize.
15. Mixed-language & config selection
Factories can take configuration:
This is how real EDA tools create “fast mode vs debug mode” engines.
16. Testing (unit + integration + contract tests)
Mock factory lets you test the pipeline without real HDL parsing:
- parser returns dummy AST
- simulator does nothing
- reports are stubbed
This is why factories massively improve testability.
17. Common pitfalls (staff-level warnings)
- Hidden ownership (raw pointers)
- Registry not thread-safe
- Factories returning cached objects without documenting ownership
- Passing STL types across plugin ABI boundaries without control
- Too many “versions” causing factory explosion (solve with config objects)
18. Synopsys Senior/Staff interview answers
“Why factory in EDA?”
Because we need:
- extensibility (new languages/features)
- plugin integration
- clean separation of concerns
- testability
- stable core pipeline
“Factory Method vs Abstract Factory?”
EDA needs product families → Abstract Factory is natural.
“How to add UPF?”
Implement UPF products + UPFFactory + register → core simulator unchanged.
19. Final 30-second summary (memorize)
Factory in EDA is not about hiding new.
It is about isolating creation decisions, keeping the simulator pipeline stable,
supporting plugins, making ownership explicit via RAII,
and enabling new languages/features without editing core code.