Skip to content

RTL Design Patterns · Chapter 1 · Combinational Building Blocks

Parity, Majority & Simple ECC

Every bit you store or send can flip: a cosmic ray toggles a flip-flop, a noisy link mangles a word, a memory cell leaks its charge. You cannot stop the physics, so you add a small amount of redundant logic that makes a corrupted word look different from a correct one, and if you spend a little more, repairs it. This page builds the three cheapest such structures from the reduction-operator toolbox you already own. Parity is one XOR-reduction that fixes the count of ones to be even or odd, so any single flip is detectable. A majority voter takes whichever value wins 2-of-3 across redundant copies. Simple ECC arranges parity in a pattern so the failing bit's position falls out as a syndrome, enough to correct one error and detect two, all in SystemVerilog, Verilog, and VHDL.

Foundation14 min readRTL Design PatternsParityECCSECDEDMajority VoterTMRData Integrity

Chapter 1 · Section 1.6 · Combinational Building Blocks

1. The Engineering Problem

You are shipping a design into an environment that corrupts data, and you cannot make it stop. Three concrete versions of the same threat:

  • A register in a radiation environment. A high-energy particle strikes a flip-flop and flips its stored bit — a single-event upset (SEU). The value you wrote is not the value you read back. This is routine in space, avionics, and increasingly at advanced nodes even at sea level.
  • A noisy serial link. You transmit an 8-bit word across a cable, a backplane, or a wireless hop. Somewhere along the way one bit picks up enough noise to arrive inverted. The receiver has no way, from the payload alone, to know the word is wrong.
  • A memory array that decays. A DRAM cell leaks charge, or an SRAM cell is struck by a particle, and a stored bit reads back flipped. A large memory sees this often enough that some words are always subtly wrong.

In all three the shape is identical: a word of data leaves you correct and arrives (or reads back) with one bit inverted, and nothing in the payload itself reveals that. You need cheap combinational logic that makes a corrupted word distinguishable from a correct one, so the hardware can act. Three graded responses exist, at three graded costs:

  • Detect the error, so you can discard the word, request a resend, or raise a flag — the job of parity, one extra bit.
  • Vote across redundant copies, so if you keep three copies of a value and one is upset, the other two outvote it — majority voting / triple-modular redundancy (TMR).
  • Correct the error in place, so a single flip is repaired transparently and a double flip is at least detectedsingle-error-correct, double-error-detect (SECDED) ECC.

The good news is that all three are built from the reduction-operator logic you already have — an XOR tree, a bit-wise vote, a small array of XOR trees. They are combinational, cheap, and among the most reused blocks in real hardware. This page is where selection logic turns into integrity logic.

the-threat.v — one flipped bit, and the payload can't tell you
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // You store or send an 8-bit word:
   wire [7:0] data_tx = 8'b1011_0010;      // 4 ones
 
   // Somewhere a single bit flips (SEU / line noise / cell decay):
   wire [7:0] data_rx = 8'b1011_0110;      // bit 2 inverted -> now 5 ones
 
   // From data_rx ALONE, the receiver cannot know it is wrong: it is a
   // perfectly valid 8-bit value. Nothing flags it.
   //
   // FIX PREVIEW — add redundant logic so a flip becomes VISIBLE:
   //   parity  : one XOR-reduce bit makes the #1s known-even -> a flip shows
   //   TMR     : keep 3 copies, vote 2-of-3 -> one upset copy is outvoted
   //   SECDED  : parity in a pattern -> the flip's POSITION falls out (syndrome)

2. Mental Model

The three consequences that run through the whole page fall straight out of these models:

  • Detection is not correction. Parity's single fingerprint can tell you the word changed, but a single bit cannot say which of eight bits moved — there is not enough information. Correction needs more redundant bits, arranged so their failure pattern is a position. Confusing "I have parity" with "I can fix errors" is the classic mistake.
  • A voter needs an odd jury. With three copies a tie is impossible; with an even number of copies a split vote has no majority, and the voter must invent a tiebreak. Odd-sized redundancy is what makes the vote unambiguous.
  • Generator and checker must share one polarity. Even parity and odd parity are both valid, but the side that makes the parity and the side that checks it must agree on which. Disagree, and either every correct word is flagged as an error, or every error slips through silently (§7).

3. Pattern Anatomy

The whole family is XOR-reduction logic, arranged three ways.

Parity — generate vs check, one XOR tree. The parity generator computes parity = ^data — the XOR-reduction of every data bit, which is 1 when the number of 1s is odd. For even parity you transmit that reduction so the total (data plus parity) has an even number of 1s; for odd parity you send its inverse. The checker at the far end recomputes the same reduction over the received data and compares: it XORs the recomputed parity with the received parity bit to produce an err flag. In gates both sides are a single balanced XOR tree of depth log2(WIDTH) — cheap, fast, one bit of overhead. The convention (even or odd) is a shared parameter: the generator's polarity and the checker's polarity are the same declared choice, or the scheme is broken.

Majority / N-of-M voter — bit-wise selection. A 3-input majority voter takes three copies a, b, c and, per bit, outputs the value at least two of them agree on: vote_out = (a & b) | (a & c) | (b & c). It is pure combinational selection, applied independently to every bit of the word, and it corrects a single-copy fault without any check logic — the outvoted bit simply loses. Generalized to N-of-M, you count how many of M copies assert each bit and output 1 when the count reaches a threshold N (majority is N = ceil((M+1)/2)). TMR (M = 3, N = 2) is the workhorse: three registers, one voter, one tolerated upset.

SECDED / Hamming(7,4) — parity in a pattern. Here the redundancy is arranged so the position of a flip is recoverable. Take k data bits and add p parity bits, where each parity bit covers a specific overlapping subset of the combined word chosen so that every bit position has a unique pattern of covering parities. On decode, you recompute each parity over its subset and collect the mismatches into a syndrome:

  • Syndrome = 0 → no single-bit error; the word is clean.
  • Syndrome = s (non-zero) → the bit at position s flipped; correct it by inverting data[s].

That is single-error correction (SEC). To reach double-error detection (DED), add one more overall-parity bit covering the entire codeword. On decode, the two facts — "is the syndrome zero?" and "does overall parity still hold?" — form a small truth table: syndrome zero and overall-parity OK is clean; syndrome non-zero with overall-parity broken is a single, correctable error (odd number of flips → overall parity moved); syndrome non-zero with overall-parity still OK is a double error (even number of flips → overall parity unchanged, but the position checks disagree) — detected, flagged, and deliberately not corrected, because correcting it would guess wrong.

Three integrity blocks from one XOR-reduction toolbox

data flow
Three integrity blocks from one XOR-reduction toolboxdata wordthe bits to protectparity (1XOR-reduce)^data -> 1 bit: detect one flipmajority voter(TMR)bit-wise 2-of-3: correct one copySECDED encodep parity bits in a pattern + overallsyndromenon-zero = position of the flipped bitcorrect / detectSEC: flip data[syndrome]; DED: flag only
All three are reduction logic over the data. One XOR over everything gives a parity fingerprint (detect only). Three redundant copies plus a bit-wise vote correct a single upset with no check logic. Several XORs over overlapping subsets give a syndrome whose value IS the position of the flipped bit, so a single error is corrected and, with one extra overall-parity bit, a double error is detected but not miscorrected. The same idea scales in every HDL — SystemVerilog, Verilog, VHDL.

