100 种语言速通:第 29 集:Verilog

Q神日志 2023-06-27 22:05:50

硬件设计大致分为两种语言:Verilog 和 VHDL。它们都有严重的问题,人们一直在尝试编写更好的语言,但收效甚微。

Verilog 比 VHDL 简洁得多,所以让我们来做一些 Verilog。

我们将专门使用Icarus Verilog

硬件设计

Verilog 程序描述硬件。一般来说,没有变量、函数、循环、if/else 或此类奇特的功能,因为这不是硬件的工作方式。

如果您从未做过任何硬件,这里有一个极其简化的视图:

  • 我们正在设计的硬件是一堆逻辑门(AND、OR、NOT、XOR 等)和将东西连接在一起的电线
  • 每根电线都可以连接到任意多个位置,但只有一根电线可以向其输出(写入),其他所有电线只能从其输入(读取)
  • 每根电线在任何给定时间只能有 0 或 1,因此如果您需要 16 位数字,则需要 16 根电线,而 Verilog 可以方便地处理此类电线束
  • 其中一些电线连接到设备的外部(一些作为输入,一些作为输出)
  • 通常有一根称为“时钟”的特殊外部线,它不断从 0 到 1 并返回,这些转换可用于同步我们设计中的事物(这就是“CPU 时钟”所指的)
  • 一堆门和电线可以循环在一起以提供一定数量的存储位 - 我们通常将其视为“寄存器”,而不用担心 Verilog 级别的各个组件
  • Verilog 还有一些非硬件命令来帮助模拟器,我们将使用它们来进行测试

在 Verilog 中创建硬件后,您可以在模拟器上运行它 - 这就是我们要做的。或者您可以将其导出以在真实硬件(FPGA 或 ASIC)上运行。

虽然硬件主要是由大公司设计的,但这对于普通人来说并不是一件完全疯狂的事情 - 如果某种新的加密货币变得流行,人们会尝试设计专门的硬件,可以比 CPU 和 GPU 更快地挖掘它。

你好世界!

由于硬件处理数字而不是文本,因此大多数常见任务(例如 Hello、World 和 FizzBu​​zz)在 Verilog 中没有多大意义。因此,让我们首先创建一个非常简单的电路,称为“多路复用器”,使其具有 4 位宽:

  • 有4条输入线A
  • 有4条输入线B
  • 有1条输入线S
  • 有4条输出线O
  • 如果S=0,则O=A
  • 如果S=1,则O=B

所以基本上,它是O = S ? A : B,除了用电线而不是代码。

多路复用器在计算机硬件中无处不在 - 硬件实际上并没有“如果”,因此为了进行一些条件计算,它会计算这两件事,然后使用多路复用器选择它真正想要的那一个。乍一看似乎有点浪费,但现代 CPU 拥有数百亿个晶体管。

让我们写下来:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mux4bit(A, B, S, O);
  input [3:0] A;
  input [3:0] B;
  input S;
  output [3:0] O;

  wire notS;
  wire [3:0] AX;
  wire [3:0] BX;

  not(notS, S);

  and(AX[0], A[0], notS);
  and(AX[1], A[1], notS);
  and(AX[2], A[2], notS);
  and(AX[3], A[3], notS);

  and(BX[0], B[0], S);
  and(BX[1], B[1], S);
  and(BX[2], B[2], S);
  and(BX[3], B[3], S);

  or(O[0], AX[0], BX[0]);
  or(O[1], AX[1], BX[1]);
  or(O[2], AX[2], BX[2]);
  or(O[3], AX[3], BX[3]);
endmodule
</code></span></span>

 

我们分别指定每个逻辑门(1x NOT、8x AND、4x OR)以及组件外部和内部的每根导线。

当然,真正的 Verilog 实际上并不使用这种极其冗长的风格,稍后我们将介绍一些更简洁的语法。

