Veloris.
返回索引
FPGA 2025-10-21

理解case-casex-casez的不同

4 分钟
1.2k words

理解case-casex-casez的不同

概述

在Verilog中,casecasexcasez都是用于实现多路选择逻辑的语句,但它们在处理”无关项”(don’t care)时的行为不同。理解它们的区别对于编写可靠的可综合代码至关重要。

1. case语句

1.1 基本特性

case语句是最严格的匹配方式,要求所有位都必须完全相同才能匹配成功。

  • 匹配规则:精确匹配,包括01x(未知态)、z(高阻态)
  • 如果case表达式中包含xz,只有当分支条件也在相同位置包含xz时才会匹配

1.2 代码示例

module case_example(
    input  [3:0] sel,
    output reg [1:0] out
);

always @(*) begin
    case(sel)
        4'b0000 : out = 2'b00;
        4'b0001 : out = 2'b01;
        4'b0010 : out = 2'b10;
        4'b0011 : out = 2'b11;
        default : out = 2'b00;  // 建议总是包含default分支
    endcase
end

endmodule

1.3 使用场景

  • 推荐使用:绝大多数RTL设计场景
  • 适用于:状态机的状态判断、译码器、多路选择器等需要精确匹配的场合
  • 优点:行为明确、可预测性强、综合结果确定

2. casez语句

2.1 基本特性

casez语句将z(高阻态)和?视为”无关项”(don’t care),在比较时会忽略这些位。

  • 匹配规则:z?在case表达式或分支条件中出现时,该位被视为无关项
  • x(未知态)仍然需要精确匹配
  • 常用?来表示无关位,因为更直观

2.2 代码示例

module casez_example(
    input  [7:0] data,
    output reg [2:0] priority
);

// 优先级编码器:找到最高位的1
always @(*) begin
    casez(data)
        8'b1???????: priority = 3'd7;  // 最高位为1
        8'b01??????: priority = 3'd6;  // 次高位为1
        8'b001?????: priority = 3'd5;
        8'b0001????: priority = 3'd4;
        8'b00001???: priority = 3'd3;
        8'b000001??: priority = 3'd2;
        8'b0000001?: priority = 3'd1;
        8'b00000001: priority = 3'd0;
        default    : priority = 3'd0;  // 全0的情况
    endcase
end

endmodule

2.3 使用场景

  • 适用于:优先级编码器、地址译码(部分地址匹配)
  • 优点:简化了需要忽略某些位的逻辑表达
  • 注意:仅在确实需要”无关项”匹配时使用

3. casex语句

3.1 基本特性

casex语句将x(未知态)、z(高阻态)和?都视为”无关项”,是最宽松的匹配方式。

  • 匹配规则:xz?在任何位置出现时,该位都被忽略
  • 这种宽松性可能导致意外匹配

3.2 代码示例

module casex_example(
    input  [3:0] opcode,
    output reg [1:0] alu_op
);

always @(*) begin
    casex(opcode)
        4'b00xx : alu_op = 2'b00;  // 忽略低2位
        4'b01xx : alu_op = 2'b01;
        4'b10xx : alu_op = 2'b10;
        4'b11xx : alu_op = 2'b11;
        default : alu_op = 2'b00;
    endcase
end

endmodule

3.3 使用场景

  • 几乎不推荐使用:在可综合RTL设计中应避免使用
  • 潜在问题:如果信号中意外出现x态(如仿真中的未初始化信号),可能导致意外匹配
  • 替代方案:优先使用casez,或者重新设计逻辑

4. 三者对比

4.1 匹配规则对比表

语句类型0的处理1的处理z的处理x的处理?的处理
case精确匹配精确匹配精确匹配精确匹配精确匹配
casez精确匹配精确匹配无关项精确匹配无关项
casex精确匹配精确匹配无关项无关项无关项

4.2 行为示例对比

假设有如下比较:

reg [3:0] signal = 4'b10xz;

// case匹配
case(signal)
    4'b10xz : // 匹配!
    4'b10?? : // 不匹配(?被当作普通字符)
    default : // 其他情况
