HDLBits学习笔记

Problem10——Vectors

module top_module(
	input [2:0] vec, 
	output [2:0] outv,
	output o2,
	output o1,
	output o0
);
	
	assign outv = vec;

	// This is ok too: assign {o2, o1, o0} = vec;
	assign o0 = vec[0];
	assign o1 = vec[1];
	assign o2 = vec[2];
	
endmodule

Wire型变量都用assign赋值(连接)等。

Problem 11 : Vector in more detail

向量组合了多个相关 wire 信号,通过向量名可以更方便地访问向量中信号。本题中我们将讨论有关向量的更多细节。

声明向量

向量在声明时,必须遵循:

type [upper:lower] vector_name;

其中 type 指定了向量的类型,一般为 wire 或者 reg 型。关于 reg 型,会在后续课程过程块的介绍中引入。如果向量为模块的输入输出端口,那么可以在 type 中添加 input/output 定义。

举一堆栗子

wire [7:0] w;         // 8-bit wire
reg  [4:1] x;         // 4-bit reg
output reg [0:0] y;   // 1-bit reg  output port (但仍然是一个向量)
input wire [3:-2] z;  // 6-bit wire input (在位宽中使用负数作为 index 是可以的,代表倒数第二位)
output [3:0] a;       // 4-bit output wire. wire 为默认定义,在没有显式声明的情况下
wire [0:7] b;         // 8-bit wire b[0]是这个向量的 最高位 MSB(most-significant bit)

这里你需要了解一个向量的比特顺序(endianness)信息,比特顺序取决于向量的 LSB 是向量的高位还是地位。比如声明为 [3:0] w 的向量,LSB 是 w[0],如果声明为 [0:3] w,那么 w[3] 是 LSB 。LSB 指的是二进制数中权值最低的一位。

在 Verilog 语法中,你可以将向量声明为 [3:0], 这种语法最为常见,但也可以将向量声明为 [0:3] 。这都是可以的,但必须在声明和使用时保持一致。如果声明为 wire [3:0] w ,但使用 w[0:3]赋值,这是不允许的。保持前后如一的比特顺序是很重要的一点,一些你挠破头都定位不了的 BUG 可能就是字节顺序不一致导致的。

变量隐式声明的危害

你知道吗,变量的隐式声明是 Verilog 中 BUG 的一大来源。

信号变量有两种声明方式,一是使用 wire 或者 assign 语句进行显示声明和定义,二是综合器的隐式声明和定义。

当你将一个未定义声明的信号连接到模块的输入输出端口时,综合器会“热心”地帮助你声明这个信号。但我可以向你保证,综合器没有厉害到能通过上下文,察言观色,“热心而正确”地帮你声明信号,它只会将其声明为 1 bit wire 型信号,当你本来需要使用一个超过 1 bit 的向量,但又忘记声明时,综合器往往就好心办坏事了。

(当然综合器会在这个生成 Warning,所以查看下 Warning 是查找 BUG 的好办法)

wire [2:0] a, c;    // Two vectors 
assign a = 3'b101;  // a = 101 
assign b = a;       // b =   1  隐式声明并定于了 b
wire assign c = b;  // c = 001  <-- bug 来了 b 被 coder 默认为和 a 相同的 3'b101,但其实 b 只有 1bit宽
my_module i1 (d,e); // d e 都被隐式声明为 1bit wire
                    //如果模块期望的是 vector 那么 BUG 就产生了

隐式声明的错误很容易在连接 IP 核的时候产生,从 IP 核模板文件复制来 IP 核模块后。往往会忘记声明连接 IP 模块之间的中间变量,而这些变量的隐式声明就可能被综合器“好心办了坏事”。

通过添加 `default_nettype none 宏定义会关闭隐式声明功能,那么这样一来,使用未声明的变量就会变成一个 Error 而不再只是 Warning。

如果等号左右两侧信号的位宽不同,那么就会进行截断或者补零操作。

左侧信号位宽大于右侧信号位宽,右值的低位赋予左值对应的低位,左值高位的部分赋零。

左侧信号位宽小于右侧信号位宽,右值的低位赋予左值对应的低位,右值高位的部分直接被截断。即保留右值的低位。

牛刀小试

分别输出 16 位输入信号的高 8 位 和低 8 位。

解答与分析

`default_nettype none     // Disable implicit nets. Reduces some types of bugs.
module top_module( 
    input wire [15:0] in,
    output wire [7:0] out_hi,
    output wire [7:0] out_lo );
    assign out_hi = in[15:8];
    assign out_lo = in[7:0];
endmodule

这里使用 [ : ] 语法进行了最朴素和常用的信号片选。

Problem 16 : Vector reversal 1(循环实现反向输出)

这里直接上题:给定一个 8bit 输入向量,将其反向输出。

解答与分析

module top_module (
input [7:0] in,
output [7:0] out);
assign {out[0],out[1],out[2],out[3],out[4],out[5],out[6],out[7]} = in; endmodule