逐步解释:

  • module mux4bit(A, B, S, O);- 模块的启动,以及我们实际使用该模块时需要连接的外部电线列表
  • input [3:0] A;- 输入线 A,4 位宽
  • input S;- 输入线S,1位宽,所以我们不需要指定它
  • output [3:0] O;- 输出线 O,4 位宽
  • wire notS- 内部线,1位宽
  • wire [3:0] AX- 内部线,4位宽
  • not(notS, S);- 非门,它S作为输入和输出notS
  • and(AX[0], A[0], notS)等等 - 四个与门,结果是 ,AX[i] = A[i] & ~SAX = S ? 0 : A
  • and(BX[0], B[0], S)等等 - 四个与门,结果是 ,BX[i] = B[i] & SAX = S ? B : 0
  • or(O[0], AX[0], BX[0])等等 - 四个或门,结果O[i] = AX[i] | BX[i], 或O = AX | BX, 或O = S ? B : A

现在我们需要编写一个“测试台”。为复杂设备编写测试平台确实是一个复杂的主题,但我们将在这里做一些非常琐碎的事情。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mux4bit_tb;
  reg [3:0] A;
  reg [3:0] B;
  reg S;
  wire [3:0] O;

  mux4bit M(A, B, S, O);

  initial begin
    if (!$value$plusargs("A=%d", A)) begin
      $display("ERROR: please specify +A=<value>");
      $finish;
    end

    if (!$value$plusargs("B=%d", B)) begin
      $display("ERROR: please specify +B=<value>");
      $finish;
    end

    S = 0;
    #1;
    $display("A = %d, B = %d, S = %d, O = %d", A, B, S, O);

    S = 1;
    #1;
    $display("A = %d, B = %d, S = %d, O = %d", A, B, S, O);

    $finish;
  end
endmodule
</code></span></span>

 

让我们尝试使用它:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ iverilog -o mux4bit_tb.vvp mux4bit.v mux4bit_tb.v
% ./mux4bit_tb.vvp +A=7 +B=3
A =  7, B =  3, S = 0, O =  7
A =  7, B =  3, S = 1, O =  3
% ./mux4bit_tb.vvp +A=4 +B=2
A =  4, B =  2, S = 0, O =  4
A =  4, B =  2, S = 1, O =  2
% ./mux4bit_tb.vvp +A=1 +B=6
A =  1, B =  6, S = 0, O =  1
A =  1, B =  6, S = 1, O =  6
% ./mux4bit_tb.vvp +A=2 +B=2
A =  2, B =  2, S = 0, O =  2
A =  2, B =  2, S = 1, O =  2
</code></span></span>

 

正如您所看到的,我们将所有模块文件一起编译成一个vvp文件,然后我们可以选择将一些参数传递给模拟,然后模拟会执行一些操作。

让我们看一下我们的测试台代码:

  • reg [3:0] A- 我们声明一个寄存器,它就像一个变量,这个是4位宽
  • reg S- 寄存器,1位宽
  • mux4bit M(A, B, S, O);- 我们的组件mux4bit_tb使用另一个组件mux4bit- 为了实例化一个组件,我们需要指定它的所有输入和输出连接到的内容。在这种情况下,A、B和S连接到测试台的寄存器,O连接到一些内部电线。
  • initial begin ... end- 通常我们会用它来指定组件的启动状态,例如计数器开始于等。0由于这是测试平台而不是真正的组件,我们可以在那里放置很多代码
  • (!$value$plusargs("A=%d", A))A从命令行获取,false如果没有通过则返回。首先将$其标记为不属于常规硬件的一部分。
  • $display- 基本上printf(),用 标记$以明确它的测试命令不是硬件的一部分。
  • $finish- 结束模拟
  • S = 0- 分配0给寄存器S
  • #1等待 1 个时钟周期 - Verilog 允许每个组件指定重新计算值所需的时间,因此您可以模拟复杂的时序,但我们不会使用任何此类功能。
  • $display("A = %d, B = %d, S = %d, O = %d", A, B, S, O);- 打印所有输入和输出 -O只计算因为我们等待了一个刻度,否则它将是空的
  • S = 1- 分配0给寄存器S- 我们等待了一个时间点,所以我们不会尝试同时写入两个不同的值(通常是一个坏主意)
  • #1等待另一个刻度 - 否则O仍然具有旧值
  • $display("A = %d, B = %d, S = %d, O = %d", A, B, S, O);- 因为我们等了,O现在更新了

