Skip to content
VLSI Mentor

sed Part IV · Module 12 · Engineering Case Studies and Review

Diagnosing a Dangerous Mass Replacement

The first two case studies followed the procedure and went well. This one skips it. A single command — an unanchored rename, run in place across a directory of RTL — and the chapter is the forensics afterwards. The thing worth sitting with is that nothing malfunctioned. sed reported success, the pattern matched exactly what it was asked to match, and the design still compiles cleanly. Compilation passes, and so does a simulation run. One thing is caught — a parent still using the old port name — and only because a second file exists to catch it. The three wrongly renamed signals are caught by nothing at all. This is what a sed incident actually looks like, and the only thing that would have caught it had to happen before the command ran.

Advanced19 min readsedPart IVCase StudyIncidentRTL

Part IV · Module 12 · Chapter 12.3 · Diagnosing a Dangerous Mass Replacement

1. The Tree, Before

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n rtl/alu.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	module alu (
     2	    input  logic clk,
     3	    input  logic rst_n,
     4	    output logic ready
     5	);
     6	  logic clk_div;          // divided clk for the slow path
     7	  logic gclk;
     8	  always_ff @(posedge clk) ready <= 1'b1;
     9	  assign gclk = clk & rst_n;
    10	endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n rtl/fifo.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	module fifo (input logic clk, output logic full);
     2	  // fifo runs on the same clk as alu
     3	  always_ff @(posedge clk) full <= 1'b0;
     4	endmodule

Three signals contain the letters clk: the port clk, the divided clock clk_div, and the gated clock gclk. They are three different nets.

The task was to rename the port clk to clk_i, for a naming-convention cleanup.

