跳转至

简单译码器

在学习完上一节的内容之后,实际上已经可以搭出所有的组合逻辑了。但应该没有人会觉得利用最简单的逻辑门来组合电路很容易——这个跟我直接在模块上连线有什么区别?

在本节当中,我们将通过简单译码器的例子学习新的语法,简化未来复杂电路设计的难度。

简单译码器的功能

3-8 译码器的真值表如下:

输入 A 输出 B
000 0000_0001
001 0000_0010
010 0000_0100
011 0000_1000
100 0001_0000
101 0010_0000
110 0100_0000
111 1000_0000

实现

向量信号和 reg

module decoder (
    input wire [2:0] a_i,
    output reg [7:0] b_o
);
    //TODO
endmodule

我们来观察一下这种新的信号定义:

  1. 符号仍然是 c++ 中的 []
  2. 内部是由 : 分隔开的两个数,而不像 c++ 中用长度来定义数组
  3. 出现了新的信号类型 reg

在 system verilog 中,向量信号的定义需要给出信号的最高位和最低位,因此在这里使用 [2:0] 表示宽度为 3 的信号,[7:0] 表示宽度为 8 的信号。

另一方面,请注意在 system verilog 的语义上:wire 和 reg 并没有明显区别,两种信号的区别仅在于赋值方式和使用位置的不同,下面会做详细说明。

模块体中,译码器的逻辑实现如下:

always_comb begin
    b_o = 8'h0; // (*)

    if(a_i == 3'b000) begin
        b_o[0] = 1;
    end else if(a_i == 3'b001) begin
        b_o[1] = 1;
    end else if(a_i == 3'b010) begin
        b_o[2] = 1;
    end else if(a_i == 3'b011) begin
        b_o[3] = 1;
    end else if(a_i == 3'b100) begin
        b_o[4] = 1;
    end else if(a_i == 3'b101) begin
        b_o[5] = 1;
    end else if(a_i == 3'b110) begin
        b_o[6] = 1;
    end else begin
        b_o[7] = 1;
    end 
end

字面信号量

字面信号量的基本结构如下:<signal-width>'<base><value>

  • <signal-length> 为信号量的宽度
  • <base><value> 的进制,常见的有 b (二进制), d (十进制), h (十六进制)
  • <value> 为字面信号量的数值,不支持负数,可以使用下划线分割便于阅读,如 (32'h0000_0001)

always_comb

always_comb 表示这是一个组合逻辑块,与 always_ff 区分,表示其内部赋值的 reg 均不是寄存器。代码块由 begin 和 end 包围。

我们之前说过,wire 信号必须在模块体内部使用 assign 进行赋值,这里我们将其细化一下:

  1. wire 信号必须在模块体内部,always 块外部使用 assign 进行赋值(这意味着 wire 信号不能使用 if)
  2. reg 信号必须在 always 块中赋值
  3. 所有 input 信号必须为 wire

if-else 结构除需要 begin / end 对以外,以及必须在 always 块中使用以外,与 c++ 中的 if 没有本质区别

always_comb 块的使用规范

关于阻塞赋值号 =

我们知道,硬件描述语言描述的是逻辑电路。而在电路中的信号,并不像软件中的变量,跟一段内存绑定。信号是跟电路中的物理线绑定的。

当一个信号以阻塞赋值方式被赋值后,这个信号名将与该右值的输出信号线 重新绑定 供后续使用。

而 system verilog 中,除阻塞赋值外,还有非阻塞赋值 <=

当一个信号以非阻塞赋值方式被赋值后,在 always 块内,这个信号名将仍然与赋值前的信号线绑定,离开 always 块后变为其最后一次绑定的右值对应的信号线。

举个例子:

reg [3:0] a;
reg [3:0] b;
always_comb begin
    a = 2;      //  (1)
    b = 2;      //  (2)
    b <= a + 1; //  (3) b <= 3, but b is still 2 here
    a = b + 1; //   (4) a = 3
    b <= a + 1; //  (5) b <= 4, but b is still 2 here
    a = b + 2; //   (6) a = 2 + 2
end // here b changes to 4

中间向量信号

向量信号的语法在中间信号的位置同样可用,如上。

不要尝试按照软件方式理解这个概念,请将上面这个代码与下面的示意图对应进行理解:

flowchart LR
    ainput[a=2] -- 2 --> adder1[+]
    const1[1] -- 1 --> adder1

    const2[1] -- 1 --> adder2
    binput[b=2] -- 2 --> adder2[+]

    adder2 -- a'=3 --> adder3[+]
    const3[1] -- 1 --> adder3

    binput -- 2 --> adder4[+]
    const4[2] -- 2 --> adder4

    adder4 -- a''=4 --> aoutput[output a=4]
    adder3 -- 4 --> boutput[output b=4]

在 system verilog 中,为了防止出现混乱,请不要混合使用阻塞赋值和非阻塞赋值,并在 always_comb 中,使用语义更加清晰的阻塞赋值毕竟你应该也不希望过几天之后就看不懂自己的代码吧

杜绝在多个 always 块中对同一个 reg 信号进行赋值

reg a;

always_comb begin
    a = 1;
end

always_comb begin
    a = 0; // incorrect
end

这样从语义上理解,你会将 0 和 1 短接在一起形成短路。

实际上上面这段代码不可综合,最后生成的电路不确定,因此请严格杜绝这种行为。

请保证在赋值的 always_comb 块中完整讨论了所有的情况

reg [3:0] a;
reg [3:0] b;

always_comb begin // incorrect
    if (a == 4'h0) begin
        b = 4'h1;
    end
end

在 system verilog 中,真值表中没有赋值的位置的默认值为保持原始值,这样会生成锁存器,导致电路不稳定。

因此请保证 always_comb 块覆盖了 所有可能分支

这里推荐一个比较麻烦的写法杜绝这个问题,像下面这样:

reg [3:0] a;
reg [3:0] b;

always_comb begin
    b = 4'hx; // (*)
    if (a == 4'h0) begin
        b = 4'h1;
    end
end

(*)这行代码给 b 信号设置了默认值 x,保证了所有分支被覆盖,从而杜绝了锁存器问题。

有关 X 的补充

实际上 X 表示无关,也就是你在卡诺图中使用的 X。

因此像下面这样的写法也完全是可以的:

reg [3:0] a;

always_comb begin
    if(a == 4'bXX1X) begin
        // do something
    end
end

这样可以简化 if 中条件的表达式,降低出错概率。

小结

向量信号:

[<input / output>] <wire / reg> [<upper-limit-close> : <lower-limit-close>] <signal-name>;

reg 信号:

  1. wire 信号必须在模块体内部,always 块外部使用 assign 进行赋值(这意味着 wire 信号不能使用 if)
  2. reg 信号必须在 always 块中赋值
  3. 所有 input 信号必须为 wire

if 语句块:

always_comb begin
    if (<expression>) begin
        // do something
    end else if (<expression>) begin
        // do something
    end else begin
        // do something
    end
end

always_comb 语句块:

  1. 请不要混合使用阻塞赋值和非阻塞赋值,并在 always_comb 中,使用语义更加清晰的阻塞赋值

  2. 请严格杜绝在多个 always 块中对同一个 reg 信号进行赋值

  3. 请保证在赋值的 always_comb 块中完整讨论了所有的情况

到这里完成了 System Verilog 1 的讲述,可以完成点亮数字人生和 4 位加法器 CPLD 实验。


最后更新: 2023年4月4日
作者:cuibst (99.23%), Jiajie Chen (0.77%)