希望这听起来不太疯狂。

更简洁的多路复用器

我以极其明确的风格展示了第一个多路复用器,但当然没有人这样写。这是 8 位多路复用器:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mux8bit(A, B, S, O);
  input [7:0] A;
  input [7:0] B;
  input S;
  output [7:0] O;

  assign O = S ? B : A;
endmodule
</code></span></span>

 

它几乎相当于长代码。assign O = S ? B : A;设置所有电线和逻辑门以使该表达式起作用。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mux8bit_tb;
  reg [7:0] A;
  reg [7:0] B;
  reg S;
  wire [7:0] O;

  reg [31:0] i;

  mux8bit M(A, B, S, O);

  initial begin
    $monitor("A=%d B=%d S=%d O=%D", A, B, S, O);

    for (i=0; i<256*256*2; i=i+1) begin
      {A,B,S} = i;
      #1;
    end

    $finish;
  end
endmodule
</code></span></span>

 

我们可以告诉测试台生成每个可能的值,而不是传递特定值。我们可以有三重嵌套for,但i已经包含 8+8+1 位(以及一些额外的位),因此{A,B,S} = i将分配正确的位iA(位 16 到 9)、B(位 8 到 1)和S(位 0)。

我们可以检查每个值,或者只是获取随机样本(randsampleunix-utilities集合中):

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ iverilog -o mux8bit_tb.vvp mux8bit.v mux8bit_tb.v
$ ./mux8bit_tb.vvp | randsample 20
I=     15911, A= 31 B= 19 S=1 O= 19
I=    113470, A=221 B=159 S=0 O=221
I=     28346, A= 55 B= 93 S=0 O= 55
I=     18224, A= 35 B=152 S=0 O= 35
I=     84536, A=165 B= 28 S=0 O=165
I=     28159, A= 54 B=255 S=1 O=255
I=    123640, A=241 B=124 S=0 O=241
I=     15950, A= 31 B= 39 S=0 O= 31
I=     50381, A= 98 B=102 S=1 O=102
I=     33082, A= 64 B=157 S=0 O= 64
I=     77032, A=150 B=116 S=0 O=150
I=     52870, A=103 B= 67 S=0 O=103
I=     63252, A=123 B=138 S=0 O=123
I=     59551, A=116 B= 79 S=1 O= 79
I=    104170, A=203 B=117 S=0 O=203
I=    126954, A=247 B=245 S=0 O=247
I=    130665, A=255 B= 52 S=1 O= 52
I=     14603, A= 28 B=133 S=1 O=133
I=    118586, A=231 B=157 S=0 O=231
I=     82511, A=161 B= 39 S=1 O= 39
</code></span></span>

 

$monitor每当其任何参数发生更改时都会显示该值,因此我们不需要$display在循环中执行操作,只需设置一次即可。

奇偶

这是一个非常简单的模块,用于检查传递的 16 位数字是奇数还是偶数。它只能检查最低位:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module oddeven(A, O);
  input [15:0] A;
  output O;

  assign O = A[0];
endmodule
</code></span></span>

 

对于更复杂的组件,测试每个可能的值并不实际。一种常见的方法是随机测试,所以让我们尝试一下:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module oddeven_tb;
  reg [15:0] A;
  wire O;

  reg [31:0] i;

  oddeven OE(A, O);

  initial begin
    $monitor("A=%d O=%d", A, O);

    for (i=0; i<20; i=i+1) begin
      A = $random;
      #1;
    end

    $finish;
  end
