多路分支
case 是 Verilog 的扁平分派结构。你给它一个表达式,它选出匹配的分支:
case (expression)
pattern_1: statement_1;
pattern_2: statement_2;
pattern_3: begin
statement_3a;
statement_3b;
end
default: default_statement;
endcase
它在结构上类似 C 语言的 switch,但是:
- 没有
break,每个分支在下一个分支开始处自动结束。 - 匹配模式可以是向量,而不仅仅是整数。
- 综合工具会把整个结构变成一个扁平的多路选择器(模式允许时则是独热码译码器)。
完整示例:4 选 1 多路选择器
case 体中有四个显式模式和一个 default。综合工具看到一个 2 位输入映射到四个值之一,于是生成一个 4 选 1 多路选择器。清晰、扁平、快速。
为什么需要 default
对于组合逻辑中的 case,省略 default 和 if 没有 else 是同一个陷阱:任何未匹配的输入值都会让 out 没有被赋值,综合工具就会推断出一个锁存器。
对于 2 位的 sel,上面的模式覆盖了全部四种可能的值,所以理论上 default 是多余的。但在实践中:
- 综合工具并不总能证明分支已经穷尽。
- 在仿真中,选择信号可能是
x或z,它不匹配任何显式分支。 - 以后添加新分支时,默认行为可能就没有定义了。
务必写上 default。对于你确定 default 不可达的状态机和多路选择逻辑,赋值为 'x:
default: out = 8'bx;
……这会告诉综合工具“这是无关值,可以随意优化”,而如果不可达的分支真的被执行到了,仿真中会出现醒目的红色 x。两全其美。
用 case 实现状态机
case 的经典用法是有限状态机的状态转移逻辑:
case (state) 块就是状态转移逻辑。每个分支决定下一个状态是什么,以及在这个状态停留多久。这里的 default 是不可达的(我们在 2 位的取值空间里穷尽了 RED、GREEN、YELLOW),但它作为安全网存在:如果 state 不知怎么变成了 2'd3,状态机会干净地复位到 RED,而不会产生锁存。
有限状态机一文会更深入地讲解这种模式。
一个分支匹配多个模式
你可以用逗号分隔,列出共用同一条语句的多个模式:
case (opcode)
4'h0, 4'h1, 4'h2: result = a + b;
4'h3, 4'h4: result = a - b;
4'h8: result = a & b;
default: result = 8'd0;
endcase
这里有两个操作码都表示“减法”,三个表示“加法”。综合工具会把这些模式“或”在一起,作为比较器。
casez 和 casex:无关位匹配
有时你想匹配部分位不确定的模式,比如“任何以 010 开头的操作码”:
casez (opcode)
8'b010?_????: instruction = ALU_OP;
8'b110?_????: instruction = LOAD_OP;
8'b1110_????: instruction = JUMP_OP;
default: instruction = UNKNOWN;
endcasez
casez 把模式中的 ?(以及 z)当作无关位。每个 ? 匹配 0 或 1。在译码指令格式时很有用,因为某些类别的操作码不使用其中一些位。
casex 进一步把 x 也当作无关位。casex 很危险,因为未初始化的信号(在仿真中是 x)会匹配所有分支,产生出人意料的行为。大多数现代编码规范推荐使用 casez,并禁止使用 casex。
SystemVerilog 中还有 case inside,它是所有版本中最清晰的,接受范围和列表;但普通 Verilog 只到 casez 为止。
case 与 if/else if 链
这两种结构都能表达多路选择,但综合出的硬件不同:
case是扁平分派。综合工具可以生成独热码译码器、平衡的多路选择器树或其他扁平结构,求值时间恒定。if/else if是优先级链。综合工具生成级联结构,每多一级就多一份延迟,按对数关系变慢。
功能上两者有重叠。风格上:只要条件是关于单个表达式的值,就用 case;当存在真正的优先级,或者条件涉及不同信号时,用 if/else if。
// Better as case:
if (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else out = d;
// Better as if/else if:
if (urgent_event) next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready) next_state = PROCESS;
else next_state = state;
前三个条件都在问“sel 是多少?”,用 case 读起来更自然,综合结果也更扁平。后三个是有明显优先级的独立事件,更适合用 if/else if。
接下来
本章最后一篇文档 for 循环讲解 Verilog 的 for,以及在可综合代码中使用它时会发生的意外情况。之后我们将正式进入时序逻辑和有限状态机。
常见问题
Verilog 中的 case 语句是什么?
case (expr) ... endcase 是 Verilog 的多路分支结构。它对表达式求值一次,然后跳转到匹配的分支。状态机、操作码译码器、多路选择器,以及其他从几个互斥选项中选出一个的场景,都习惯用它来写。
Verilog 中 case、casex 和 casez 有什么区别?
case 按位精确匹配,包括 x 和 z 值。casez 把分支项中的 z(以及 ?)当作无关位。casex 把 x 和 z 都当作无关位。无关位匹配适合某些位无关紧要的操作码模式,但 casex 在仿真中很危险,因为未初始化的信号(x)可能意外地匹配所有分支。
为什么 Verilog case 语句需要 default?
没有 default 时,综合工具会认为可能没有任何分支匹配,于是判定输出信号必须保持之前的值,从而推断出一个不需要的锁存器。default 分支处理所有未匹配的值,通常是把输出设为一个安全值,或者用 x 赋值把这种情况标记为不可达。务必加上它。
在 Verilog 中什么时候用 case,什么时候用 if-else?
当条件互斥、并且根据单个表达式的值进行分派时用 case,例如状态机、操作码译码器、多路选择器。当存在真正的优先级顺序,或者条件涉及不同的信号时用 if/else。与很长的 else if 链相比,case 综合出的硬件更扁平、更快。