Two closing facts anchor the anatomy. First, the cost ladder is real: parity is one bit and detects one error; SECDED over k data bits needs about ceil(log2(k)) + 2 check bits and both corrects one and detects two; TMR is the bluntest, tripling storage to correct one. Second, every one of these rests on an invariant the block cannot enforce alone — parity assumes generator and checker share a polarity, TMR assumes an odd number of copies and at most one fault, SECDED assumes at most a double error per word. State the assumption, then verify it.

4. Real RTL Implementation

This is the core of the page. We build three patterns — (a) a parameterized parity generator + checker (even/odd via a parameter), (b) a majority voter (3-input TMR and a generic N-of-M), and (c) a simple SECDED / Hamming(7,4) encode + decode — and each is shown in SystemVerilog, Verilog, and VHDL with both an RTL block and a self-checking testbench. The parity-polarity bug (buggy vs fixed) closes §4 in all three HDLs. The idea is identical across the languages; the syntax differs, and seeing them side by side is what makes the pattern language-independent.

One discipline runs through every block: the polarity/threshold is a declared parameter, and the checker/decoder recomputes exactly what the generator/encoder computed. Break that symmetry and you get §7's silent inversion.

4a. Parameterized parity generator + checker (even/odd)

Parity is one XOR-reduction. The generator emits a parity bit for a WIDTH-bit word; the checker recomputes it over the received word and raises err on a mismatch. A single EVEN parameter sets the polarity on both sides — the whole point is that generator and checker read the same parameter, so they cannot drift apart.

parity.sv — parameterized generator + checker, even/odd via one parameter
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // Generator: parity is the XOR-reduction of the data. For EVEN parity the
   // transmitted bit makes the TOTAL number of 1s even; for ODD parity it is
   // inverted so the total is odd. One shared EVEN parameter drives both modules.
   module parity_gen #(
       parameter int WIDTH = 8,
       parameter bit EVEN  = 1'b1          // 1 = even parity, 0 = odd parity
   )(
       input  logic [WIDTH-1:0] data_in,
       output logic             parity     // send this alongside data_in
   );
       // ^data_in == 1 when the data has an ODD number of 1s.
       // EVEN parity: parity bit = ^data (so data+parity has an even count).
       // ODD  parity: invert it.
       assign parity = EVEN ? (^data_in) : ~(^data_in);
   endmodule
 
   // Checker: recompute parity over the RECEIVED data with the SAME polarity and
   // compare against the received parity bit. err = 1 => a (single) bit flipped.
   module parity_check #(
       parameter int WIDTH = 8,
       parameter bit EVEN  = 1'b1          // MUST equal the generator's EVEN
   )(
       input  logic [WIDTH-1:0] data_in,
       input  logic             parity_in, // the parity bit that arrived
       output logic             err        // 1 = parity mismatch (error detected)
   );
       logic expected;
       assign expected = EVEN ? (^data_in) : ~(^data_in);
       // XOR of expected vs received is the mismatch flag — a bare XOR compare.
       assign err = expected ^ parity_in;
   endmodule

The testbench sweeps every word, checks that a clean word raises no error, then injects a single-bit flip and asserts the checker catches it — the exhaustive single-bit-flip sweep the standard asks for.