endmodule
</code></span></span>

 

我们可以用它来运行:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ iverilog -o oddeven.vvp oddeven.v oddeven_tb.v
$ ./oddeven.vvp
A=13604 O=0
A=24193 O=1
A=54793 O=1
A=22115 O=1
A=31501 O=1
A=39309 O=1
A=33893 O=1
A=21010 O=0
A=58113 O=1
A=52493 O=1
A=61814 O=0
A=52541 O=1
A=22509 O=1
A=63372 O=0
A=59897 O=1
A= 9414 O=0
A=33989 O=1
A=53930 O=0
A=63461 O=1
A=29303 O=1
</code></span></span>

 

模数学

在讨论 FizzBu​​zz 之前,我们需要弄清楚如何处理被 3 和 5 整除的问题,而不需要在设计中添加硬件除法模块,因为它们需要大量晶体管。

以下是有关模运算的基本数学事实:

  • (A + B) % M = ((A % M) + (B % M)) % M
  • (A * B) % M = ((A % M) * (B % M)) % M

嗯,这有什么关系呢?

它可以用于一个聪明的算法:

  • 对于 1 位数字 A,我们知道它模 3 或 5 - 它总是 A(1 或 0)
  • 所以通过归纳,假设我们有一个算法来计算模 M 的 N 位数字,并且我们想用它来实现 2N 位数字
  • 假设 2N 位数字是(A * 2^N) + B
  • 我们可以预先计算,(2^N mod M)因为它是一个常数
  • 我们知道(A % M)(从小规模算法)和(2^N mod M)(因为它是一个常数),然后我们可以使用 M*M 尺寸表来计算(A * 2^N) % M
  • 我们知道(A * 2^N) % M(从上一步)和B mod M(从小规模算法),然后我们可以使用 M*M 尺寸表来计算(A * 2^N) + B

模 3 分量

我们将有很多组件,每个组件都有 3 根导线,指示除以 3 是否得到 0、1 或 2。

每个组件都在与其名称相对应的文件中,我将它们全部列出来:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mod3_32bit(A, Mod);
  input [31:0] A;
  output [2:0] Mod;

  wire [2:0] ModHi;
  wire [2:0] ModLow;

  mod3_16bit Hi(A[31:16], ModHi);
  mod3_16bit Low(A[15:0], ModLow);

  // No need to do shift as (2**16) % 3 == 1
  mod3_add Add(ModHi, ModLow, Mod);
endmodule

module mod3_16bit(A, Mod);
  input [15:0] A;
  output [2:0] Mod;

  wire [2:0] ModHi;
  wire [2:0] ModLow;

  mod3_8bit Hi(A[15:8], ModHi);
  mod3_8bit Low(A[7:0], ModLow);

  // No need to do shift as (2**8) % 3 == 1
  mod3_add Add(ModHi, ModLow, Mod);
endmodule

module mod3_8bit(A, Mod);
  input [7:0] A;
  output [2:0] Mod;

  wire [2:0] ModHi;
  wire [2:0] ModLow;

  mod3_4bit Hi(A[7:4], ModHi);
  mod3_4bit Low(A[3:0], ModLow);

  // No need to do shift as (2**4) % 3 == 1
  mod3_add Add(ModHi, ModLow, Mod);
endmodule

module mod3_4bit(A, Mod);
  input [3:0] A;
  output [2:0] Mod;

  wire [2:0] ModHi;
  wire [2:0] ModLow;

  mod3_2bit Hi(A[3:2], ModHi);
  mod3_2bit Low(A[1:0], ModLow);

  // No need to do shift as (2**2) % 3 == 1
  mod3_add Add(ModHi, ModLow, Mod);
endmodule

