6
社区成员
发帖
与我相关
我的任务
分享硬件设计大致分为两种语言:Verilog 和 VHDL。它们都有严重的问题,人们一直在尝试编写更好的语言,但收效甚微。
Verilog 比 VHDL 简洁得多,所以让我们来做一些 Verilog。
我们将专门使用Icarus Verilog。
Verilog 程序描述硬件。一般来说,没有变量、函数、循环、if/else 或此类奇特的功能,因为这不是硬件的工作方式。
如果您从未做过任何硬件,这里有一个极其简化的视图:
在 Verilog 中创建硬件后,您可以在模拟器上运行它 - 这就是我们要做的。或者您可以将其导出以在真实硬件(FPGA 或 ASIC)上运行。
虽然硬件主要是由大公司设计的,但这对于普通人来说并不是一件完全疯狂的事情 - 如果某种新的加密货币变得流行,人们会尝试设计专门的硬件,可以比 CPU 和 GPU 更快地挖掘它。
由于硬件处理数字而不是文本,因此大多数常见任务(例如 Hello、World 和 FizzBuzz)在 Verilog 中没有多大意义。因此,让我们首先创建一个非常简单的电路,称为“多路复用器”,使其具有 4 位宽:
所以基本上,它是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作为输入和输出notSand(AX[0], A[0], notS)等等 - 四个与门,结果是 ,AX[i] = A[i] & ~S或AX = S ? 0 : Aand(BX[0], B[0], S)等等 - 四个与门,结果是 ,BX[i] = B[i] & S或AX = S ? B : 0or(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将分配正确的位i到A(位 16 到 9)、B(位 8 到 1)和S(位 0)。
我们可以检查每个值,或者只是获取随机样本(randsample从unix-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>
在讨论 FizzBuzz 之前,我们需要弄清楚如何处理被 3 和 5 整除的问题,而不需要在设计中添加硬件除法模块,因为它们需要大量晶体管。
以下是有关模运算的基本数学事实:
嗯,这有什么关系呢?
它可以用于一个聪明的算法:
(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 是否得到 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_32bit、mod3_16bit、mod3_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>
这与模 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 整除,我们可以做 FizzBuzz 了。
我们的 FizzBuzz 不会进行任何打印,它有 1 个 32 位数字输入,以及 4 条输出线- PrintFizz、PrintBuzz和PrintFizzBuzz。PrintNumber我们可以将它连接到一些打印组件来进行实际的打印。
<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 和 VHDL 都非常不满意,而且新的硬件设计语言一直在被创建。但就目前而言,它几乎是压倒性的标准(VHDL 是一种更冗长但在其他方面非常相似的语言)。
硬件设计中的另一个关键技能是自动验证,为此您需要了解Z3 这样的逻辑语言。
对于想玩电子产品的人,我首先推荐一款视频游戏。我个人最喜欢《硅零》。深圳I/O更多的是对微控制器进行编程而不是制作电路,但它也很受欢迎。一旦你玩了一堆这样的游戏,并想尝试一些更真实的东西,Verilog 就是一种值得尝试的有趣语言。谁知道呢,也许您会为下一个大型加密货币设计 FPGA 或 ASIC 并致富。