Day 13 · SystemVerilog Transition

enum · struct · package

Video 4 of 4 · ~10 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

Why SVlogic typealways_*enum/struct/package

🌍 Where This Lives

In Industry

Large RTL codebases live and die by their type systems. ARM's CPU projects have central package files defining instruction opcodes, cache states, pipeline bundle types — referenced by hundreds of modules. Intel's ASIC flows require typedef enum for every FSM state. Without these features, thousand-module codebases would drown in localparam and port lists. Shared types are how large teams stay sane.

In This Course

Your Week 4 capstone will have a handful of modules sharing state definitions. package lets you define them once. typedef enum lets the compiler type-check your FSM transitions. struct lets you bundle UART frame fields into one signal. Modest codebase, large effect.

⚠️ “localparam Is Fine”

❌ Wrong Model

“My FSM uses localparam [2:0] S_IDLE = 3'd0; ... — it works. What does enum add?”

✓ Right Model

Three things typedef enum adds that localparam cannot: (1) type safety — the compiler catches state = WRITE_REG if WRITE_REG belongs to a different enum. (2) named value debugging — in GTKWave, signals show S_BUSY instead of 3'd2. (3) width inference — no manual $clog2 arithmetic. Three bugs shut, three readability wins, zero silicon cost.

The receipt: typedef enum is what localparam should have been. localparam was a hack for “no enum keyword in 1995.” Use enum now.

👁️ I Do — typedef enum for FSM States

VERILOG-2001

localparam [1:0]
    S_GREEN  = 2'd0,
    S_YELLOW = 2'd1,
    S_RED    = 2'd2;

reg [1:0] r_state, r_next;

always @(posedge clk)
    r_state <= r_next;

SYSTEMVERILOG

typedef enum logic [1:0] {
    S_GREEN,
    S_YELLOW,
    S_RED
} state_t;

state_t r_state, r_next;

always_ff @(posedge clk)
    r_state <= r_next;
My thinking: Three improvements. (1) state_t is a named type I can reuse across modules. (2) Values auto-assigned 0, 1, 2 (or I can override). (3) Width is part of the type — no width declaration on every variable. Plus GTKWave shows S_GREEN not 2'd0.

🤝 We Do — struct for Bundled Signals

// Bundle a UART frame into one struct
typedef struct packed {
    logic       start_bit;    // 1 bit
    logic [7:0] data;         // 8 bits
    logic       parity;       // 1 bit
    logic       stop_bit;     // 1 bit
} uart_frame_t;                // total: 11 bits, packed left-to-right

// Now this is one signal, not four
uart_frame_t frame_in, frame_out;

// Access individual fields:
assign frame_out.data = processed_byte;
// Or assign the whole thing:
assign frame_out = '{start_bit: 1'b0, data: 8'hA5,
                     parity: 1'b1, stop_bit: 1'b1};
Together: packed struct means the struct lives contiguously in memory as a flat bit vector — $bits(uart_frame_t) = 11, and you can treat frame_out as an [10:0] value. Ports that carry frames no longer need 4 separate signals; they carry one uart_frame_t. Port lists shrink; bundling is explicit; individual fields are still accessible.

🤝 We Do — package for Shared Types

// File: uart_pkg.sv
package uart_pkg;
    parameter int DEFAULT_BAUD = 115200;

    typedef enum logic [1:0] {
        PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK
    } parity_t;

    typedef struct packed {
        logic       start_bit;
        logic [7:0] data;
        parity_t    parity_mode;   // nested enum!
        logic       stop_bit;
    } frame_t;
endpackage

// File: uart_tx.sv
import uart_pkg::*;        // now frame_t, parity_t, DEFAULT_BAUD all visible

module uart_tx #(parameter BAUD = DEFAULT_BAUD) (
    input frame_t frame_in,
    // ...
);
Together: One package file defines all shared types and constants. Every module that needs them imports. Change the definition in one place, everyone sees the update. This is how real codebases scale from 10 modules to 1000 without drowning.

🧪 You Do — Design a Package for Week 3's UART

Your UART TX, UART RX, and top-level need a shared definition. Sketch a uart_pkg.sv for them.