这里使用左侧位连接符,比较笨的方法完成了题目。好,假设输入为 1024bit 向量,那咋办?

接下来我们将讨论两种使用循环实现的方法,可能会涉及一些没有讨论过的知识点,但请不用担心,我们将在后续的文章或者题目中详细讨论。

使用 for 循环

integer i;always @(*) begin
      for (i=0; i<8; i++) //Use integer for pure Verilog.      
out[i] = in[8-i-1];
end

我们可以在创建一个组合逻辑 always 块(后续文章中会详细解释什么是组合逻辑 always 块),在块中的组合逻辑将会按照一定的顺序运行。for 循环描述了电路的行为,而不是电路的结构,因此,for 循环必须置于比如 always 块这样的过程块中。(描述电路行为)

但需要强调的是,for 循环中的“循环”指的是代码层面的循环,而如你所知,电路是不存在循环这种的东西的,无论是信号而是门电路,都不存在循环一说。实际上,for 循环表示的代码将被综合器解析,for 循环将被分别解析为硬件电路。(不过在仿真中,确实按照循环处理)。

所以 for 循环可以理解为代码循环的语法,减少编码量,但真正的硬件电路不存在循环,还是该怎么样怎么样。

另请注意循环变量 i,HDLBits 上的 solution 中,i 定义于 for 循环的括号中,这在 Verilog 的语法中是不被允许的,是 SystemVerilog 的语法。笔者在 ISE 中实测了一下,综合会将其作为警告,但在默认情况下,仿真将会视其为错误。Verilog 的语法需要提前定义 integer 变量,即整形。

使用 generate 生成块

generate
genvar i;
for (i=0; i<8; i = i+1) begin: my_block_name
//begin: 的后面必须有几个字符才能运行,具体原因还未知
assign out[i] = in[8-i-1];
endendgenerate

generate 生成块很有意思的一点是,虽然在 generate ,endgenerate 之间使用的仍然是 for 循环,但生成块的概念和上面的 for 循环完全不同。

for 循环和 Verilog 中其他的几种循环语句 while ,forever,repeat 本质上都用于控制语句的执行次数。但生成块主要用于动态生成语句,例化 something(不只是例化模块),生成块与上述的过程块循环语句不同,并不是描述电路的一种行为。

生成块可以例化 assign 语句,模块,信号和变量的声明以及 always initial 这样的过程块。循环生成块是生成块中的一种类型,在综合过程中同样被综合器进行编译,这个过程可以看做综合过程中动态生成更多 Verilog 代码的预处理过程。在上面的例子中,generate 块在综合的过程中,综合了 8 句 assign 赋值语句。

总的来说,for 循环强调了对电路的行为描述,在综合的过程中循环展开,而生成块则用于综合过程中,动态生成代码,两者有本质上的不同。

说一点笔者在实践而不是从书本上得来的发现:在生成块中的 for 循环中不能像前例一样使用 integer 作为循环变量,而是必须使用 genvar 变量。


Problem 17 : Replication operator(构建更宽的向量)

连接操作符允许我们将短小的向量连接在一起构成更宽的向量。很方便,但有的时候需要将多个重复的向量连接在一起,诸如 assign a = {b,b,b,b,b,b}; 这样的语句写多了是非常让人忧愁的。而重复操作符语法就可以在这种情况下帮到你,允许你将一个向量重复多次,并将它们连接在一起,语法是这样:{ 重复次数 { 向量 } }。

重复次数必须是一个常量,而且请特别注意重复操作符有两对 { }.外层的 {} 不能少。

来自 HDLBits 的例子

{5{1'b1}}           // 5'b11111 (or 5'd31 or 5'h1f)
{2{a,b,c}}          // The same as {a,b,c,a,b,c}
{3'd5, {2{3'd6}}}   // 9'b101_110_110. It's a concatenation of 101 with
                    // the second vector, which is two copies of 3'b110.

如果写成 {3'd5, 2{3'd6}} ,少了一对 {} 是错误的。

第一行为例://5'b11111中“5”是位宽为5。第三行的9同理

牛刀小试

重复操作符的应用场景之一是在有符号数的扩展。有符号数的扩展是将符号位填充待扩展的比特。比如要将 4bit 的 4'b0101 有符号数扩展为 8bit ,0 是符号位,那么扩展之后为 8'b0000 0101.

现在要求你构建一个电路,将一个 8bit 有符号数扩展为 32bit 数。

解答与分析

module top_module (
    input [7:0] in,
    output [31:0] out );//

    // assign out = { replicate-sign-bit , the-input };
    assign out = {{24{in[7]}},in};
endmodule

将符号位 in[7] 的值复制扩展 24 位,后接原本的 8bit 数。下面答案中一个块儿,就是out一次输出,输出32个字符。

答案如下:第一行为输入,第二行为输出(如果正确就没答案了,所以这一行是错的),第三行是正确输出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值