module mod3_2bit(A, Mod);
  input [1:0] A;
  output [2:0] Mod;
  // 2      => 2 mod 3
  assign Mod[2] = A[1] & ~A[0];
  // 1      => 1 mod 3
  assign Mod[1] = ~A[1] & A[0];
  // 0 or 3 => 0 mod 3
  assign Mod[0] = (~A[0] & ~A[1]) | (A[0] & A[1]);
endmodule

module mod3_add(ModA, ModB, ModOut);
  input [2:0] ModA;
  input [2:0] ModB;
  output [2:0] ModOut;

  // 0 + 0 = 0 => 0
  // 1 + 2 = 3 => 0
  // 2 + 1 = 3 => 0
  assign ModOut[0] = (ModA[0] & ModB[0]) | (ModA[1] & ModB[2]) | (ModA[2] & ModB[1]);

  // 0 + 1 = 1 => 1
  // 1 + 0 = 1 => 1
  // 2 + 2 = 4 => 1
  assign ModOut[1] = (ModA[0] & ModB[1]) | (ModA[1] & ModB[0]) | (ModA[2] & ModB[2]);

  // 0 + 2 = 2 => 2
  // 2 + 0 = 2 => 2
  // 1 + 1 = 2 => 2
  assign ModOut[2] = (ModA[0] & ModB[2]) | (ModA[2] & ModB[0]) | (ModA[1] & ModB[1]);
endmodule

module mod3_tb;
  reg [31:0] A;
  wire [2:0] Mod;

  reg [31:0] i;

  mod3_32bit M(A, Mod);

  initial begin
    $monitor("A=%d O[0]=%d O[1]=%d O[2]=%d", A, Mod[0], Mod[1], Mod[2]);

    for (i=0; i<20; i=i+1) begin
      A = $random;
      #1;
    end

    $finish;
  end
endmodule
</code></span></span>

 

代码很多,但其实没什么新意:

  • mod3_tb与我们用于奇偶测试的测试台相同,只是打印不同
  • mod3_32bitmod3_16bitmod3_8bit、 以及mod3_4bit所有这些都只需设置 2 个较小的模块,然后将它们的结果加在一起
  • mod3_2bit可以用 做到这一点mod3_1bit,但它只是用一些逻辑门计算输出
  • mod3_add取两个余数,并返回它们的和(每条线都是潜在的余数)

一般来说,这个递归可能需要进行一些位重新洗牌,但碰巧的是,对于除以 3,我们不需要做任何事情(mod3_2bit如果我们与其他递归实现相同,则需要执行重新洗牌步骤)。

我们现在可以运行它:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ ./mod3_tp.vvp
A= 303379748 O[0]=0 O[1]=0 O[2]=1
A=3230228097 O[0]=1 O[1]=0 O[2]=0
A=2223298057 O[0]=0 O[1]=1 O[2]=0
A=2985317987 O[0]=0 O[1]=0 O[2]=1
A= 112818957 O[0]=1 O[1]=0 O[2]=0
A=1189058957 O[0]=0 O[1]=0 O[2]=1
A=2999092325 O[0]=0 O[1]=0 O[2]=1
A=2302104082 O[0]=0 O[1]=1 O[2]=0
A=  15983361 O[0]=1 O[1]=0 O[2]=0
A= 114806029 O[0]=0 O[1]=1 O[2]=0
A= 992211318 O[0]=1 O[1]=0 O[2]=0
A= 512609597 O[0]=0 O[1]=0 O[2]=1
A=1993627629 O[0]=1 O[1]=0 O[2]=0
A=1177417612 O[0]=0 O[1]=1 O[2]=0
A=2097015289 O[0]=0 O[1]=1 O[2]=0
A=3812041926 O[0]=1 O[1]=0 O[2]=0
A=3807872197 O[0]=0 O[1]=1 O[2]=0
A=3574846122 O[0]=1 O[1]=0 O[2]=0
A=1924134885 O[0]=1 O[1]=0 O[2]=0
A=3151131255 O[0]=1 O[1]=0 O[2]=0
</code></span></span>

 