parity_tb.sv — clean word -> no err; inject 1 flip -> err; sweep every bit
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module parity_tb;
       localparam int WIDTH = 8;
       localparam bit EVEN  = 1'b1;                    // one polarity, both sides
       logic [WIDTH-1:0] data;
       logic             p, err;
       int               errors = 0;
 
       parity_gen   #(.WIDTH(WIDTH), .EVEN(EVEN)) g (.data_in(data), .parity(p));
       parity_check #(.WIDTH(WIDTH), .EVEN(EVEN)) c (.data_in(data), .parity_in(p), .err(err));
 
       initial begin
           for (int w = 0; w < 40; w++) begin
               data = $urandom;
               #1;
               // 1) A clean word (gen feeds check directly) must NOT flag.
               assert (err === 1'b0)
                   else begin $error("clean word flagged: data=%h", data); errors++; end
               // 2) Inject a single-bit flip on the wire between gen and check.
               for (int b = 0; b < WIDTH; b++) begin
                   logic corrupt_err;
                   logic [WIDTH-1:0] corrupt = data ^ (1'b1 << b);   // flip bit b
                   // recompute the checker on the corrupted word:
                   corrupt_err = (EVEN ? (^corrupt) : ~(^corrupt)) ^ p;
                   assert (corrupt_err === 1'b1)               // one flip MUST be caught
                       else begin $error("missed flip at bit %0d, data=%h", b, data); errors++; end
               end
           end
           if (errors == 0) $display("PASS: clean words pass, every single flip is detected");
           else             $display("FAIL: %0d parity errors", errors);
           $finish;
       end
   endmodule

The Verilog form is identical in intent — wire typing, ^ reduction, $random. The generator and checker still read one EVEN parameter; the checker is a bare XOR compare.

parity.v — generator + checker in Verilog (one shared EVEN parameter)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module parity_gen #(
       parameter WIDTH = 8,
       parameter EVEN  = 1'b1               // 1 = even, 0 = odd
   )(
       input  wire [WIDTH-1:0] data_in,
       output wire             parity
   );
       // ^data_in is the XOR-reduction (1 for an odd number of 1s).
       assign parity = EVEN ? (^data_in) : ~(^data_in);
   endmodule
 
   module parity_check #(
       parameter WIDTH = 8,
       parameter EVEN  = 1'b1               // MUST match the generator
   )(
       input  wire [WIDTH-1:0] data_in,
       input  wire             parity_in,
       output wire             err
   );
       wire expected = EVEN ? (^data_in) : ~(^data_in);
       assign err = expected ^ parity_in;  // mismatch => error
   endmodule
parity_tb.v — self-checking; clean passes, injected flip is caught
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module parity_tb;
       parameter WIDTH = 8;
       parameter EVEN  = 1'b1;
       reg  [WIDTH-1:0] data;
       wire             p, err;
       reg  [WIDTH-1:0] corrupt;
       reg              corrupt_err;
       integer          w, b, errors;
 
       parity_gen   #(.WIDTH(WIDTH), .EVEN(EVEN)) g (.data_in(data), .parity(p));
       parity_check #(.WIDTH(WIDTH), .EVEN(EVEN)) c (.data_in(data), .parity_in(p), .err(err));
 
       initial begin
           errors = 0;
           for (w = 0; w < 40; w = w + 1) begin
               data = $random;
               #1;
               if (err !== 1'b0) begin                       // clean word must not flag
                   $display("FAIL: clean word flagged: data=%h", data);
                   errors = errors + 1;
               end
               for (b = 0; b < WIDTH; b = b + 1) begin        // sweep every bit
                   corrupt     = data ^ (1'b1 << b);          // flip bit b
                   corrupt_err = (EVEN ? (^corrupt) : ~(^corrupt)) ^ p;
                   if (corrupt_err !== 1'b1) begin            // one flip must be caught
                       $display("FAIL: missed flip at bit %0d data=%h", b, data);
                       errors = errors + 1;
                   end
               end
           end
           if (errors == 0) $display("PASS: parity detects every single-bit flip");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

In VHDL the reduction is written with xor_reduce from ieee.std_logic_misc (or a folded XOR loop). The generic carries the polarity; the checker recomputes and XORs against the received bit.

parity.vhd — generator + checker in VHDL (generic polarity, xor_reduce)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_misc.all;                       -- xor_reduce
 
   entity parity_gen is
       generic (
           WIDTH : positive := 8;
           EVEN  : boolean  := true                   -- true = even, false = odd
       );
       port (
           data_in : in  std_logic_vector(WIDTH-1 downto 0);
           parity  : out std_logic
       );
   end entity;
 
   architecture rtl of parity_gen is
   begin
       -- xor_reduce(data_in) = '1' for an odd number of 1s. Even parity sends it;
       -- odd parity inverts it. Both sides read the SAME generic.
       parity <= xor_reduce(data_in) when EVEN else not xor_reduce(data_in);
   end architecture;
 
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_misc.all;
 
   entity parity_check is
       generic (
           WIDTH : positive := 8;
           EVEN  : boolean  := true                   -- MUST equal the generator's
       );
       port (
           data_in   : in  std_logic_vector(WIDTH-1 downto 0);
           parity_in : in  std_logic;
           err       : out std_logic                  -- '1' = mismatch
       );
   end entity;
 
   architecture rtl of parity_check is
       signal expected : std_logic;
   begin
       expected <= xor_reduce(data_in) when EVEN else not xor_reduce(data_in);
       err      <= expected xor parity_in;            -- XOR compare
   end architecture;
parity_tb.vhd — self-checking; clean word clean, every injected flip flagged
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_misc.all;
   use ieee.numeric_std.all;
 
   entity parity_tb is
   end entity;
 
   architecture sim of parity_tb is
       constant WIDTH : positive := 8;
       constant EVEN  : boolean  := true;
       signal data      : std_logic_vector(WIDTH-1 downto 0);
       signal p, err    : std_logic;
   begin
       g : entity work.parity_gen   generic map (WIDTH => WIDTH, EVEN => EVEN)
                                    port map (data_in => data, parity => p);
       c : entity work.parity_check generic map (WIDTH => WIDTH, EVEN => EVEN)
                                    port map (data_in => data, parity_in => p, err => err);
 
       stim : process
           variable corrupt     : std_logic_vector(WIDTH-1 downto 0);
           variable corrupt_err : std_logic;
       begin
           for w in 0 to 39 loop
               data <= std_logic_vector(to_unsigned((w*37+5) mod 256, WIDTH));
               wait for 1 ns;
               assert err = '0'                              -- clean word must not flag
                   report "clean word flagged by parity check" severity error;
               for b in 0 to WIDTH-1 loop                    -- sweep every bit
                   corrupt      := data xor std_logic_vector(to_unsigned(2**b, WIDTH));
                   corrupt_err  := (xor_reduce(corrupt) when EVEN else not xor_reduce(corrupt)) xor p;
                   assert corrupt_err = '1'                  -- one flip must be caught
                       report "parity missed a single-bit flip" severity error;
               end loop;
           end loop;
           report "parity self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. Majority voter — 3-input TMR and generic N-of-M

The voter corrects by redundancy: three copies in, the 2-of-3 winner out, per bit. The 3-input form is the classic (a&b)|(a&c)|(b&c); the generic N-of-M form sums the copies per bit and thresholds. Both are pure combinational, applied across the whole word.

majority.sv — 3-input TMR voter and a generic N-of-M voter
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 3-input majority voter (TMR). Per bit: output the value at least two of the
   // three copies agree on. One upset copy is a lone dissenter and is outvoted.
   module tmr_voter #(
       parameter int WIDTH = 8
   )(
       input  logic [WIDTH-1:0] a, b, c,      // three redundant copies
       output logic [WIDTH-1:0] vote_out      // the corrected value
   );
       // Bit-wise 2-of-3: (a&b)|(a&c)|(b&c). No check logic — the loser vanishes.
       assign vote_out = (a & b) | (a & c) | (b & c);
   endmodule
 
   // Generic N-of-M voter: M copies, output 1 where at least N copies are 1.
   // Majority is N = (M/2)+1. Requires an ODD M for an unambiguous majority.
   module nofm_voter #(
       parameter int WIDTH = 8,
       parameter int M     = 3,               // number of copies (odd for majority)
       parameter int N     = (M/2) + 1        // threshold (majority)
   )(
       input  logic [WIDTH-1:0] copies [M],   // M redundant copies
       output logic [WIDTH-1:0] vote_out
   );
       always_comb begin
           for (int bit_i = 0; bit_i < WIDTH; bit_i++) begin
               int count = 0;
               for (int m = 0; m < M; m++)         // count how many copies assert this bit
                   count += copies[m][bit_i];
               vote_out[bit_i] = (count >= N);     // threshold => majority wins
           end
       end
   endmodule

The testbench proves the two behaviours that matter: all-agree passes through, and any single copy corrupted still votes to the correct value — plus the honest limit that a double corruption in the same bit can outvote the truth.

majority_tb.sv — all-agree passes; one-bad corrected; two-bad can lose
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module majority_tb;
       localparam int WIDTH = 8;
       logic [WIDTH-1:0] good, a, b, c, y;
       int               errors = 0;
 
       tmr_voter #(.WIDTH(WIDTH)) dut (.a(a), .b(b), .c(c), .vote_out(y));
 
       initial begin
           for (int t = 0; t < 40; t++) begin
               good = $urandom;
               // 1) All three agree -> output equals the value, unchanged.
               a = good; b = good; c = good; #1;
               assert (y === good) else begin $error("all-agree corrupted: %h", y); errors++; end
               // 2) Corrupt ONE copy on a random bit -> voter still returns good.
               a = good ^ (1'b1 << ($urandom % WIDTH)); b = good; c = good; #1;
               assert (y === good) else begin $error("one-bad not corrected: %h vs %h", y, good); errors++; end
           end
           // 3) HONEST LIMIT: two copies wrong on the same bit outvote the truth.
           good = 8'h00; a = 8'h01; b = 8'h01; c = 8'h00; #1;
           assert (y[0] === 1'b1) else begin $error("expected majority to follow the two-bad copies"); errors++; end
           if (errors == 0) $display("PASS: TMR corrects one upset; two upsets can win (by design)");
           else             $display("FAIL: %0d voter errors", errors);
           $finish;
       end
   endmodule

The Verilog voter is the same AND-OR for TMR; the generic N-of-M flattens the M copies into one bus and sums bit slices in a loop.

majority.v — TMR voter and generic N-of-M in Verilog (flattened copies)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module tmr_voter #(
       parameter WIDTH = 8
   )(
       input  wire [WIDTH-1:0] a, b, c,
       output wire [WIDTH-1:0] vote_out
   );
       // 2-of-3 majority, bit-wise.
       assign vote_out = (a & b) | (a & c) | (b & c);
   endmodule
 
   module nofm_voter #(
       parameter WIDTH = 8,
       parameter M     = 3,                     // copies (odd for majority)
       parameter N     = (M/2) + 1              // threshold
   )(
       input  wire [M*WIDTH-1:0] copies_flat,   // M copies packed into one bus
       output reg  [WIDTH-1:0]   vote_out
   );
       integer bit_i, m, count;
       always @(*) begin
           for (bit_i = 0; bit_i < WIDTH; bit_i = bit_i + 1) begin
               count = 0;
               for (m = 0; m < M; m = m + 1)               // count asserters of this bit
                   count = count + copies_flat[m*WIDTH + bit_i];
               vote_out[bit_i] = (count >= N);             // majority threshold
           end
       end
   endmodule
majority_tb.v — self-checking; all-agree, one-bad corrected
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module majority_tb;
       parameter WIDTH = 8;
       reg  [WIDTH-1:0] good, a, b, c;
       wire [WIDTH-1:0] y;
       integer          t, errors;
 
       tmr_voter #(.WIDTH(WIDTH)) dut (.a(a), .b(b), .c(c), .vote_out(y));
 
       initial begin
           errors = 0;
           for (t = 0; t < 40; t = t + 1) begin
               good = $random;
               a = good; b = good; c = good; #1;           // all agree
               if (y !== good) begin
                   $display("FAIL: all-agree corrupted t=%0d y=%h", t, y);
                   errors = errors + 1;
               end
               a = good ^ (1'b1 << (t % WIDTH)); b = good; c = good; #1;  // one copy bad
               if (y !== good) begin
                   $display("FAIL: one-bad not corrected t=%0d y=%h exp=%h", t, y, good);
                   errors = errors + 1;
               end
           end
           if (errors == 0) $display("PASS: TMR voter corrects a single bad copy");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

In VHDL the TMR voter is a concurrent AND-OR; the N-of-M form loops over the bits, summing an array of copies with to_integer(unsigned'(...))-style counting.

majority.vhd — TMR voter (concurrent AND-OR) and N-of-M in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity tmr_voter is
       generic ( WIDTH : positive := 8 );
       port (
           a, b, c  : in  std_logic_vector(WIDTH-1 downto 0);   -- three copies
           vote_out : out std_logic_vector(WIDTH-1 downto 0)
       );
   end entity;
 
   architecture rtl of tmr_voter is
   begin
       -- Bit-wise 2-of-3 majority. One dissenting copy is outvoted.
       vote_out <= (a and b) or (a and c) or (b and c);
   end architecture;
 
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   use work.vote_pkg.all;                                 -- slv_array (M copies)
 
   entity nofm_voter is
       generic (
           WIDTH : positive := 8;
           M     : positive := 3;                          -- copies (odd for majority)
           N     : positive := 2                           -- threshold = (M/2)+1
       );
       port (
           copies   : in  slv_array(0 to M-1)(WIDTH-1 downto 0);
           vote_out : out std_logic_vector(WIDTH-1 downto 0)
       );
   end entity;
 
   architecture rtl of nofm_voter is
   begin
       process (all)
           variable count : integer;
       begin
           for bit_i in 0 to WIDTH-1 loop
               count := 0;
               for m in 0 to M-1 loop                       -- count asserters of this bit
                   if copies(m)(bit_i) = '1' then count := count + 1; end if;
               end loop;
               if count >= N then vote_out(bit_i) <= '1';   -- majority threshold
               else                vote_out(bit_i) <= '0';
               end if;
           end loop;
       end process;
   end architecture;
majority_tb.vhd — self-checking; all-agree passes, one-bad corrected
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity majority_tb is
   end entity;
 
   architecture sim of majority_tb is
       constant WIDTH : positive := 8;
       signal good, a, b, c, y : std_logic_vector(WIDTH-1 downto 0);
   begin
       dut : entity work.tmr_voter generic map (WIDTH => WIDTH)
                                   port map (a => a, b => b, c => c, vote_out => y);
 
       stim : process
           variable g : std_logic_vector(WIDTH-1 downto 0);
       begin
           for t in 0 to 39 loop
               g    := std_logic_vector(to_unsigned((t*53+9) mod 256, WIDTH));
               good <= g;
               a <= g; b <= g; c <= g; wait for 1 ns;       -- all agree
               assert y = g report "all-agree corrupted" severity error;
               -- corrupt one copy on bit (t mod WIDTH):
               a <= g xor std_logic_vector(to_unsigned(2**(t mod WIDTH), WIDTH));
               b <= g; c <= g; wait for 1 ns;
               assert y = g report "TMR failed to correct one bad copy" severity error;
           end loop;
           report "majority self-check complete" severity note;
           wait;
       end process;
   end architecture;

4c. Simple SECDED — Hamming(7,4) encode + decode

The correct-capable block. Four data bits become a 7-bit Hamming codeword (three parity bits placed at power-of-two positions), and an eighth overall-parity bit lifts it to SECDED. On decode the three position-parities form a 3-bit syndrome0 means clean, non-zero is the flipped bit's position — and the overall-parity check separates a single (correctable) from a double (detect-only) error. Every subset and the syndrome-to-position mapping is commented, Foundation-clear.

secded.sv — Hamming(7,4) + overall parity: encode, then decode with syndrome
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // ENCODE: 4 data bits d[3:0] -> 7-bit Hamming codeword + 1 overall parity = 8b.
   // Bit positions are 1-based: 1,2,4 are PARITY; 3,5,6,7 are DATA. Each parity
   // covers the positions whose index has that parity's weight bit set:
   //   p1 covers positions 1,3,5,7 ; p2 covers 2,3,6,7 ; p4 covers 4,5,6,7.
   module secded_enc (
       input  logic [3:0] data_in,          // d0..d3
       output logic [7:0] code_out          // [7]=overall, [6:0]=Hamming(7,4)
   );
       logic p1, p2, p4;
       logic [6:0] ham;                      // ham[0]=pos1 ... ham[6]=pos7
       // data at Hamming positions 3,5,6,7 (0-based indices 2,4,5,6):
       // pos3=d0, pos5=d1, pos6=d2, pos7=d3
       // Parity = XOR of the data bits each parity covers:
       assign p1 = data_in[0] ^ data_in[1] ^ data_in[3];   // covers 3,5,7
       assign p2 = data_in[0] ^ data_in[2] ^ data_in[3];   // covers 3,6,7
       assign p4 = data_in[1] ^ data_in[2] ^ data_in[3];   // covers 5,6,7
       assign ham = {data_in[3], data_in[2], data_in[1], p4, data_in[0], p2, p1};
       // Overall parity over the whole 7-bit codeword => enables double detect.
       assign code_out = {(^ham), ham};
   endmodule
 
   // DECODE: recompute the three position-parities into a 3-bit SYNDROME, and
   // recompute overall parity. Correct a single error; flag a double error.
   module secded_dec (
       input  logic [7:0] code_in,
       output logic [3:0] corrected,         // recovered 4 data bits
       output logic       single_err,        // 1 = one error, corrected
       output logic       double_err         // 1 = two errors, detected only
   );
       logic [6:0] ham = code_in[6:0];
       logic       overall_in = code_in[7];
       logic s1, s2, s4;
       logic [2:0] syndrome;
       logic       overall_ok;
       logic [6:0] fixed;
       // Recompute each position-parity over its covered positions (1-based):
       assign s1 = ham[0] ^ ham[2] ^ ham[4] ^ ham[6];      // positions 1,3,5,7
       assign s2 = ham[1] ^ ham[2] ^ ham[5] ^ ham[6];      // positions 2,3,6,7
       assign s4 = ham[3] ^ ham[4] ^ ham[5] ^ ham[6];      // positions 4,5,6,7
       assign syndrome = {s4, s2, s1};                     // = flipped bit POSITION
       // Overall parity of the received 7-bit codeword vs the received overall bit:
       assign overall_ok = ((^ham) == overall_in);
       // Truth table: syndrome==0 & overall_ok  -> clean
       //              syndrome!=0 & !overall_ok -> SINGLE (odd flips): correct pos
       //              syndrome!=0 &  overall_ok -> DOUBLE (even flips): detect only
       always_comb begin
           fixed      = ham;
           single_err = 1'b0;
           double_err = 1'b0;
           if (syndrome != 3'd0) begin
               if (!overall_ok) begin                      // single error
                   single_err = 1'b1;
                   fixed[syndrome-1] = ~ham[syndrome-1];   // flip the named bit back
               end else begin                              // double error
                   double_err = 1'b1;                      // detected, NOT corrected
               end
           end
       end
       // Extract the 4 data bits from Hamming positions 3,5,6,7 of the fixed word:
       assign corrected = {fixed[6], fixed[5], fixed[4], fixed[2]};
   endmodule

The testbench walks the three cases the standard names: 0-error (no flag, data recovered), 1-error (corrected, syndrome named the bit), 2-error (detected, not miscorrected).

secded_tb.sv — 0/1/2-error cases; single corrected, double detected-only
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module secded_tb;
       logic [3:0] data, corrected;
       logic [7:0] code, rx;
       logic       single_err, double_err;
       int         errors = 0;
 
       secded_enc enc (.data_in(data), .code_out(code));
       secded_dec dec (.code_in(rx), .corrected(corrected),
                       .single_err(single_err), .double_err(double_err));
 
       initial begin
           for (int d = 0; d < 16; d++) begin
               data = d[3:0];
               #1;
               // 0-error: pass the codeword through untouched.
               rx = code; #1;
               assert (!single_err && !double_err && corrected === data)
                   else begin $error("0-err: flag/data wrong d=%0d", d); errors++; end
               // 1-error: flip every single bit in turn -> corrected, syndrome named it.
               for (int b = 0; b < 8; b++) begin
                   rx = code ^ (8'b1 << b); #1;
                   assert (single_err && !double_err && corrected === data)
                       else begin $error("1-err bit %0d not corrected d=%0d", b, d); errors++; end
               end
               // 2-error: flip two distinct bits -> DOUBLE detected, NOT miscorrected.
               rx = code ^ 8'b0000_0011; #1;               // flip bits 0 and 1
               assert (double_err && !single_err)
                   else begin $error("2-err not flagged as double d=%0d", d); errors++; end
           end
           if (errors == 0) $display("PASS: SECDED corrects 1 error, detects 2, passes 0");
           else             $display("FAIL: %0d SECDED errors", errors);
           $finish;
       end
   endmodule

The Verilog SECDED is the same logic with reg/wire typing. The syndrome-to-position correction uses an indexed bit flip; the double-error case sets double_err and deliberately leaves the data unrepaired.

secded.v — Hamming(7,4) + overall parity encode/decode in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module secded_enc (
       input  wire [3:0] data_in,
       output wire [7:0] code_out
   );
       // Parity bits over their covered data (positions 3,5,6,7 = d0,d1,d2,d3):
       wire p1 = data_in[0] ^ data_in[1] ^ data_in[3];    // covers 3,5,7
       wire p2 = data_in[0] ^ data_in[2] ^ data_in[3];    // covers 3,6,7
       wire p4 = data_in[1] ^ data_in[2] ^ data_in[3];    // covers 5,6,7
       wire [6:0] ham = {data_in[3], data_in[2], data_in[1], p4, data_in[0], p2, p1};
       assign code_out = {(^ham), ham};                    // MSB = overall parity
   endmodule
 
   module secded_dec (
       input  wire [7:0] code_in,
       output wire [3:0] corrected,
       output reg        single_err,
       output reg        double_err
   );
       wire [6:0] ham        = code_in[6:0];
       wire       overall_in = code_in[7];
       wire s1 = ham[0] ^ ham[2] ^ ham[4] ^ ham[6];        // syndrome bit 1 (pos 1,3,5,7)
       wire s2 = ham[1] ^ ham[2] ^ ham[5] ^ ham[6];        // syndrome bit 2 (pos 2,3,6,7)
       wire s4 = ham[3] ^ ham[4] ^ ham[5] ^ ham[6];        // syndrome bit 4 (pos 4,5,6,7)
       wire [2:0] syndrome   = {s4, s2, s1};               // = flipped bit position
       wire       overall_ok = ((^ham) == overall_in);
       reg  [6:0] fixed;
       always @(*) begin
           fixed      = ham;
           single_err = 1'b0;
           double_err = 1'b0;
           if (syndrome != 3'd0) begin
               if (!overall_ok) begin                      // single: correct it
                   single_err        = 1'b1;
                   fixed[syndrome-1] = ~ham[syndrome-1];
               end else begin                              // double: detect only
                   double_err = 1'b1;
               end
           end
       end
       assign corrected = {fixed[6], fixed[5], fixed[4], fixed[2]};
   endmodule
secded_tb.v — self-checking 0/1/2-error; PASS/FAIL prints
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module secded_tb;
       reg  [3:0] data;
       wire [3:0] corrected;
       wire [7:0] code;
       reg  [7:0] rx;
       wire       single_err, double_err;
       integer    d, b, errors;
 
       secded_enc enc (.data_in(data), .code_out(code));
       secded_dec dec (.code_in(rx), .corrected(corrected),
                       .single_err(single_err), .double_err(double_err));
 
       initial begin
           errors = 0;
           for (d = 0; d < 16; d = d + 1) begin
               data = d[3:0];
               #1;
               rx = code; #1;                               // 0-error
               if (single_err || double_err || corrected !== data) begin
                   $display("FAIL 0-err: d=%0d", d); errors = errors + 1;
               end
               for (b = 0; b < 8; b = b + 1) begin           // 1-error, each bit
                   rx = code ^ (8'b1 << b); #1;
                   if (!single_err || double_err || corrected !== data) begin
                       $display("FAIL 1-err bit %0d d=%0d", b, d); errors = errors + 1;
                   end
               end
               rx = code ^ 8'b0000_0011; #1;                 // 2-error (bits 0,1)
               if (!double_err || single_err) begin
                   $display("FAIL 2-err not double d=%0d", d); errors = errors + 1;
               end
           end
           if (errors == 0) $display("PASS: SECDED 1-correct, 2-detect, 0-clean");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

In VHDL the same block uses numeric_std to index the syndrome-named bit for correction. The syndrome is a std_logic_vector converted to an integer position; the double-error arm sets the flag and leaves the data alone.

secded.vhd — Hamming(7,4) + overall parity encode/decode in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity secded_enc is
       port (
           data_in  : in  std_logic_vector(3 downto 0);
           code_out : out std_logic_vector(7 downto 0)     -- (7)=overall, (6..0)=Hamming
       );
   end entity;
 
   architecture rtl of secded_enc is
       signal p1, p2, p4 : std_logic;
       signal ham        : std_logic_vector(6 downto 0);
   begin
       p1  <= data_in(0) xor data_in(1) xor data_in(3);    -- covers positions 3,5,7
       p2  <= data_in(0) xor data_in(2) xor data_in(3);    -- covers positions 3,6,7
       p4  <= data_in(1) xor data_in(2) xor data_in(3);    -- covers positions 5,6,7
       ham <= data_in(3) & data_in(2) & data_in(1) & p4 & data_in(0) & p2 & p1;
       code_out <= (xor_reduce(ham)) & ham;                -- overall parity in MSB
   end architecture;
 
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   use ieee.std_logic_misc.all;
 
   entity secded_dec is
       port (
           code_in    : in  std_logic_vector(7 downto 0);
           corrected  : out std_logic_vector(3 downto 0);
           single_err : out std_logic;                     -- one error, corrected
           double_err : out std_logic                      -- two errors, detected only
       );
   end entity;
 
   architecture rtl of secded_dec is
   begin
       process (code_in)
           variable ham        : std_logic_vector(6 downto 0);
           variable syndrome   : std_logic_vector(2 downto 0);
           variable overall_ok : boolean;
           variable fixed      : std_logic_vector(6 downto 0);
           variable pos        : integer;
       begin
           ham        := code_in(6 downto 0);
           -- Syndrome bits = recomputed position-parities:
           syndrome(0) := ham(0) xor ham(2) xor ham(4) xor ham(6);   -- pos 1,3,5,7
           syndrome(1) := ham(1) xor ham(2) xor ham(5) xor ham(6);   -- pos 2,3,6,7
           syndrome(2) := ham(3) xor ham(4) xor ham(5) xor ham(6);   -- pos 4,5,6,7
           overall_ok  := (xor_reduce(ham) = code_in(7));
           fixed       := ham;
           single_err  <= '0';
           double_err  <= '0';
           if syndrome /= "000" then
               pos := to_integer(unsigned(syndrome));       -- syndrome IS the position
               if not overall_ok then                       -- single error: correct
                   single_err       <= '1';
                   fixed(pos-1)     := not ham(pos-1);
               else                                          -- double error: detect only
                   double_err       <= '1';
               end if;
           end if;
           corrected <= fixed(6) & fixed(5) & fixed(4) & fixed(2);   -- data at pos 3,5,6,7
       end process;
   end architecture;
secded_tb.vhd — self-checking 0/1/2-error (assert report severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity secded_tb is
   end entity;
 
   architecture sim of secded_tb is
       signal data       : std_logic_vector(3 downto 0);
       signal code, rx   : std_logic_vector(7 downto 0);
       signal corrected  : std_logic_vector(3 downto 0);
       signal single_err : std_logic;
       signal double_err : std_logic;
   begin
       enc : entity work.secded_enc port map (data_in => data, code_out => code);
       dec : entity work.secded_dec port map (code_in => rx, corrected => corrected,
                                              single_err => single_err, double_err => double_err);
       stim : process
       begin
           for d in 0 to 15 loop
               data <= std_logic_vector(to_unsigned(d, 4));
               wait for 1 ns;
               rx <= code; wait for 1 ns;                    -- 0-error
               assert single_err = '0' and double_err = '0' and corrected = data
                   report "0-error case wrong" severity error;
               for b in 0 to 7 loop                          -- 1-error, each bit
                   rx <= code xor std_logic_vector(to_unsigned(2**b, 8));
                   wait for 1 ns;
                   assert single_err = '1' and double_err = '0' and corrected = data
                       report "single-bit error not corrected" severity error;
               end loop;
               rx <= code xor "00000011"; wait for 1 ns;      -- 2-error (bits 0,1)
               assert double_err = '1' and single_err = '0'
                   report "double error not flagged" severity error;
           end loop;
           report "SECDED self-check complete" severity note;
           wait;
       end process;
   end architecture;

4d. The parity-polarity bug — buggy vs fixed, in all three HDLs

The signature failure of this pattern, shown here as buggy vs fixed, then dramatized in §7. The bug is a polarity mismatch: the transmitter generates EVEN parity, but the receiver checks ODD. Both modules are individually correct; they simply disagree on the shared convention, so the checker's err flag inverts — every good word is flagged, and (with the opposite mismatch) real errors slip through.

parity_bug.sv — BUGGY (gen EVEN, check ODD) vs FIXED (one shared EVEN)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: the generator makes EVEN parity, but the checker recomputes with ODD.
   //        For a clean word the two disagree -> err is asserted on GOOD data.
   module parity_link_bad (
       input  logic [7:0] data_in,
       output logic       err
   );
       logic parity;                                 // transmit side
       assign parity = (^data_in);                   // EVEN parity (send ^data)
       // receive side recomputes with the WRONG polarity:
       logic expected;
       assign expected = ~(^data_in);                // ODD parity  <-- MISMATCH
       assign err = expected ^ parity;               // = 1 on every CLEAN word
   endmodule
 
   // FIXED: one declared polarity drives BOTH sides. gen and check agree ->
   //        err is 0 on clean data and 1 only on a real single-bit flip.
   module parity_link_good #(
       parameter bit EVEN = 1'b1                      // the single shared convention
   )(
       input  logic [7:0] data_in,
       output logic       err
   );
       logic parity   = EVEN ? (^data_in) : ~(^data_in);   // generate with EVEN
       logic expected = EVEN ? (^data_in) : ~(^data_in);   // check with the SAME EVEN
       assign err = expected ^ parity;                     // 0 on clean data
   endmodule
parity_bug_tb.sv — clean words expose the polarity mismatch
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module parity_bug_tb;
       logic [7:0] data;
       logic       err;
       int         errors = 0;
 
       // Bind to parity_link_good to PASS; to parity_link_bad to see clean data flagged.
       parity_link_good dut (.data_in(data), .err(err));
 
       initial begin
           for (int w = 0; w < 32; w++) begin
               data = $urandom;
               #1;
               // A CLEAN word must never raise err. The buggy module fails here.
               assert (err === 1'b0)
                   else begin $error("clean word flagged (polarity mismatch): data=%h", data); errors++; end
           end
           if (errors == 0) $display("PASS: gen and check share one polarity; clean data passes");
           else             $display("FAIL: %0d false errors (EVEN gen vs ODD check)", errors);
           $finish;
       end
   endmodule

The Verilog buggy/fixed pair is the same story with wire typing — the fix is a single EVEN parameter both sides read.

parity_bug.v — BUGGY vs FIXED in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: EVEN generator, ODD checker -> err on every clean word.
   module parity_link_bad (
       input  wire [7:0] data_in,
       output wire       err
   );
       wire parity   = (^data_in);                    // EVEN
       wire expected = ~(^data_in);                   // ODD  <-- MISMATCH
       assign err = expected ^ parity;                // 1 on clean data
   endmodule
 
   // FIXED: one shared EVEN parameter on both sides.
   module parity_link_good #(
       parameter EVEN = 1'b1
   )(
       input  wire [7:0] data_in,
       output wire       err
   );
       wire parity   = EVEN ? (^data_in) : ~(^data_in);
       wire expected = EVEN ? (^data_in) : ~(^data_in);
       assign err = expected ^ parity;                // 0 on clean data
   endmodule
parity_bug_tb.v — self-checking; clean data must not flag
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module parity_bug_tb;
       reg  [7:0] data;
       wire       err;
       integer    w, errors;
 
       // Bind to _good to PASS; to _bad to observe every clean word flagged.
       parity_link_good dut (.data_in(data), .err(err));
 
       initial begin
           errors = 0;
           for (w = 0; w < 32; w = w + 1) begin
               data = $random;
               #1;
               if (err !== 1'b0) begin
                   $display("FAIL: clean word flagged (polarity mismatch): data=%h", data);
                   errors = errors + 1;
               end
           end
           if (errors == 0) $display("PASS: shared polarity, clean data passes");
           else             $display("FAIL: %0d false errors", errors);
           $finish;
       end
   endmodule

In VHDL the same mismatch appears when the generator uses xor_reduce and the checker uses not xor_reduce (or the two generic values differ). The fix ties both to one EVEN generic.

parity_bug.vhd — BUGGY (EVEN gen, ODD check) vs FIXED (one generic)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_misc.all;
 
   -- BUGGY: generator EVEN, checker ODD -> err on every clean word.
   entity parity_link_bad is
       port ( data_in : in std_logic_vector(7 downto 0); err : out std_logic );
   end entity;
   architecture rtl of parity_link_bad is
       signal parity, expected : std_logic;
   begin
       parity   <= xor_reduce(data_in);               -- EVEN
       expected <= not xor_reduce(data_in);           -- ODD  <-- MISMATCH
       err      <= expected xor parity;               -- '1' on clean data
   end architecture;
 
   -- FIXED: one shared EVEN generic on both sides.
   entity parity_link_good is
       generic ( EVEN : boolean := true );
       port ( data_in : in std_logic_vector(7 downto 0); err : out std_logic );
   end entity;
   architecture rtl of parity_link_good is
       signal parity, expected : std_logic;
   begin
       parity   <= xor_reduce(data_in) when EVEN else not xor_reduce(data_in);
       expected <= xor_reduce(data_in) when EVEN else not xor_reduce(data_in);
       err      <= expected xor parity;               -- '0' on clean data
   end architecture;
parity_bug_tb.vhd — self-checking; clean data must not flag (assert severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity parity_bug_tb is
   end entity;
 
   architecture sim of parity_bug_tb is
       signal data : std_logic_vector(7 downto 0);
       signal err  : std_logic;
   begin
       -- Bind to parity_link_good to PASS; to _bad to observe clean data flagged.
       dut : entity work.parity_link_good port map (data_in => data, err => err);
 
       stim : process
       begin
           for w in 0 to 31 loop
               data <= std_logic_vector(to_unsigned((w*29+3) mod 256, 8));
               wait for 1 ns;
               assert err = '0'
                   report "clean word flagged (parity polarity mismatch)" severity error;
           end loop;
           report "parity polarity self-check complete" severity note;
           wait;
       end process;
   end architecture;

Across all three languages the lesson is one sentence: the generator and the checker must read one declared polarity — a shared parameter, not two independent choices — or a correct scheme inverts and either flags good data or passes corrupt data.

5. Verification Strategy

Each of these blocks is combinational, and its correctness is a small, checkable property. The good news is that every one can be swept — parity and TMR exhaustively, SECDED across all data words and all single-bit flips. The invariants:

Parity: a clean word raises no error, and any single flipped bit does. Voter: all-agree passes through, one bad copy is corrected, and (honestly) two bad copies can win. SECDED: zero errors pass with data recovered, one error is corrected with the syndrome naming the bit, two errors are detected and NOT miscorrected.

Everything below is those invariants made checkable, mapping onto the §4 testbenches.

  • Parity — inject a 1-bit flip, assert it flags. Generate parity, then XOR the wire between generator and checker with a one-hot mask (data ^ (1 << b)) and assert err == 1; sweep b over every bit for an exhaustive single-bit-flip proof. Also assert a clean word gives err == 0. The SV, Verilog, and VHDL parity testbenches in §4a do exactly this.
  • Parity — even/odd polarity consistency. Verify that generator and checker, sharing one EVEN parameter, agree: clean data never flags. Then flip the checker's polarity (as in §4d) and confirm the opposite result — every clean word flags — so the test proves the polarity is load-bearing, not incidental.
  • Voter — all-agree, one-disagree, tie behaviour. Drive a == b == c and assert vote_out equals the value unchanged. Corrupt exactly one copy on a random bit and assert vote_out is still the correct value (the correction). For the N-of-M form with an even M, drive a split (half 1, half 0) and confirm the tiebreak your threshold defines — an even jury has no clean majority, which is why TMR uses three. The §4b testbenches cover all-agree and one-bad; the honest two-bad-can-win check documents the limit.
  • SECDED — 0/1/2-error cases. Encode each of the 16 data words, then: (0) pass the codeword through and assert no flag with corrected == data; (1) flip every single bit in turn and assert single_err, no double_err, and corrected == data — the syndrome named and repaired the bit; (2) flip two distinct bits and assert double_err with no single_err and no silent miscorrection. This is the exact structure of the §4c testbenches.
  • Exhaustive single-bit-flip sweep. Because these words are small, the flip sweep is complete, not sampled: parity over WIDTH bits, SECDED over all 8 codeword positions for all 16 data values. A block that passes a random sample but fails a specific bit index is caught only by the full sweep.
  • Expected waveform. These blocks have no clock, so "correct" is immediate: as the injected error steps across bit positions, err/syndrome/single_err respond combinationally within the propagation delay. A SECDED syndrome that steps 001, 010, 011, … as you flip bit positions 1, 2, 3, … is the visual signature of a correct decoder; a syndrome that does not track the flipped position is the fingerprint of a wrong parity-coverage table.

6. Common Mistakes

Parity polarity mismatch (generator even, checker odd). The signature bug (§7). If the transmit side makes even parity and the receive side checks odd (or vice-versa), the recomputed and received parity disagree on every clean word, so err is asserted continuously — a link that discards all good data. The inverse mismatch is worse: a scheme tuned so the flags happen to cancel can pass corrupt words silently. The fix is one declared polarity, shared as a parameter by both sides — never two independent even/odd choices.

A majority voter that ties on an even input count. A voter fed an even number of copies has no guaranteed majority: with four copies split 2-and-2, "at least two agree" is true for both values, and the threshold logic must invent a tiebreak that may silently favour 0 (or 1). TMR uses three copies precisely so a tie is impossible. If you must use an even M, define and verify the tiebreak explicitly; do not let it fall out of a >= comparison by accident.

A SECDED that miscorrects a double error as a single. A plain single-error-correcting Hamming code (no overall-parity bit) computes a syndrome even when two bits flipped — but that syndrome points at a third, innocent bit, and "correcting" it makes the word worse (three errors). The overall-parity bit is what prevents this: two flips leave overall parity unchanged (even count) while the syndrome is non-zero, and that combination is the unmistakable signature of a double error — flag it, refuse to correct. Omit the overall bit and your ECC actively corrupts data on double faults.

Confusing detect-only parity with correct-capable ECC. One parity bit can detect a single flip but can never correct it — a single bit carries "something changed," not "bit 5 changed." Treating parity as if it repairs errors, or assuming any check bit implies correction, is the category error this whole page guards against. Correction needs enough redundancy for the failure pattern to encode a position (the syndrome); detection needs only one bit. Know which you built.

Un-replicated or mis-covered parity subsets. In SECDED, if a parity bit covers the wrong set of positions (a typo in the coverage table), the syndrome no longer maps to the bit index — it will "correct" the wrong bit on every single error, turning a corrector into a corrupter that passes a casual clean-data test but fails the single-bit-flip sweep. This is why §5's exhaustive per-bit sweep is mandatory, not optional.

7. DebugLab

The link that discarded every good packet — a parity polarity mismatch

The engineering lesson: a parity scheme is only as correct as the agreement between its generator and its checker — even and odd are both valid, but they must be one declared, shared polarity, not two independent choices. The tell is a flag that fires on every word regardless of data (a systematic convention error, not a random bit-flip) — or, in the inverse mismatch, real errors that never flag at all. Two habits make it impossible, and they hold across SystemVerilog, Verilog, and VHDL: make the polarity a single shared parameter both sides read, and verify with the real generator feeding the real checker (not each checking its own parity). And never forget the second half of the lesson baked into this pattern: detect is not correct — parity notices a flip; only ECC repairs one.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the "detect vs correct, and which redundancy buys what" habit.

Exercise 1 — Detect, correct, or vote?

For each scenario, state which structure you'd use — parity, TMR, or SECDED — and why in one line: (a) a config register in a satellite that must survive single-event upsets with continuous, decode-free correction; (b) an 8-bit UART word on a short cable where a resend is cheap and you only need to know a word was corrupted; (c) a large on-chip data memory where you want to transparently repair the occasional flipped cell and be warned of the rare double flip; (d) a single critical control flip-flop whose upset would hang the machine. (Hint: match the cost and the detect-vs-correct need to the block.)

Exercise 2 — Predict the parity flag

A generator uses even parity; a checker uses odd. (i) For a clean word, what value does the checker's err flag take, and does it depend on the data? (ii) Now suppose a single real bit-flip occurs on the wire between them — does err correctly become 0, 1, or is it inverted from what a matched pair would show? (iii) State in one sentence what this proves about the relationship between generator polarity and checker polarity.

Exercise 3 — Walk the syndrome

For the Hamming(7,4) decoder in §4c, a received 7-bit codeword produces syndrome bits s4 s2 s1 = 1 0 1. (i) What is the syndrome as a number, and therefore which bit position flipped? (ii) The overall-parity check comes back broken — is this a single or a double error, and does the decoder correct it? (iii) Now suppose instead the syndrome is 1 0 1 but overall parity is intact — what does the decoder conclude, and what must it deliberately not do?

Exercise 4 — Size the protection

You must protect a 64-bit memory word. (i) If you only need to detect a single flip, how many extra bits, and what is the block? (ii) If you need single-error-correct, double-error-detect, roughly how many check bits does SECDED cost for 64 data bits, and what are the two "kinds" of those check bits? (iii) If instead you triplicated the whole 64-bit register for TMR, how many total storage bits, and what is the one thing TMR gives you that SECDED does not (and vice-versa)? (Hint: ceil(log2(64)) for the Hamming bits, plus one overall bit.)

Continue in RTL Design Patterns (forward links unlock as they ship):

  • Encoding & Conversions — Chapter 1.7; Gray/binary and one-hot conversions — the encoding logic next door to the parity/coverage patterns here.

In-track combinational building blocks (the reduction-logic neighbours):

  • Multiplexers (2:1, N:1, one-hot) — Chapter 1.1; the AND-OR selection that the bit-wise majority voter here is a specialization of.
  • Decoders — Chapter 1.2; turning a binary index into a one-hot — the inverse move to the syndrome-to-position mapping this page uses.
  • Encoders — Chapter 1.3; collapsing a one-hot back to an index — the same reduction toolbox that builds a parity tree.
  • Priority Encoders — Chapter 1.5; the "first set bit" logic that a syndrome decoder's position math resembles.

Backward / method:

  • The RTL Design Mindset — Chapter 0.2; the Interface → State → Datapath → Control → Verification lenses this page applied to integrity logic (all pure datapath, no state).
  • What Is an RTL Design Pattern? — Chapter 0.1; why parity, TMR, and SECDED each earn the name "pattern" — a recurring problem, a reusable form, a synthesis reality, and a signature failure.

Verilog v1 prerequisites (the grammar this pattern transcribes into):

  • Reduction Operators — the ^ XOR-reduction that is a parity generator, and the AND/OR reductions behind the N-of-M voter's count.
  • Bitwise Operators — the bit-wise AND/OR/XOR that build the majority vote and the syndrome compare.
  • Continuous Assignments — the assign / dataflow form these combinational blocks are written in.
  • Generate Blocks — the elaboration-time replication used to parameterize the parity WIDTH and the N-of-M voter.

11. Summary

  • Data gets corrupted, and you engineer around it with cheap combinational logic. SEUs, noisy links, and decaying memory cells all flip bits; parity, majority voting, and SECDED add redundant logic that makes a corrupted word look different from a correct one — all built from the reduction-operator toolbox you already own.
  • Parity is one XOR-reduction — detect only. The generator emits ^data (even) or its inverse (odd); the checker recomputes and XOR-compares to raise err. It catches every single flip and misses every double — and it can never correct, because one bit says "something changed," not "which bit."
  • A majority voter corrects by redundancy. Bit-wise 2-of-3 ((a&b)|(a&c)|(b&c)) outvotes a single upset copy with no check logic — triple-modular redundancy. It needs an odd jury so a tie is impossible, and it costs 3× storage to tolerate one fault.
  • SECDED is parity in a pattern — correct one, detect two. Several XORs over overlapping subsets produce a syndrome whose value is the flipped bit's position (correct it by inverting that bit); one extra overall-parity bit separates a correctable single error from a detect-only double error, so a double fault is flagged rather than miscorrected onto an innocent bit.
  • Detection is not correction, and generator must match checker. The two lessons that run through the page: know whether you built a detector or a corrector, and make the parity polarity one declared, shared parameter — the §7 mismatch (even generator, odd checker) flags every good word or, inverted, passes every bad one. Verify by an exhaustive single-bit-flip sweep and 0/1/2-error cases — self-checking testbenches shown here in SystemVerilog, Verilog, and VHDL.