Menu

Verilog case 语句:正确实现多路译码

如何用 case 写出清晰的多路译码,绝不能省略的 default,以及 case、casex 和 casez 之间的区别。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

多路分支

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 是多余的。但在实践中:

  1. 综合工具并不总能证明分支已经穷尽。
  2. 在仿真中,选择信号可能是 x 或 z,它不匹配任何显式分支。
  3. 以后添加新分支时,默认行为可能就没有定义了。

务必写上 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 综合出的硬件更扁平、更快。

Coddy 编程语言插图

和 Coddy 一起学编程

开始