Project 1, Part 1: Setting Up and Configuring the ALU Flow
This is the first capstone of the module, and I have chosen the easiest possible design for it on purpose: an 8-bit ALU with no clock, no macro and nothing waiting to cause trouble three stages later. Before we take on designs where clock tree synthesis fights back and a macro takes over the floorplan, I want you to run the entire flow once, start to finish, on something that cannot hide a surprise in a sequential timing path, because it has none.
Easy is not the same as not worth doing carefully, though. Every setting you choose here you will choose again on harder designs, and getting them right on a forgiving example is how you learn what they do.
The design: an 8-bit ALU
This is the ALU from Module 2.2 of the Verilog course, widened from 4 to 8 bits: eight operations, a 3-bit opcode, four flag outputs, and no registers.
Opcode S[2:0] |
Operation | Result |
|---|---|---|
000 |
ADD | A + B |
001 |
SUB | A - B |
010 |
AND | A & B |
011 |
OR | A | B |
100 |
XOR | A ^ B |
101 |
NOT | ~A |
110 |
PASSA | A |
111 |
PASSB | B |
module alu8 (
input [7:0] A, B,
input [2:0] S,
output reg [7:0] Result,
output Z, N, C, V
);
// combinational only; see Module 2.2 of the Verilog course for
// the arithmetic/logic split and the flag logic reused here
endmodule
No flip-flops and no clock pin. That one fact affects every configuration decision in this article.
The config for a design without a clock
A lot of OpenLane’s defaults assume a clock: CLOCK_PORT, CLOCK_PERIOD and the whole CTS stage exist because most real designs are sequential. alu8 has none, so the config says so explicitly:
{
"DESIGN_NAME": "alu8",
"VERILOG_FILES": "dir::src/alu8.v",
"CLOCK_PORT": null,
"CLOCK_PERIOD": 10,
"PNR_SDC_FILE": "dir::alu8.sdc",
"SIGNOFF_SDC_FILE": "dir::alu8.sdc",
"FP_SIZING": "relative",
"FP_CORE_UTIL": 35,
"PL_TARGET_DENSITY": 0.45,
"SYNTH_STRATEGY": "AREA 0",
"RUN_CTS": false
}
CLOCK_PORT: null tells OpenLane there is no clock net. RUN_CTS: false makes the skipped stage explicit, so that someone reading the reports later does not wonder whether CTS quietly failed. The PDK and standard cell library come from OpenLane’s defaults (sky130A, sky130_fd_sc_hd) and do not need repeating here.
CLOCK_PERIOD is still set, and that is not a contradiction. A design without a clock still needs input and output timing constraints if signoff STA is to mean anything: how long may a change on A, B or S take to reach Result? Without some reference clock, set_input_delay and set_output_delay have nothing to be measured against. The usual answer is a virtual clock: a clock defined in the SDC that is not attached to any pin and exists only as a timing reference.
The SDC
# alu8.sdc: this design has no clock pin, so the clock is virtual.
create_clock -name vclk -period 10
set_input_delay 2.0 -clock vclk [get_ports {A[*] B[*] S[*]}]
set_output_delay 2.0 -clock vclk [get_ports {Result[*] Z N C V}]
set_driving_cell -lib_cell sky130_fd_sc_hd__buf_2 -pin X [all_inputs]
set_load 0.02 [all_outputs]
This is the SDC from Module 2.4 applied to the case that lesson only mentioned in passing. The driving cell, the load and the I/O delays are exactly what you would write for a sequential block. Only the clock’s purpose is different: it is a ruler, not a signal. With 2 ns taken by the input delay and 2 ns by the output delay, the ALU has 6 ns to get from its inputs to its outputs.
Running synthesis
openlane --flow Classic --run-tag alu8_run1 --to Yosys.Synthesis designs/alu8/config.json
Yosys maps the RTL through ABC into sky130_fd_sc_hd cells and writes a statistics report in the synthesis step’s directory. The part that matters, trimmed:
=== alu8 ===
Number of wires: 96
Number of wire bits: 212
Number of cells: 163
sky130_fd_sc_hd__a21oi_1 16
sky130_fd_sc_hd__and2_1 18
sky130_fd_sc_hd__buf_1 6
sky130_fd_sc_hd__clkinv_1 4
sky130_fd_sc_hd__mux2_1 32
sky130_fd_sc_hd__nand2_1 12
sky130_fd_sc_hd__o21ai_0 2
sky130_fd_sc_hd__or2_1 14
sky130_fd_sc_hd__xnor2_1 9
sky130_fd_sc_hd__xor2_1 24
...
Chip area for module '\alu8': 1958.1
163 cells and not a single dfxtp or latch. That is worth actually checking, because a flop or latch appearing here would mean something in the RTL was inferred as storage when it should not be, and on a design meant to be purely combinational that is the most important line in the report. The area, about 1,960 µm², is what the floorplan will be sized from in the next article.
One mistake I would warn against is leaving CLOCK_PERIOD out on a clockless design because “there is no clock, so it does not matter”. The flow then falls back on a default that has nothing to do with your I/O timing intent, and your signoff numbers are measured against a period you never chose. Set it deliberately, even when the clock is virtual, and make the SDC match it.
Practice Exercise
The report shows 32 mux2_1 cells, about a fifth of the design. Using the opcode table, explain in one or two sentences why an 8-operation ALU needs that many 2-input multiplexers, and roughly how the count changes if a ninth operation is added.
Solution
Selecting one of eight results for each output bit takes a three-level tree of 2-to-1 muxes, 7 per bit, or 56 for 8 bits. Synthesis gets the count down to 32 because several operations share logic (PASSA and NOT both come from A, the logic operations can be merged into AND-OR gates, and ABC maps some of the selection into other cell types), so not every bit needs the full tree.
A ninth operation does not add just one mux per bit. The select tree becomes four levels deep, with a new first level on every bit, so the selection logic grows by a noticeable step. Opcode width is a physical area decision as well as an instruction set one.
The config and SDC are settled, synthesis is clean, and the cell count makes sense for the RTL. In the next article we take this netlist through floorplanning, placement and routing, and see why CTS has nothing to do here.