模 5 分量

这与模 3 基本相同,只是mod5_4bit需要进行一些重新洗牌。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module mod5_32bit(A, Mod);
  input [31:0] A;
  output [4:0] Mod;

  wire [4:0] ModHi;
  wire [4:0] ModLow;

  mod5_16bit Hi(A[31:16], ModHi);
  mod5_16bit Low(A[15:0], ModLow);

  // No need to do shift as (2**16) % 5 == 1
  mod5_add Add(ModHi, ModLow, Mod);
endmodule

module mod5_16bit(A, Mod);
  input [15:0] A;
  output [4:0] Mod;

  wire [4:0] ModHi;
  wire [4:0] ModLow;

  mod5_8bit Hi(A[15:8], ModHi);
  mod5_8bit Low(A[7:0], ModLow);

  // No need to do shift as (2**8) % 5 == 1
  mod5_add Add(ModHi, ModLow, Mod);
endmodule

module mod5_8bit(A, Mod);
  input [7:0] A;
  output [4:0] Mod;

  wire [4:0] ModHi;
  wire [4:0] ModLow;

  mod5_4bit Hi(A[7:4], ModHi);
  mod5_4bit Low(A[3:0], ModLow);

  // No need to do shift as (2**4) % 5 == 1
  mod5_add Add(ModHi, ModLow, Mod);
endmodule

module mod5_4bit(A, Mod);
  input [3:0] A;
  output [4:0] Mod;

  wire [4:0] ModHi;
  wire [4:0] ModHi4;
  wire [4:0] ModLow;

  mod5_2bit Hi(A[3:2], ModHi);
  mod5_2bit Low(A[1:0], ModLow);

  // We need to do shift as (2**2) % 5 == 4
  // Notice that if we do this reshuffle twice,
  // everything will be back where it started
  // That's why 8bit and bigger components don't
  // need to do any reshuffle.
  // mod5_8bit should do this 2x, but that's same as not doing it.
  // mod5_16bit should do this 4x, but that's same as not doing it etc.
  assign ModHi4[0] = ModHi[0]; // 0 * 4 = 0  => 0
  assign ModHi4[4] = ModHi[1]; // 1 * 4 = 4  => 4
  assign ModHi4[3] = ModHi[2]; // 2 * 4 = 8  => 3
  assign ModHi4[2] = ModHi[3]; // 3 * 4 = 12 => 2
  assign ModHi4[1] = ModHi[4]; // 4 * 4 = 16 => 1

  mod5_add Add(ModHi4, ModLow, Mod);
endmodule

module mod5_2bit(A, Mod);
  input [1:0] A;
  output [4:0] Mod;
  // no 2 bit number is 4 mod 5
  assign Mod[4] = 0;
  // numbers 0-3 are themselves mod 5
  assign Mod[3] = A[0] & A[1];
  assign Mod[2] = A[1] & ~A[0];
  assign Mod[1] = ~A[1] & A[0];
  assign Mod[0] = ~A[0] & ~A[1];
endmodule