2. The Command

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -i '' 's/clk/clk_i/g' rtl/*.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rc=0

No output. Exit status 0. From the shell's point of view the task is done.

3. What It Actually Did

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n rtl/alu.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	module alu (
     2	    input  logic clk_i,
     3	    input  logic rst_n,
     4	    output logic ready
     5	);
     6	  logic clk_i_div;          // divided clk_i for the slow path
     7	  logic gclk_i;
     8	  always_ff @(posedge clk_i) ready <= 1'b1;
     9	  assign gclk_i = clk_i & rst_n;
    10	endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
cat -n rtl/fifo.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
     1	module fifo (input logic clk_i, output logic full);
     2	  // fifo runs on the same clk_i as alu
     3	  always_ff @(posedge clk_i) full <= 1'b0;
     4	endmodule

Line 2 and line 8 are right. Then:

  • Line 6: clk_div became clk_i_div. That is the divided clock, a different net, and its name now says it is the _div variant of clk_i, which is not what it is.
  • Line 7 and 9: gclk became gclk_i. The gated clock, renamed by a substitution that had nothing to do with it, into a name that reads like an input port.
  • Both comments rewritten, including fifo's, which now describes a signal relationship using a name the sentence was not written about.

4. What the Tools Do and Do Not Catch

This is the part worth dwelling on, and it needs care about which checks were actually run.

Compilation — measured, and it passes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
iverilog -g2012 -o /tmp/alu.out rtl/alu.sv
iverilog -g2012 -o /tmp/fifo.out rtl/fifo.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Exit status 0 each. Renaming a signal and every reference to it consistently within a module is a legal transformation, so the files still elaborate.

Simulation — also measured, and it also passes. With a small testbench driving the renamed port:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vvp /tmp/incident.vvp | grep TB:
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TB: ready=1

The original produced the same line. The two runs are identical for this stimulus, which is what you would expect: a consistent rename does not change behaviour.

The interface, though, is broken — and this one is caught. A parent that still instantiates alu with the old port name:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
iverilog -g2012 -o /tmp/x.vvp rtl/alu.sv tb_old_iface.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tb_old_iface.sv:3: error: port `clk' is not a port of u.
1 error(s) during elaboration.

So the rename does break something a tool can see — but only once something else in the compile set instantiates the module. In this fixture alu.sv compiled alone, which is exactly why it looked clean.

5. The Forensics — Measuring the Over-Reach

"It changed too much" is a feeling. Here is how to turn it into a number and a list.

First, count what the command touched versus what it should have:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -n 's/clk/&/gp' rtl/alu.sv | wc -l
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
       5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -n 's/[[:<:]]clk[[:>:]]/&/gp' rtl/alu.sv | wc -l
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
       4

Five lines touched; four should have been. The replacement is & in both, so neither command edits anything — Chapter 7.3's measurement trick.

Then name the difference exactly, by running the correct rename and diffing the two outcomes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed 's/[[:<:]]clk[[:>:]]/clk_i/g' rtl/alu.sv > /tmp/correct.sv
sed 's/clk/clk_i/g'               rtl/alu.sv > /tmp/incident.sv
diff -u /tmp/correct.sv /tmp/incident.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--- /tmp/correct.sv	2026-10-12 23:43:00
+++ /tmp/incident.sv	2026-10-12 23:43:01
@@ -3,8 +3,8 @@
     input  logic rst_n,
     output logic ready
 );
-  logic clk_div;          // divided clk_i for the slow path
-  logic gclk;
+  logic clk_i_div;          // divided clk_i for the slow path
+  logic gclk_i;
   always_ff @(posedge clk_i) ready <= 1'b1;
-  assign gclk = clk_i & rst_n;
+  assign gclk_i = clk_i & rst_n;
 endmodule

Exactly three lines of over-reach, named: the clk_div declaration, the gclk declaration, and the gclk assignment. That is a diagnosis rather than a suspicion, and it is the list you take to whoever has to fix it.

6. The Rollback

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
git checkout -- rtl/
git status --short
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet

Silent. Both files restored in one command.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sed -n '6,7p' rtl/alu.sv
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  logic clk_div;          // divided clk for the slow path
  logic gclk;

Back to the original. The incident cost the time to notice it and one command to undo.

7. The Guard — and Which Audit Actually Catches It

Everything above is after the fact. The command that would have prevented it runs before anything is written, and it is worth being precise about which command, because the obvious one is not enough.

Chapter 9.2's audit asks what else shares the prefix:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -oh 'clk[a-z_0-9]*' rtl/*.sv | sort -u
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clk
clk_div

Two identifiers. That is already a stop signal — more than one line means a plain substitution will over-reach — but look at what is missing. gclk is not there, and gclk is one of the two signals the incident damaged.

The reason is in the pattern. clk[a-z_0-9]* anchors the match at clk, so inside gclk it matches the trailing clk and reports it as the bare identifier clk. A prefix audit cannot see a name that merely contains the string.

Chapter 11.2's context census can, because it captures the characters on both sides:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grep -oh '[a-z_0-9]*clk[a-z_0-9]*' rtl/*.sv | sort -u
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clk
clk_div
gclk

Three identifiers, which is the true answer. Two versus three — and the one the prefix form missed is exactly the one that makes this incident interesting, because gclk is a gated clock whose name has nothing to do with the port being renamed.

The full guard, in the order that catches the most for the least effort:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   1. git status --short                              clean tree, or no rollback
   2. grep -oh '[a-z_0-9]*name[a-z_0-9]*' … | sort -u  >1 line means stop
   3. sed -n 's/pat/&/gp' file | wc -l                 the count, before and after
   4. sed … | diff -u file -                           read the change
   5. only then, -i

Steps 1 to 4 write nothing and take under a minute on any tree. The incident is entirely explained by none of them having been run.

8. Think It Through

1. What exactly went wrong — name it precisely.

The pattern clk was unanchored, so it matched inside clk_div and gclk, which are different nets. sed behaved correctly; the instruction was wider than the intention. Chapter 7.3 calls this class the bug with no error.

2. Why did the compiler not catch it?

Because renaming a net and all its references consistently inside a module is a legal transformation. Measured: iverilog exit 0 on both files, and a testbench run producing output identical to the original. A compiler checks consistency, not intent — and note what was caught, in section 4: a parent still using the old port name fails to elaborate, because the rename changed the module's interface.

3. How do you measure the over-reach rather than estimate it?

Run the correct version and the incident version separately, then diff the two outputs. Section 5 did this and got exactly three lines, each named. The count comparison — 5 touched versus 4 correct — gives the magnitude; the diff gives the list.

4. Which single command would have prevented this — and which nearly-identical one would not?

grep -oh '[a-z_0-9]*clk[a-z_0-9]*' rtl/*.sv | sort -u returns three identifiers (clk, clk_div, gclk), and anything above one means stop.

The prefix-only form from Chapter 9.2, grep -oh 'clk[a-z_0-9]*', returns only two — it misses gclk, because the match starts at the clk inside it. For a string that can appear mid-identifier, use the containment form from Chapter 11.2.

5. The word-boundary rename still changed a comment. Is that a bug?

Not in the same sense. clk in the comment is a standalone word, so the boundaries matched it correctly; whether it should change is a judgement about documentation, and here it probably should. The point is that word boundaries fix the identifier problem and not the comment problem — Chapter 9.3 — so even the careful version needs its diff read.

6. The tree was not committed and there are no backups. What now?

Reconstruct from the diff you can still compute against whatever reference exists — the last release tag, a colleague's checkout, the build area's copy — and treat every clk_i as suspect until it is confirmed. Then commit, and make git status --short the first thing in the procedure. The honest answer is that this case is expensive, and that is the whole argument for step 1.

9. Summary

  • Nothing malfunctioned. sed exited 0, matched exactly what it was told to match, and produced a design that compiles.
  • Measured: iverilog exit 0 on both files, and simulation output identical to the original for the testbench used. A consistent rename is legal, so neither compilation nor that run has an opinion. Lint and a regression suite were not run — neither exists on this machine, and an earlier draft wrongly reported them as clean.
  • The interface break is caught — a parent using the old port name gives port 'clk' is not a port of u, but only if such a parent is in the compile set.
  • Three signals were wrong: clk_div became clk_i_div, and gclk became gclk_i in both its declaration and its assignment.
  • The over-reach is measurable, not a feeling: 5 lines touched versus 4 correct, and a diff between the two outcomes naming exactly three wrong lines.
  • The correct rename still rewrote a comment, because clk there is a standalone word. Word boundaries are not a total fix.
  • The blast radius being small is the trap. At 400 files the three wrong lines are indistinguishable from the two thousand correct ones.
  • git checkout -- rtl/ restored everything in one command — and only because the tree was committed first.
  • One guard would have prevented it — but it has to be the right one. The containment census grep -oh '[a-z_0-9]*clk[a-z_0-9]*' … | sort -u returns three identifiers; Chapter 9.2's prefix form returns two and misses gclk, one of the two damaged signals.

Three case studies: two that followed the procedure and one that did not. What remains is the skill that sits above all of them — looking at somebody else's sed command and deciding whether to approve it. Chapter 12.4 — Engineering Review and Interview Reasoning is the last chapter of the track, and it is the one that turns everything here into something you can say out loud.

Where this fits

Part of the sed curriculum.