endcase

// casez匹配
casez(signal)
    4'b10xz : // 匹配!(z被忽略)
    4'b10x? : // 匹配!(z和?都被忽略)
    4'b10?? : // 匹配!(两个?都被忽略)
    4'b1x?z : // 不匹配!(?和z都被忽略,x需要精确匹配)
    default : // 其他情况
endcase

// casex匹配
casex(signal)
    4'b10xz : // 匹配!(x和z都被忽略)
    4'b10?? : // 匹配!(所有?都被忽略)
    4'b1??? : // 匹配!(x、z、?都被忽略)
    4'b1x?z : // 匹配!(x、z、?都被忽略)
    default : // 其他情况
endcase

5. 设计建议与最佳实践

5.1 推荐的使用优先级

  1. 首选case:用于绝大多数RTL设计
  2. 谨慎使用casez:仅在明确需要”无关项”匹配时使用(如优先级编码器)
  3. 避免使用casex:在可综合RTL中基本不应使用

5.2 编码规范

✅ 良好实践

// 1. 使用case实现状态机
always @(posedge clk) begin
    if (rst) begin
        state_c <= IDLE;
    end else begin
        case(state_c)
            IDLE    : if(start) state_c <= LOAD;
            LOAD    : state_c <= PROCESS;
            PROCESS : if(done) state_c <= IDLE;
            default : state_c <= IDLE;  // 防止进入未定义状态
        endcase
    end
end

// 2. 使用casez实现优先级编码器
always @(*) begin
    casez(req)
        8'b1???????: grant = 3'd7;
        8'b01??????: grant = 3'd6;
        8'b001?????: grant = 3'd5;
        // ...
        default    : grant = 3'd0;
    endcase
end

❌ 不良实践

// 1. 不要在状态机中使用casex
always @(posedge clk) begin
    casex(state_c)  // 错误!状态不应有无关位
        3'b00x : // ...
    endcase
end

// 2. 不要让case语句没有default
always @(*) begin
    case(sel)
        2'b00 : out = a;
        2'b01 : out = b;
        // 缺少2'b10和2'b11的情况,会推断出锁存器!
    endcase
end

5.3 避免推断锁存器

在组合逻辑中使用case语句时,必须确保:

  1. 包含所有可能的分支,或者
  2. 包含default分支
// 方法1:列出所有情况
always @(*) begin
    case(sel)
        2'b00 : out = a;
        2'b01 : out = b;
        2'b10 : out = c;
        2'b11 : out = d;
    endcase
end

// 方法2:使用default(推荐)
always @(*) begin
    case(sel)
        2'b00   : out = a;
        2'b01   : out = b;
        2'b10   : out = c;
        default : out = d;  // 覆盖所有其他情况
    endcase
end

5.4 综合考虑

  • case:综合结果确定,生成标准的多路选择器逻辑
  • casez:综合工具会正确处理无关项,生成优化的逻辑
  • casex:综合时x会被当作无关项,但仿真行为可能与综合结果不一致

6. 仿真与综合的差异

6.1 仿真中的行为

在仿真中:

  • x态表示信号值未知(可能是0也可能是1)
  • z态表示高阻态(三态逻辑)
  • casex会将x当作无关项匹配,可能掩盖设计问题

6.2 综合后的行为

在综合中:

  • xz在RTL中仅作为匹配模式,综合后被转换为don’t care优化
  • 实际硬件中不存在x态,只有0和1
  • 如果设计依赖于x态的特殊行为,综合结果会与仿真不符

7. 实际应用示例

7.1 状态机(使用case)

module fsm_case(
    input clk,
    input rst,
    input start,
    input done,
    output reg busy
);

// 状态定义
localparam IDLE    = 3'b001;
localparam WORKING = 3'b010;
localparam FINISH  = 3'b100;

reg [2:0] state_c, state_n;

// 第一段:状态寄存器
always @(posedge clk) begin
    if (rst)
        state_c <= IDLE;
    else
        state_c <= state_n;