module mod5_add(ModA, ModB, ModOut);
  input [4:0] ModA;
  input [4:0] ModB;
  output [4:0] ModOut;

  // 0 + 0 = 0 => 0
  // 1 + 4 = 5 => 0
  // 2 + 3 = 5 => 0
  // 3 + 2 = 5 => 0
  // 4 + 1 = 5 => 0
  assign ModOut[0] = (ModA[0] & ModB[0]) | (ModA[1] & ModB[4]) | (ModA[2] & ModB[3]) | (ModA[3] & ModB[2]) | (ModA[4] & ModB[1]);

  // 0 + 1 = 1 => 1
  // 1 + 0 = 1 => 1
  // 2 + 4 = 6 => 1
  // 3 + 3 = 6 => 1
  // 4 + 2 = 6 => 1
  assign ModOut[1] = (ModA[0] & ModB[1]) | (ModA[1] & ModB[0]) | (ModA[2] & ModB[4]) | (ModA[3] & ModB[3]) | (ModA[4] & ModB[2]);

  // 0 + 2 = 2 => 2
  // 1 + 1 = 2 => 2
  // 2 + 0 = 2 => 2
  // 3 + 4 = 7 => 2
  // 4 + 3 = 7 => 2
  assign ModOut[2] = (ModA[0] & ModB[2]) | (ModA[1] & ModB[1]) | (ModA[2] & ModB[0]) | (ModA[3] & ModB[4]) | (ModA[4] & ModB[3]);

  // 0 + 3 = 3 => 3
  // 1 + 2 = 3 => 3
  // 2 + 1 = 3 => 3
  // 3 + 0 = 3 => 3
  // 4 + 4 = 8 => 3
  assign ModOut[3] = (ModA[0] & ModB[3]) | (ModA[1] & ModB[2]) | (ModA[2] & ModB[1]) | (ModA[3] & ModB[0]) | (ModA[4] & ModB[4]);

  // 0 + 4 = 4 => 4
  // 1 + 3 = 4 => 4
  // 2 + 2 = 4 => 4
  // 3 + 1 = 4 => 4
  // 4 + 0 = 4 => 4
  assign ModOut[4] = (ModA[0] & ModB[4]) | (ModA[1] & ModB[3]) | (ModA[2] & ModB[2]) | (ModA[3] & ModB[1]) | (ModA[4] & ModB[0]);
endmodule

module mod5_tb;
  reg [31:0] A;
  wire [4:0] Mod;

  reg [31:0] i;

  mod5_32bit M(A, Mod);

  initial begin
    $monitor("A=%d O[0]=%d O[1]=%d O[2]=%d O[3]=%d O[4]=%d", A, Mod[0], Mod[1], Mod[2], Mod[3], Mod[4]);

    for (i=0; i<20; i=i+1) begin
      A = $random;
      #1;
    end

    $finish;
  end
endmodule
</code></span></span>

 

mod5_4bit除了我添加一些注释的地方之外,所有模块都只是同一事物的更广泛版本。

我们可以看到它的实际效果:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$  ./mod5_tp.vvp
A= 303379748 O[0]=0 O[1]=0 O[2]=0 O[3]=1 O[4]=0
A=3230228097 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=2223298057 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=2985317987 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A= 112818957 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=1189058957 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=2999092325 O[0]=1 O[1]=0 O[2]=0 O[3]=0 O[4]=0
A=2302104082 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=  15983361 O[0]=0 O[1]=1 O[2]=0 O[3]=0 O[4]=0
A= 114806029 O[0]=0 O[1]=0 O[2]=0 O[3]=0 O[4]=1
A= 992211318 O[0]=0 O[1]=0 O[2]=0 O[3]=1 O[4]=0
A= 512609597 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=1993627629 O[0]=0 O[1]=0 O[2]=0 O[3]=0 O[4]=1
A=1177417612 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=2097015289 O[0]=0 O[1]=0 O[2]=0 O[3]=0 O[4]=1
A=3812041926 O[0]=0 O[1]=1 O[2]=0 O[3]=0 O[4]=0
A=3807872197 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=3574846122 O[0]=0 O[1]=0 O[2]=1 O[3]=0 O[4]=0
A=1924134885 O[0]=1 O[1]=0 O[2]=0 O[3]=0 O[4]=0
A=3151131255 O[0]=1 O[1]=0 O[2]=0 O[3]=0 O[4]=0
</code></span></span>

 

菲兹巴兹

现在我们有模块告诉我们某个东西是否能被 5 或 3 整除,我们可以做 FizzBu​​zz 了。