package uart_pkg;
    parameter int DEFAULT_CLKS_PER_BIT = 217;    // 25 MHz / 115200

    typedef enum logic [1:0] {
        TX_IDLE, TX_START, TX_DATA, TX_STOP
    } tx_state_t;

    typedef enum logic [1:0] {
        RX_IDLE, RX_START, RX_DATA, RX_STOP
    } rx_state_t;

    typedef struct packed {
        logic       valid;
        logic [7:0] data;
        logic       frame_error;
    } rx_result_t;
endpackage
Note: Different state types for TX and RX. Import in each module; compiler prevents assigning TX state to RX state variable. Type safety.
▶ LIVE DEMO

State Names in GTKWave

~3 minutes

▸ COMMANDS

cd labs/week4_day13/ex4_enum_struct/
iverilog -g2012 -o sim \\
    traffic_fsm_sv.sv tb_traffic.sv
./sim
gtkwave waves.vcd

▸ IN GTKWAVE

r_state is shown as:

  S_GREEN   S_YELLOW   S_RED

not as:

  2'd0   2'd1   2'd2

(automatic — no manual VCD config)

▸ KEY OBSERVATION

GTKWave reads the SV enum symbol table from the VCD file. State names appear automatically — no format gymnastics. This alone changes debug ergonomics. Identifying “we got stuck in S_WAITING_ACK” is faster than “we got stuck in 3'd4.”

🔧 Same Silicon, Better Source

$ yosys -p "read_verilog -sv traffic_fsm_sv.sv; synth_ice40; stat"

=== traffic_fsm ===
   Number of cells: 7     ← same as Verilog-2001 version
     SB_DFF:  2
     SB_LUT4: 5

# SV types/packages/enums vanish at elaboration. The synthesis
# result is identical to hand-written Verilog-2001 with localparam.
# You pay nothing for the readability and type safety.
What to notice: SV features are source-level constructs. They do not change the hardware. They change what you can catch at compile time. 7 cells before, 7 cells after. Same silicon, cleaner source, safer code.

🤖 Check the Machine

Ask AI: “Refactor my UART design to use a shared package with typedef enums for states and a packed struct for the frame.”

TASK

AI introduces package + enum + struct.

BEFORE

Predict: one package file, separate TX/RX enums, packed frame struct.

AFTER

Strong AI uses import statements + packed struct. Weak AI inlines types per module.

TAKEAWAY

Good AI produces a single package. Bad AI duplicates definitions — defeats the purpose.

Key Takeaways

typedef enum is what localparam should have been: type-safe, debuggable, reusable.

packed struct bundles signals into a single-wire type with field access.

package is the single source of truth for shared types and constants.

 Source-level features only. Same silicon, safer code, zero runtime cost.

Name your types. The compiler, your waveforms, and next year's you will thank you.

Pre-Class Self-Check

Q1: What three things does typedef enum give you that localparam doesn't?

(1) Type safety — compiler catches cross-enum assignment. (2) GTKWave shows named states instead of integers. (3) Automatic width — no $clog2 gymnastics.

Q2: What's the difference between packed and unpacked struct?

Packed = contiguous bit vector, can be treated as a flat wire ([N-1:0] value), usable on ports. Unpacked = collection of named fields, no bit ordering guarantee, primarily for testbenches.

Pre-Class Self-Check (cont.)

Q3: You import a package. Can you redefine one of its types locally in a module?

You can shadow it with a new typedef of the same name — but this is discouraged. The point of a package is a single source of truth. If you need a different type, name it differently. Shadowing produces the confusion package import was meant to eliminate.

Q4: Do SV features (enum, struct, package) change the synthesized hardware?

No. They vanish at elaboration — the synthesizer sees the same primitives as Verilog-2001. Zero silicon cost. All the gains are at source/compile/debug time.

🔗 End of Day 13

Tomorrow: Advanced Verification

Day 14 · Assertions · AI Workflows · PPA · The Road Ahead

▸ WHY THIS MATTERS NEXT

Your testbenches so far check output values at specific times. Day 14 adds assertions — executable specifications that watch for protocol violations continuously. Plus AI-assisted verification workflows, PPA methodology, and the road ahead in hardware engineering. This is the capstone of your formal HDL education — after tomorrow you'll have every tool needed to build real silicon.