end

// 第二段:次态逻辑
always @(*) begin
    case(state_c)
        IDLE : begin
            if(start)
                state_n = WORKING;
            else
                state_n = state_c;
        end
        WORKING : begin
            if(done)
                state_n = FINISH;
            else
                state_n = state_c;
        end
        FINISH : begin
            state_n = IDLE;
        end
        default : state_n = IDLE;  // 安全保护
    endcase
end

// 第三段:输出逻辑
always @(posedge clk) begin
    if (rst)
        busy <= 1'b0;
    else if(state_c == WORKING)
        busy <= 1'b1;
    else
        busy <= 1'b0;
end

endmodule

7.2 优先级编码器(使用casez)

module priority_encoder(
    input  [7:0] request,
    output reg [2:0] grant_id,
    output reg grant_valid
);

always @(*) begin
    casez(request)
        8'b1???????: begin
            grant_id = 3'd7;
            grant_valid = 1'b1;
        end
        8'b01??????: begin
            grant_id = 3'd6;
            grant_valid = 1'b1;
        end
        8'b001?????: begin
            grant_id = 3'd5;
            grant_valid = 1'b1;
        end
        8'b0001????: begin
            grant_id = 3'd4;
            grant_valid = 1'b1;
        end
        8'b00001???: begin
            grant_id = 3'd3;
            grant_valid = 1'b1;
        end
        8'b000001??: begin
            grant_id = 3'd2;
            grant_valid = 1'b1;
        end
        8'b0000001?: begin
            grant_id = 3'd1;
            grant_valid = 1'b1;
        end
        8'b00000001: begin
            grant_id = 3'd0;
            grant_valid = 1'b1;
        end
        default: begin
            grant_id = 3'd0;
            grant_valid = 1'b0;
        end
    endcase
end

endmodule

8. 常见问题与调试

8.1 问题:case语句推断出锁存器

原因:组合逻辑的case语句没有覆盖所有情况

解决方案

// 方法1:在always块开头提供默认值
always @(*) begin
    out = 2'b00;  // 默认值
    case(sel)
        2'b01 : out = 2'b01;
        2'b10 : out = 2'b10;
        // 不需要完整列举,因为已有默认值
    endcase
end

// 方法2:使用default分支
always @(*) begin
    case(sel)
        2'b00   : out = 2'b00;
        2'b01   : out = 2'b01;
        2'b10   : out = 2'b10;
        default : out = 2'b11;
    endcase
end

8.2 问题:casez没有按预期匹配

原因:分支顺序错误,优先级更高的分支应该放在前面

解决方案

// 错误:低优先级在前
casez(req)
    8'b???????1: grant = 0;  // 会先匹配!
    8'b1???????: grant = 7;
endcase

// 正确:高优先级在前
casez(req)
    8'b1???????: grant = 7;  // 最高优先级
    8'b01??????: grant = 6;
    // ...
    8'b???????1: grant = 0;  // 最低优先级
endcase

8.3 问题:仿真和综合结果不一致

原因:可能使用了casex,且设计中存在x态

解决方案

  • 避免使用casex
  • 确保所有信号都正确初始化
  • 使用case或casez替代

9. 总结

关键要点

  1. case是首选:用于大多数RTL设计,行为明确可靠
  2. casez有其价值:在需要无关项匹配时使用(如优先级编码器)
  3. 避免casex:容易引入难以调试的问题
  4. 始终包含default:防止推断锁存器,提供安全默认行为
  5. 注意分支顺序:casez/casex中,先匹配的分支生效

选择决策树

需要多路选择逻辑?
  ├─ 需要忽略某些位?
  │   ├─ 是 → 使用casez(确保分支顺序正确)
  │   └─ 否 → 使用case(推荐)
  └─ 是否包含default?
      ├─ 组合逻辑 → 必须有default或覆盖所有情况
      └─ 时序逻辑 → 建议有default作为安全保护

通过正确使用这三种case语句,可以编写出清晰、可靠、易于综合的Verilog代码。

End of file.