我们的 FizzBu​​zz 不会进行任何打印,它有 1 个 32 位数字输入,以及 4 条输出线- PrintFizzPrintBuzzPrintFizzBuzzPrintNumber我们可以将它连接到一些打印组件来进行实际的打印。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>module fizzbuzz(A, PrintNumber, PrintFizz, PrintBuzz, PrintFizzBuzz);
  input [31:0] A;
  output PrintNumber, PrintFizz, PrintBuzz, PrintFizzBuzz;
  wire [2:0] Mod3;
  wire [4:0] Mod5;

  mod3_32bit M3(A, Mod3);
  mod5_32bit M5(A, Mod5);

  assign PrintNumber   = ~Mod3[0] & ~Mod5[0];
  assign PrintFizz     = Mod3[0] & ~Mod5[0];
  assign PrintBuzz     = ~Mod3[0] & Mod5[0];
  assign PrintFizzBuzz = Mod3[0] & Mod5[0];
endmodule

module fizzbuzz_tb;
  reg [31:0] A;
  wire PrintNumber, PrintFizz, PrintBuzz, PrintFizzBuzz;

  reg [31:0] i;

  fizzbuzz M(A, PrintNumber, PrintFizz, PrintBuzz, PrintFizzBuzz);

  initial begin
    $monitor("A=%d Number=%d Fizz=%d Buzz=%d FizzBuzz=%d", A, PrintNumber, PrintFizz, PrintBuzz, PrintFizzBuzz);

    for (i=1; i<=100; i=i+1) begin
      A = i;
      #1;
    end

    $finish;
  end
endmodule
</code></span></span>

 

它完全有效:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>$ ./fizzbuzz_tp.vvp
A=         1 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=         2 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=         3 Number=0 Fizz=1 Buzz=0 FizzBuzz=0
A=         4 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=         5 Number=0 Fizz=0 Buzz=1 FizzBuzz=0
A=         6 Number=0 Fizz=1 Buzz=0 FizzBuzz=0
A=         7 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=         8 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=         9 Number=0 Fizz=1 Buzz=0 FizzBuzz=0
A=        10 Number=0 Fizz=0 Buzz=1 FizzBuzz=0
A=        11 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        12 Number=0 Fizz=1 Buzz=0 FizzBuzz=0
A=        13 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        14 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        15 Number=0 Fizz=0 Buzz=0 FizzBuzz=1
A=        16 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        17 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        18 Number=0 Fizz=1 Buzz=0 FizzBuzz=0
A=        19 Number=1 Fizz=0 Buzz=0 FizzBuzz=0
A=        20 Number=0 Fizz=0 Buzz=1 FizzBuzz=0
</code></span></span>

 

你应该使用 Verilog 吗?

在这一集中,我使用了非常低级和冗长的 Verilog,并且仅使用打印进行测试。在现实生活中,您通常会在更高的水平上进行编写,并且有许多复杂的可视化工具用于测试和模拟电路。因此,不要将此事件视为典型 Verilog 的代表。

据我所知,实际的硬件设计人员似乎对 Verilog 和 VHDL 都非常不满意,而且新的硬件设计语言一直在被创建。但就目前而言,它几乎是压倒性的标准(VHDL 是一种更冗长但在其他方面非常相似的语言)。

硬件设计中的另一个关键技能是自动验证,为此您需要了解Z3 这样的逻辑语言

对于想玩电子产品的人,我首先推荐一款视频游戏。我个人最喜欢《硅零》深圳I/O更多的是对微控制器进行编程而不是制作电路,但它也很受欢迎。一旦你玩了一堆这样的游戏,并想尝试一些更真实的东西,Verilog 就是一种值得尝试的有趣语言。谁知道呢,也许您会为下一个大型加密货币设计 FPGA 或 ASIC 并致富。

...全文
235 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

6

社区成员

发帖
与我相关
我的任务
社区描述
分享
java-rocketmqpygame前端 个人社区 广东省·广州市
社区管理员
  • Q shen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