From 4ee5de433f9062ee58470ed4c5144f7072ed23a1 Mon Sep 17 00:00:00 2001 From: windowsair Date: Tue, 22 Feb 2022 21:18:47 +0800 Subject: [PATCH] fix code block style --- README.md | 744 +++++++++++++++++++++++++++--------------------------- 1 file changed, 372 insertions(+), 372 deletions(-) diff --git a/README.md b/README.md index d24c4f1..9ea8380 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ end 下面展示 SPI 控制器的 BSV 实现。8~24 行描述的是一个类似于 Verilog testbench 的流程,详见代码注释。 -```verilog +```bsv module mkSPIWriter (SPIWriter); // BSV SPI 发送(可综合!!), 模块名称为 mkSPIWriter Reg#(bit) ss <- mkReg(1'b1); Reg#(bit) sck <- mkReg(1'b1); @@ -501,7 +501,7 @@ BSV 的代码文件名后缀为 .bsv ,尽管用记事本都能编写,但没 BSV 项目是由**包** (package) 和**模块** (module) 来组织的。我们首先看看单包、单模块项目。打开 `src/1.Hello/Hello.bsv` 可以看到如下代码,它打印 `Hello World!` 后直接退出: -```verilog +```bsv // 代码路径:src/1.Hello/Hello.bsv package Hello; // 包名: Hello。每个.bsv文件内只能有1个与文件名相同的包 @@ -585,7 +585,7 @@ $ bsvbuild.sh -bs Hello.bsv 我们再看看如何组织单包、多模块项目。打开 `src/2.DecCounter/DecCounter.bsv` 。它的结构如下: -```verilog +```bsv // 代码路径:src/2.DecCounter/DecCounter.bsv (部分) interface DecCounter; // 接口名 DecCounter,用于连接调用者和被调用者 method UInt#(4) count; // 方法1:可被被调用者调用 @@ -601,7 +601,7 @@ endmodule module mkTb (); // 模块名 mkTb ,调用者 DecCounter counter <- mkDecCounter; // 例化一个 mkDecCounter,并拿到它的接口,叫做 counter - + // counter.count ... // 通过接口名 counter 来调用子模块,比如调用 count 方法 endmodule ``` @@ -639,7 +639,7 @@ $ bsvbuild.sh -vs mkTb DecCounter.bsv 我们再看看如何组织多包、多模块项目。打开目录 `src/3.SPIWriter/` ,目录下有两个 `.bsv` 文件,每个文件内都有一个包 (package),其中 `SPIWriter.bsv` 就包含 2.1 节中所述的 SPI 发送控制器,而 `TbSPIWriter.bsv` 中的 `mkTb` 调用了 `mkSPIWriter` 进行仿真。与单包多模块项目不同的是,调用者 `mkTb` 与被调用者 `mkSPIWriter` 不在同一个包中,因此 `TbSPIWriter.bsv` 中用如下语句引入了被调用包: -```verilog +```bsv import SPIWriter::*; // 引入用户编写的包 SPIWriter (对应文件SPIWriter.bsv) ``` @@ -708,13 +708,13 @@ $ gtkwave mkTb_vw.vcd # 只有有 GUI 的 Linux 实体机/虚拟机 能运 BSV 对大小写有严格的要求,类型名和常量的首字母总是大写,比如 `UInt`, `Bit`, `Bool`, `True`, `False` 。而变量名的首字母总是小写。例如对于以下的布尔变量(布尔变量 `Bool`,要么取`True`,要么取`False`),类型名 `Bool` 首字母大写,变量名 `oflow` 首字母小写: -```verilog +```bsv Bool oflow = cnt >= 9; ``` 甚至对于接口变量也这样,比如以下代码实例化了模块 `mkSPIWriter` 并获得其接口,接口类型名 `SPIWriter` 首字母大写,接口变量名 `spi_writer` 首字母小写: -```verilog +```bsv SPIWriter spi_writer <- mkSPIWriter; ``` @@ -745,7 +745,7 @@ BSV 中的类型必须派生 (deriving) 自零个、一个或多个**类型类** 例如,以下代码自定义了一个**结构体** (stuct) 类型,用来表示以太帧报头,类型名为 `EthHeader`,它派生自 `Bits` 和 `Eq` 类型类。 -```verilog +```bsv typedef struct { UInt#(48) dst_mac; // 成员变量1:目的地址 UInt#(48) src_mac; // 成员变量2:源地址 @@ -755,21 +755,21 @@ typedef struct { 对于 `EthHeader` 类型的两个变量: -```verilog +```bsv EthHeader hdr1 = EthHeader{dst_mac: 'h0123456789AB, src_mac: 'h456789ABCDEF, pkt_type: 'h0800}; EthHeader hdr2 = EthHeader{dst_mac: 'h0123456789AB, src_mac: 'h456789ABCDEF, pkt_type: 'h0860}; ``` 因为派生自 `Eq` 类型类,可以用 `==` 判断它们是否相等: -```verilog +```bsv hdr1 == hdr2 // 若相等,该语句返回 True,否则返回 False // 只有当3个成员变量都相等时,才返回 True ``` 又因为派生自 `Bits` 类型类,可以用 `pack()` 函数来把它转换为 `Bit#(112)` 类型的变量,也即把三个成员变量拼接成一个 112 位的向量: -```verilog +```bsv Bit#(112) bits = pack(hdr1); //结构体的成员变量共占 48+48+16=112 位 ``` @@ -798,13 +798,13 @@ BSV 中常用的类型转换函数如**表3**。注意 :如果代码中包含 `Bit#(n)` 是 n 位向量,下面语句定义了一个 8 位向量,并给他赋值为 `'h12` (即16进制的`0x12`): -```verilog +```bsv Bit#(8) a = 'h12; //和 Verilog 中的 wire [7:0] a = 8'h12 效果类似 ``` 因为 1 位向量很常用,所以 BSV 还规定 `bit` 是 `Bit#(1)` 的别名: -```verilog +```bsv bit a = 'b1; // 等效于 Bit#(1) a = 'b1 ``` @@ -821,7 +821,7 @@ wire [ 3:0] t5 = t1[3:0]; // 显式截断,需要手动给出位宽,有点 BSV 严格进行位宽检查,只支持显式的高位截断和扩展,但写起来很优雅: -```verilog +```bsv Bit#(7) t1 = 'h12; //Bit#(12) t2 = t1; // 隐式零扩展,错误!!! Bit#(12) t3 = extend(t1); // 显式零扩展,自动根据左值(12位)判断出来要补 12-7=5位 @@ -831,9 +831,9 @@ Bit#(4) t5 = truncate(t1); // 显式截断,自动根据左值(4位)判 > :pushpin: 后续我们会学到 Bit#(n) 是一个多态类型(泛型),而 Bit#(7) 和 Bit#(4) 完全不是一种数据类型,这也解释了为什么 BSV 必须进行显式截断和扩展。 -用常数对 `Bit#(n)` 类型的变量进行赋值时,和 Verilog 类似,可以用二进制、十进制或十六进制表示常数,举例如下 :point_down: +用常数对 `Bit#(n)` 类型的变量进行赋值时,和 Verilog 类似,可以用二进制、十进制或十六进制表示常数,举例如下 :point_down: -```verilog +```bsv Bit#(7) t1 = 7'b0010111; // 二进制,位宽匹配 Bit#(7) t2 = 'b0010111; // 二进制,位宽自动匹配 Bit#(7) t3 = 'b10111; // 二进制,位宽自动匹配(高位补零) @@ -855,14 +855,14 @@ Bit#(80) t12= 12; // 十进制,位宽自动匹配 BSV 中还有一种 Verilog 没有的常数定义方式: `'0` 和 `'1` 。`'0` 代表所有位都为 0 ;`'1` 代表所有位都为 1 ,它们也会自动匹配左值的位宽。比如: -```verilog +```bsv Bit#(45) t1 = '1; // t1 的所有位置 1 Bit#(89) t2 = '0; // t2 的所有位置 0 ``` 与 Verilog 类似,`Bit#(n)` 类型支持位下标选择、位拼接、逐位合并运算、按位逻辑运算、算术运算(等效于无符号数算术运算)、大小比较(等效于无符号数比较)。记住:在进行这些运算时, BSV 依然会进行严格的位宽检查。 -```verilog +```bsv Bit#(8) t1 = 'hFF; Bit#(16) t2 = 'h10; Bit#(16) t3 = 'h3456; @@ -896,7 +896,7 @@ Bool t18= t2 > extend(t1); // t1 先拓展为 16'h00FF,再比较大小, 与 `Bit#(n)` 不同点在于: `UInt#(n)` 不能进行位下标选择和位拼接: -```verilog +```bsv UInt#(4) t1 = 13; //UInt#(2) t2 = t1[2:1]; // 错误!! //UInt#(8) t3 = {t1, t1}; // 错误!! @@ -904,7 +904,7 @@ UInt#(4) t1 = 13; 除此之外,`UInt#(n)` 的特性与 `Bit#(n)` 相同,包括能进行常数赋值、 `extend()`(零扩展,等效于 `zeroExtend()` )、`truncate()` 、逐位合并运算、按位逻辑运算、算术运算、大小比较。但是注意:`UInt#(n)` 与 `Bit#(n)` 在运算中不能混用,如果要混用,就要用 `pack()` 和 `unpack()` 函数转换,比如: -```verilog +```bsv UInt#(16) t1 = 12; Bit#(16) t2 = 34; //UInt#(16) t3 = t1 + t2; // 错误!!因为 t1, t2 类型不同,不能混用 @@ -914,7 +914,7 @@ Bit#(16) t5 = pack(t1) + t2; // 正确 再比如: -```verilog +```bsv UInt#(16) t1 = 12; //bit t2 = &t1; // 错误的逐位合并,应该用 UInt#(1) 承接结果 UInt#(1) t3 = &t1; // 正确的逐位合并 @@ -932,14 +932,14 @@ UInt#(1) t3 = &t1; // 正确的逐位合并 特别注意: `Int#(n)` 之间比较大小用的是有符号比较,在 `extend()` 时也进行符号扩展,即把原来的最高位扩展到高位。看下面的例子: -```verilog +```bsv module mkTb(); rule extend_example; UInt#(8) u1 = 'hFA; // 相当于十进制的 250 Int#(8) i1 = unpack(pack(u1)); //把u1转换成有符号数i1,会得到十进制的 -5 $display("%d", u1 > 2); //无符号比较,打印 1,显然 250 > 2 成立 $display("%d", i1 > 2); //有符号比较,打印 0,显然 -5 > 2 不成立 - + UInt#(16) u2 = extend(u1); //u1零扩展 Int#(16) i2 = extend(i1); //i1符号扩展 $display("u2=%x", u2); //打印 u2=00fa, 因为 fa 零扩展得到 00fa @@ -955,9 +955,9 @@ endmodule `Bool` 类型只有两种取值:`True` 和 `False` 。 -虽然 `Bool` 底层是用 1 比特实现的,但 `Bool` 类型与 `bit` 类型不能混淆,它们之间可以用 `pack` 和 `unpack` 互相转化。 +虽然 `Bool` 底层是用 1 比特实现的,但 `Bool` 类型与 `bit` 类型不能混淆,它们之间可以用 `pack` 和 `unpack` 互相转化。 -```verilog +```bsv Bool b1 = True; bit b2 = pack(b1); // 得到 b2 = 1'b1; Bool b3 = unpack(b2); // 得到 b3 = True @@ -965,7 +965,7 @@ Bool b3 = unpack(b2); // 得到 b3 = True `Bool` 可以进行非(`!`)、且(`&&`)、或(`||`)运算。注意要区别于 `Bit#(n)` 的按位非(`~`)、按位与(`&`)、按位或(`|`): -```verilog +```bsv Bool b1 = True; // Bool : Bool b2 = !b1; // 非 Bool b3 = b1 && b2; // 且 @@ -986,7 +986,7 @@ bit b6 = b4 & b5; // 按位与 `Integer` 类型派生自 `Arith` 类型类,是数学上的整数,是无界的,对他进行算术运算永远不会溢出,不像 `UInt#(n)` 和 `Int#(n)` 是有界的。`Integer` 可以用于仿真,也可在可综合电路中作为循环下标,比如: -```verilog +```bsv int arr[16]; // 数组 for (int i=0; i<16; i=i+1) // 正确 arr[i] = i; @@ -996,7 +996,7 @@ for (Integer i=0; i<16; i=i+1) // 也正确 但 `Integer` 不能作为寄存器、FIFO或存储器中的取值: -```verilog +```bsv Reg#(int) <- mkReg(0); // 寄存器里存放 int 类型,正确 //Reg#(Integer) <- mkReg(0); // 寄存器里存放 Integer 类型,错误!! ``` @@ -1005,7 +1005,7 @@ Reg#(int) <- mkReg(0); // 寄存器里存放 int 类型,正确 `String` 类型表示一个字符串,一般用作仿真打印、指定仿真文件名等作用。 `String` 具有不定的长度,可以使用 `+` 拼接,比如: -```verilog +```bsv rule test; String s = "Hello"; String t = "BSV Strings"; @@ -1035,13 +1035,13 @@ endrule 调用格式是: -```verilog +```bsv $display(格式字符串, 变量1, 变量2, 变量3, ...); ``` 比如: -```verilog +```bsv $display("a=%d b=%3d c=%08x d=%x e=%b", a, b, c, d, e ); ``` @@ -1067,7 +1067,7 @@ $display("a=%d b=%3d c=%08x d=%x e=%b", a, b, c, d, e ); ### 变量定义 -变量定义的格式是: +变量定义的格式是: ``` 类型名 变量名; @@ -1075,19 +1075,19 @@ $display("a=%d b=%3d c=%08x d=%x e=%b", a, b, c, d, e ); 比如: -```verilog +```bsv UInt#(6) value; ``` 可以在变量定义时为它赋值,称作“初始化”,比如: -```verilog +```bsv UInt#(6) value = 42; ``` 之后也可以继续赋值,比如: -```verilog +```bsv UInt#(6) value = 42; value = 53; ``` @@ -1108,14 +1108,14 @@ BSV 有两种赋值符号: 下面是一个区分 `=` 和 `<-` 的例子: -```verilog +```bsv module mkTb(); Reg#(int) ra <- mkReg(0); // 1. 定义一个 Reg#(int) 类型的接口变量 ra 。 // mkReg(0) 实例化一个初值=0的寄存器,ra 拿到了该寄存器的接口 - + Reg#(int) rb = ra; // 2. 定义一个 Reg#(int) 类型的接口变量 rb 。 // 并没有实例化寄存器,而是让 rb 成为 ra 的别名 - + Reg#(int) rc <- mkReg(0); // 3. 定义一个 Reg#(int) 类型的接口变量 rc 。 // mkReg(0) 实例化一个初值=0的寄存器,rc 拿到了该寄存器的接口 @@ -1151,7 +1151,7 @@ ra = 5, rb = 5, rc = 10 在定义变量并赋初始值时,如果右值的类型可以被编译器推断出来,则左值的类型名可以省略,用 `let` 关键字代替。举例: -```verilog +```bsv UInt#(8) a = 1; UInt#(8) b = 2; UInt#(8) c = a + b; //定义变量 c,不省略类型声明 @@ -1163,7 +1163,7 @@ let d = a + b; //定义变量 d, 注意: `let` 只用在变量定义并赋初始值时。对已经定义过的变量,直接赋值即可,不要用 `let` ,举例: -```verilog +```bsv UInt#(8) a = 1; UInt#(8) b = 2; // let b = a + b; // 错误!! 不要用 let ,因为 b 已经定义过了 @@ -1172,7 +1172,7 @@ b = a + b; // 正确,直接赋值即可 注意:不要在无法推断右值的类型时使用 `let` 。比如以下例子中,可以用 `let sub <- mkSub;` 语句来定义一个 `Sub` 类型的接口,因为用户自定义的接口 `Sub` 不是多态接口类型,而是一个确定的接口类型,且代码明确指出 `mkSub` 的接口是 `Sub`, 编译器能推断出 `mkSub` 一定会获得 `Sub` 类型的接口。但是,不要用 `let ra <- mkReg(0);` 语句来定义一个寄存器接口,因为 `Reg#()` 是一个多态类型接口,仅靠右值 `mkReg(0)` 无法推断出左值应该是 `Reg#(int)` 还是 `Reg#(bit)` 抑或是 `Reg#(UInt#(8))` 之类的类型。 -```verilog +```bsv interface Sub; // 自定义的接口,不是多态类型,是确定的数据类型 method int read; endinterface @@ -1185,7 +1185,7 @@ module mkTb (); let sub <- mkSub; // 可以用 let // mkSub 一定会获得 Sub 类的接口,无歧义 // 等效于 Sub sub <- mkSub; - + // let ra <- mkReg(0); // 不能用 let !!! // 不知道 mkReg 会获得 Reg#(int) 还是 Reg#(bit) 还是 Reg#(UInt#(8)) 之类的接口 // 这里要用完整类型名,比如 Reg#(bit) <- mkReg(0); @@ -1241,7 +1241,7 @@ end 首先打开`GrayCode_v1.bsv` ,如下,它把组合逻辑表达式写在**规则**(rule)内,这样,获得的结果(`cnt_gray` 和 `cnt_bin` 变量)的作用域就仅限于该规则,不像上述 Verilog 代码那样,`reg [5:0] bin` 的作用域是整个模块。限制变量作用域有利于提高可读性! -```verilog +```bsv // 代码路径:src/4.GrayCode/GrayCode_v1.bsv (部分) module mkTb (); @@ -1287,7 +1287,7 @@ cnt=000111 cnt_gray=000100 cnt_bin=000111 打开 `GrayCode_v2.bsv` ,看到它与写法1的不同是:把格雷码转二进制码中的重复性高的五行写成了 `for` 循环。这个 `for` 循环可综合,且不代表任何时序行为,编译器会把它**全展开**(unroll)成纯组合逻辑电路。这样的好处是提高了可读性。(实际上 Verilog 的 `always` 块里也能写 `for` 循环): -```verilog +```bsv // 代码路径:src/4.GrayCode/GrayCode_v2.bsv (部分) Bit#(6) cnt_bin = cnt_gray; for(int i=4; i>=0; i=i-1) @@ -1304,7 +1304,7 @@ for(int i=4; i>=0; i=i-1) 打开 `GrayCode_v3.bsv` ,看到它与写法2的不同是:把相关的变量定义和计算都移动到了规则外,但仍然在模块内,形如: -```verilog +```bsv // 代码路径:src/4.GrayCode/GrayCode_v3.bsv (部分) module mkTb (); @@ -1328,7 +1328,7 @@ module mkTb (); 打开 `GrayCode_v4.bsv` ,看到它把二进制码转格雷码、格雷码转二进制码写成了**函数** (function) : -```verilog +```bsv // 代码路径:src/4.GrayCode/GrayCode_v4.bsv (部分) module mkTb (); @@ -1336,20 +1336,20 @@ module mkTb (); function Bit#(6) binary2gray(Bit#(6) value); // 输入参数:Bit#(6) ,返回 Bit#(6) return (value >> 1) ^ value; endfunction - + // 函数:把格雷码转化为二进制编码 function Bit#(6) gray2binary(Bit#(6) value); // 输入参数:Bit#(6) ,返回 Bit#(6) for(int i=4; i>=0; i=i-1) value[i] = value[i] ^ value[i+1]; return value; endfunction - + //... ``` 本模块内可以调用这两个函数,每处调用都会实例化一个组合逻辑电路: -```verilog +```bsv // 代码路径:src/4.GrayCode/GrayCode_v4.bsv (部分) rule convert; Bit#(6) cnt_gray = binary2gray(cnt); // 调用函数 binary2gray @@ -1364,7 +1364,7 @@ module mkTb (); 打开 `GrayCode_v5.bsv` ,与写法4唯一的不同是,它把两个函数定义在了模块外。这样,它们就不再属于某个模块,而是属于整个包,能被包内的所有模块调用。另外,其它包(文件)也可以用引入语句: -```verilog +```bsv import GrayCode_v5::*; ``` @@ -1376,13 +1376,13 @@ import GrayCode_v5::*; ## 5.7 元组 Tuple -元组相关的代码见 `src/5.TupleTest/TupleTest.bsv` +元组相关的代码见 `src/5.TupleTest/TupleTest.bsv` 元组是把多个类型的变量放在一起的复合数据类型, BSV 预定义了二元组、三元组、四元组、……、八元组。 以下语句定义了一个二元组并赋值: -```verilog +```bsv Tuple2#(Bool, Int#(9)) t2 = tuple2(True, -25); ``` @@ -1390,14 +1390,14 @@ Tuple2#(Bool, Int#(9)) t2 = tuple2(True, -25); 同理,以下语句定义一个八元组并赋值。 -```verilog +```bsv Tuple8#(int, Bool, Bool, int, UInt#(3), int, bit, Int#(6)) t8 = tuple8(-3, False, False, 19, 1, 7, 'b1, 45); ``` 用函数 `tpl_1()` 、 `tpl_2()` 、 `tpl_3()` 、... 可以获得元组的第 1, 2, 3, … 个元素: -```verilog +```bsv // 代码路径:src/5.TupleTest/TupleTest.bsv (部分) Bool v1 = tpl_1(t2); // 获取 t2 的第一个元素,得到 True int v2 = tpl_2(t2); // 获取 t2 的第二个元素,得到 -25 @@ -1407,7 +1407,7 @@ Bool v3 = tpl_3(t8); // 获取 t8 的第三个元素,得到 False 元组的方便之处在于,可以用 `match` 语句来承接元组中的元素,比如如下例子:(它会自动定义两个变量 v1 和 v2)。 -```verilog +```bsv match {.va, .vb} = t2; // 隐式定义了2个变量来承接 t2 的值 // va 是 Bool 类型的 True // vb 是 Int#(9) 类型的 -25 @@ -1415,7 +1415,7 @@ match {.va, .vb} = t2; // 隐式定义了2个变量来承接 t2 的值 下面看看元组的用途,如果函数 (function) 和方法 (method) 想要达到“多个返回值”的效果,就可以用元组。我们用 BSV 的预定义函数 `split()` 举例,它可以把一个 `Bit#()` 类型拆分成两个 `Bit#()` 。它的函数原型如下: -```verilog +```bsv function Tuple2#(Bit#(m), Bit#(n)) split (Bit#(mn) xy) // split函数返回二元组,两个元素是拆分后的 Bit#() provisos (Add#(m,n,mn)); // provisos 关键字是函数声明中的补充要求, // 这里要求 m + n == mn @@ -1424,7 +1424,7 @@ function Tuple2#(Bit#(m), Bit#(n)) split (Bit#(mn) xy) // split函数返回二 比如我们要把一个 `Bit#(13)` 变量拆成 `Bit#(8)` (高位)和一个 `Bit#(5)` (低位),可以用: -```verilog +```bsv Bit#(13) b13 = 'b1011100101100; Tuple2#(Bit#(8), Bit#(5)) tsplit = split(b13); match {.b8, .b5} = tsplit; // 得到 b8='b10111001 b5=01100 @@ -1438,7 +1438,7 @@ match {.b8, .b5} = tsplit; // 得到 b8='b10111001 b5=0 以下代码中,我们定义两个 Maybe 类型的变量,它们中的数据类型都是 `Int#(9)` ,一个无效,一个有效: -```verilog +```bsv Maybe#(Int#(9)) value1 = tagged Invalid; // 无效 Maybe#(Int#(9)) value2 = tagged Valid 42; // 有效,取值为 42 ``` @@ -1446,17 +1446,17 @@ Maybe#(Int#(9)) value2 = tagged Valid 42; // 有效,取值为 42 BSV 针对 `Maybe#(td)` 类型提供了两个函数: - `isValid(x)` : 接受 `Maybe#(td)` 类型的变量 `x` 作为参数: -- `x` 无效则返回 False +- `x` 无效则返回 False - `x` 有效则返回 True - `fromMaybe(dv, x)` : 接受 `td` 类型的变量 `dv` 和 `Maybe#(td)` 类型的变量 `x` 作为参数: -- `x` 无效则返回 `dv` +- `x` 无效则返回 `dv` - `x` 有效则返回 `x` 中的取值。 使用例: -```verilog +```bsv let v1 = isValid(value1); // 得到 v1 是 Bool 类型的 False let d1 = fromMaybe(-99, value1); // 得到 d1 是 Int#(9) 类型的 -99 let v2 = isValid(value2); // 得到 v2 是 Bool 类型的 True @@ -1475,7 +1475,7 @@ let d2 = fromMaybe(-99, value2); // 得到 d2 是 Int#(9) 类型的 42 寄存器是一类用于保存数据(或者叫保存电路状态)的模块。本节涉及: -- 接口`Reg#()` 以及其配套的模块 `mkReg` 、 `mkRegU` 、 `mkDReg` +- 接口`Reg#()` 以及其配套的模块 `mkReg` 、 `mkRegU` 、 `mkDReg` ### mkReg 和 mkRegU @@ -1483,7 +1483,7 @@ let d2 = fromMaybe(-99, value2); // 得到 d2 是 Int#(9) 类型的 42 以下例子定义并实例化了两个可以存储 `int` 值的寄存器 `x` 和 `y` : -```verilog +```bsv Reg#(int) x <- mkReg(23); //初值=23 Reg#(int) y <= mkRegU; //初值未知 ``` @@ -1492,7 +1492,7 @@ Reg#(int) y <= mkRegU; //初值未知 `Reg#(int)` 是一个**接口** (interface) 的类型名,`int` 可以换成其它任何类型,因此 `Reg#()` 是个多态接口,其定义为: -```verilog +```bsv interface Reg#(type td); // 寄存器中存储的数据的类型名为 td ,可能是任何类型 method Action _write (td x); // 该方法用于把 td 类型的变量 x 写入寄存器 method td _read; // 该方法用于读出寄存器的值,得到 td 类型的返回值 @@ -1501,7 +1501,7 @@ endinterface 而 `mkReg` 的模块定义为: -```verilog +```bsv module mkReg#(td v) (Reg#(td)) // 第一个括号里是模块参数,是一个类型为 td 的变量 v ,这里是作为寄存器初始值。 // 第二个括号里,表示 mkReg 具有 Reg#(td) 类型的接口 provisos (Bits#(td, sz)); // 要求 td 派生自 Bits 类型类,即寄存器的值必须有特定的位宽(保证寄存器可综合) @@ -1509,15 +1509,15 @@ module mkReg#(td v) (Reg#(td)) // 第一个括号里是模块参数,是一个 以上 `interface Reg#(type td)` 的定义中有两个方法: `_write` 和 `_read`,其中 `_write` 方法用于写入寄存器;`_read` 方法用于读寄存器 。比如对于我们熟悉的计数器,完整的写法是: -```verilog +```bsv module mkTb (); Reg#(int) x <- mkReg(23); - + rule up_counter; // rule 每时钟周期都会执行一次 x._write( x._read + 1 ); // 寄存器的x的值先读出来,+1后再写回去 $display ("x=%d", x._read ); endrule - + rule done (x >= 26); // 只有满足条件 x >= 26 的时钟周期才会执行退出 $finish; endrule @@ -1526,7 +1526,7 @@ module mkReg#(td v) (Reg#(td)) // 第一个括号里是模块参数,是一个 因为寄存器的读写非常常用,所以 BSV 规定可以用寄存器名本身代替 `_read` ,用写入符号 `<=` 代替 `_write` 。 -```verilog +```bsv x <= x + 1; // 简化写法,等效于 x._write( x._read + 1 ); $display ("x=%d", x ); // 简化写法,等效于 $display ("x=%d", x._read ); ``` @@ -1547,13 +1547,13 @@ BSV 还提供了一种实用的寄存器模块 `mkDReg` ,它和 `mkReg` / `mkR 使用 `mkDReg` 前,需要先引入包: -```verilog +```bsv import DReg::*; ``` `mkDReg` 举例如下: -```verilog +```bsv // 代码路径:src/6.RegTest/RegTest.bsv (部分) module mkTb (); Reg#(int) cnt <- mkReg(0); @@ -1562,7 +1562,7 @@ module mkTb (); cnt <= cnt + 1; if(cnt > 9) $finish; endrule - + Reg#(int) reg1 <- mkReg(99); // reg1 初值 = 99 Reg#(int) reg2 <- mkDReg(99); // reg2 默认值 = 99 @@ -1606,7 +1606,7 @@ cnt=10 reg1=-9 reg2=-9 #实现的效果: y = sqrt(x) x = 114514 # 输入数据 y = 0 # 待计算的开方结果 -for n in range(15, -1, -1): # 迭代 16 次,迭代变量 n=15,14,13,...,2,1,0 +for n in range(15, -1, -1): # 迭代 16 次,迭代变量 n=15,14,13,...,2,1,0 t = (y<<1<= t: # 迭代体 x -= t # 迭代体 @@ -1626,7 +1626,7 @@ print(y) # 打印开方结果 √114514 首先,我们根据以上伪代码,编写单次迭代的函数 `sqrtIteration` 如下。注意 x 和 y 被打包成二元组 `Tuple2#(UInt#(32), UInt#(32))` ,这是为了后续编写的方便。 -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v1.bsv (部分) function Tuple2#(UInt#(32), UInt#(32)) sqrtIteration( Tuple2#(UInt#(32), UInt#(32)) data, int n ); match {.x, .y} = data; @@ -1641,26 +1641,26 @@ endfunction 然后创建 17 个 `mkDReg` 来构成流水线各级之间的寄存器。考虑到每个寄存器都应持有中间结果 x 和 y,因此寄存器的内容数据类型是二元组 `Tuple2#(UInt#(32), UInt#(32))` ,所以寄存器的接口类型名是: -```verilog +```bsv Reg#( Tuple2#(UInt#(32), UInt#(32)) ) ``` 我们用长度为 17 的数组定义 17 个该类型的接口: -```verilog +```bsv Reg#( Tuple2#(UInt#(32), UInt#(32)) ) dregs [17]; // 这是一个寄存器接口数组! ``` 并在 for 循环中用 `mkDReg` 分别实例化它们。注意: `mkDReg` 的默认值设为 `tuple2(0, 0)` ,即 x=0, y=0 ,作为没有数据输入(流水级空闲)的情况。 -```verilog +```bsv for(int n=16; n>=0; n=n-1) dregs[n] <- mkDReg( tuple2(0, 0) ); ``` 然后我们编写计算行为。每级流水线的行为都是调用迭代函数进行迭代计算,是高度重复的,所以用 for 循环批量生成 16 个规则 (rule) ,每个规则都从上一级段寄存器中取出数据,经过 `sqrtIteration` 函数完成迭代计算,然后写入下一级寄存器: -```verilog +```bsv for(int n=15; n>=0; n=n-1) // 该 for 循环用来批量部署 rule rule pipe_stages; dregs[n] <= sqrtIteration( dregs[n+1] , n ); @@ -1669,7 +1669,7 @@ Reg#( Tuple2#(UInt#(32), UInt#(32)) ) dregs [17]; // 这是一个寄存器接 最后编写测试代码,每周期向最前级寄存器 `dregs[16]` 写入想要开方的数据,从最末级寄存器 `dreg[0]` 拿出开方结果: -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v1.bsv (部分) Reg#(UInt#(32)) cnt <- mkReg(1); rule sqrter_input; @@ -1724,7 +1724,7 @@ BSV 的每一个硬件模块的方法之间都有调度注解,用来指示该 为了理解寄存器的 _write **SBR** _write ,试试编译如下代码: -```verilog +```bsv module mkTb (); Reg#(int) cnt <- mkReg(0); rule up_counter; @@ -1758,7 +1758,7 @@ Error: "test.bsv", line 15, column 9: (G0004) 但我们可以写成如下这样,因为两个分支只能执行一个,所以不报任何 Warning 。 -```verilog +```bsv rule test1; if(cnt%2 == 0) // 判断cnt能否整除2 x <= cnt + 1; @@ -1769,7 +1769,7 @@ Error: "test.bsv", line 15, column 9: (G0004) 我们还可以把两条 `x._write` 放在不同的规则里: -```verilog +```bsv Reg#(int) x <- mkReg(0); rule test1; @@ -1832,7 +1832,7 @@ Wire 包括以下几种接口和模块: `Wire#(type td)` 的接口定义如下,具有一个写方法 `_write` 和一个读方法 `_read` 。 -```verilog +```bsv interface Wire#(type td); // Wire中的数据的类型名为 td ,可能是任何类型 method Action _write (td x); // 该方法用于把 td 类型的变量 x 写入 method td _read; // 该方法用于读出,得到 td 类型的返回值 @@ -1843,7 +1843,7 @@ endinterface 以下语句实例化一个 `mkDWire` ,其中的数据类型是 `int`,指定它的默认值是 `42` : -``` +```bsv Wire#(int) <- mkDWire(42); ``` @@ -1851,7 +1851,7 @@ Wire#(int) <- mkDWire(42); 以下展示一个例子,比较了 `mkDWire` 和 `mkReg` ,在 `cnt` 整除 2 的周期写入 `mkDWire` 和 `mkReg` 。 -```verilog +```bsv // 代码路径:src/7.WireTest/TestDWire.bsv (部分) module mkTb (); Reg#(int) cnt <- mkReg(0); @@ -1862,7 +1862,7 @@ module mkTb (); Wire#(int) w1 <- mkDWire(99); // w1 默认值 = 99 Reg#(int) r1 <- mkReg(99); // r1 初始值 = 99 - + rule test1 (cnt%2 == 0); // rule条件:只在能整除2的周期激活 w1 <= cnt; endrule @@ -1916,7 +1916,7 @@ cnt= 4 w1= 4 r1= 2 举例如下: -```verilog +```bsv // 代码路径:src/7.WireTest/TestWire.bsv (部分) module mkTb (); Reg#(int) cnt <- mkReg(1); @@ -1927,7 +1927,7 @@ module mkTb (); Wire#(int) w1 <- mkWire; Wire#(int) w2 <- mkWire; - + rule test1 (cnt%2 == 0); // rule条件:只在能整除2的周期激活 $display("cnt=%1d test1", cnt); w1 <= cnt; @@ -1966,7 +1966,7 @@ cnt=8 test1 `mkRWire` 是功能最全的 `Wire` (但是用起来略显复杂),具有以下接口定义: -```verilog +```bsv interface RWire#(type td); // RWire中的数据的类型名为 td method Action wset(td x); // 该方法用于把 td 类型的变量 x 写入 method Maybe#(td) wget(); // 该方法用于读,得到 Maybe#(td) 类型,含义是把 td 类型的数据加上一个有效位 @@ -1975,19 +1975,19 @@ endinterface: RWire 把变量 `int x` 写入一个 `mkRWire` `w1` 时,使用如下语句: -```verilog +```bsv w1.wset(x); ``` 读取一个 `mkRWire` ,则使用如下语句得到一个 Maybe 类型 的变量。 -```verilog +```bsv Maybe#(int) mbi = w1.wget; // 用一个 Maybe#(td) 类型接受 w1 的读取结果 ``` 5.8 节学过,可以用 `isValid` 函数获取 `mbi` 是否有效,用 `fromMaybe` 函数获取其中的数据。 -```verilog +```bsv Bool valid = isValid(mbi); // 如果有效,得到 valid = True, 否则 valid = False int data = fromMaybe(<无效值>, mbi); // 如果有效,得到 data = 写入值,否则得到 data = <无效值> // 无效值可以任意设置,比如 0 @@ -2008,7 +2008,7 @@ int data = fromMaybe(<无效值>, mbi); // 如果有效,得到 data = 写入 `mkPulseWire` 是不带数据的 `RWire`,只用来传递是否有效的信号,其接口定义如下: -```verilog +```bsv interface PulseWire; // 是确定的接口,不是多态接口 method Action send(); // 该方法用于写入,即让 PulseWire 在本周期变得“有效” method Bool _read(); // 该方法用于读取,返回值代表本周期是否有效,True 有效,False 无效 @@ -2028,7 +2028,7 @@ endinterface `mkRWire` 和 `mkPulseWire` 举例如下: -```verilog +```bsv // 代码路径:src/7.WireTest/TestRWire.bsv module mkTb (); Reg#(int) cnt <- mkReg(1); @@ -2039,7 +2039,7 @@ module mkTb (); RWire#(int) w1 <- mkRWire; // w1 是个 RWire PulseWire w2 <- mkPulseWire; // w2 是个 PulseWire - + rule test1 (cnt%2 == 0); // rule条件:只在能整除2的周期激活 w1.wset(cnt); endrule @@ -2074,7 +2074,7 @@ cnt=6 w1_v=1 w1_d=6 w2_v=1 规则的格式是: -```verilog +```bsv rule 规则名称 (显式条件的表达式); // 语句…… endrule @@ -2082,7 +2082,7 @@ endrule 条件表达式指定了规则的**显式条件**,规则只在显式条件=True 的时钟周期执行。对于显式条件永远=True 的规则,可以省略显式条件的表达式: -```verilog +```bsv rule 规则名称; // 语句…… endrule @@ -2096,7 +2096,7 @@ endrule 这里引入了新的概念—— 规则的**激活**,规则只有在显式和隐式条件都满足时才能激活。当激活时,规则内的所有语句都执行。注意:**激活**是针对规则的概念;而**执行**是针对语句的概念。 比如: -```verilog +```bsv rule test(cnt < 10); // 只有在 cnt < 10 时, 规则激活 x <= x + 1; // 只要 rule 激活,就执行 if(x > 4) // 应该把if语句看作一个整体,只要规则激活,if语句就执行 @@ -2149,7 +2149,7 @@ endrule 设 `x` , `y` 都是寄存器,对于如下规则,规则内的执行顺序是:先读 `y` 和 `x`(读到的是上周期的旧值),然后写 `x` 和 `y` 。这样就实现了 `x` , `y` 交换的效果。 -```verilog +```bsv // rule 内执行顺序举例 rule swap; x <= y + 1; // 读y 写x @@ -2167,7 +2167,7 @@ endrule 考察如下代码中的三个规则 `r1` 、 `r2` 和 `r3` 。 -```verilog +```bsv // 代码路径:src/8.RuleTest/Test1.bsv (部分) Reg#(int) x <- mkReg(1); @@ -2237,7 +2237,7 @@ r1 为了理解**排序冲突**,举例如下: -```verilog +```bsv // 代码路径:src/8.RuleTest/Test2.bsv (部分) Reg#(int) x <- mkReg(1); Reg#(int) y <- mkReg(2); @@ -2253,7 +2253,7 @@ r1 endrule ``` -编译器会分析以上代码: +编译器会分析以上代码: - 如果把 `x2y` 排在 `y2x` 前面,则不能满足 y.\_read **SB** y.\_write - 如果把 `y2x` 排在 `x2y` 前面,则不能满足 x.\_read **SB** x.\_write @@ -2299,13 +2299,13 @@ Warning: "Test1.bsv", line 20, column 9: (G0021) 比如,规定规则 `r1` 比规则 `r2` 紧急: -```verilog +```bsv (* descending_urgency = "r1, r2" *) ``` 再比如,规定规则 `r1` 比 `r2` 紧急、 规则 `r2` 比规则 `r3` 紧急: -```verilog +```bsv (* descending_urgency = "r1, r2, r3" *) ``` @@ -2315,7 +2315,7 @@ Warning: "Test1.bsv", line 20, column 9: (G0021) 以下是一个例子。显然,规则 `x2y` 和规则 `y2x` 间发生了**排序冲突** 。用户用 `descending_urgency` 规定 `y2x` 比 `x2y` 紧急。 -```verilog +```bsv // 代码路径:src/9.RuleUrgency/Test1.bsv module mkTb (); Reg#(int) cnt <- mkReg(0); @@ -2365,7 +2365,7 @@ cnt=6 x=3 y=2 具有**显式条件**和**隐式条件**的规则会影响冲突的发生。比如我们给规则 `y2x` 加一个显式条件: -```verilog +```bsv // 代码路径:src/9.RuleUrgency/Test2.bsv module mkTb (); Reg#(int) cnt <- mkReg(0); @@ -2378,7 +2378,7 @@ module mkTb (); Reg#(int) y <- mkReg(2); (* descending_urgency = "y2x, x2y" *) - rule x2y; + rule x2y; y <= x + 1; // 读 x,写 y endrule rule y2x (cnt<3); // 显式条件 cnt<3 @@ -2392,7 +2392,7 @@ endmodule 编译时,不再报任何 Warning ,因为 `y2x` 只在 `cnt<3` 时激活,而 `x2y` 会在 `y2x` 不激活时激活。 -仿真打印如下 ,显然,在 `cnt<3` 时,执行的是 `x<=y+1;` ,在 `cnt>=3` 时,执行的是 `y<=x+1;` +仿真打印如下 ,显然,在 `cnt<3` 时,执行的是 `x<=y+1;` ,在 `cnt>=3` 时,执行的是 `y<=x+1;` ``` cnt=0 x=1 y=2 @@ -2408,7 +2408,7 @@ cnt=6 x=3 y=4 6.3 节学过, `mkWire` 的 `_read` 方法具有隐式条件——只在本周期调用了 `_write` 的情况下满足。我们给规则 `y2x` 构造一个隐式条件: -```verilog +```bsv // 代码路径:src/9.RuleUrgency/Test3.bsv (部分) module mkTb (); Reg#(int) cnt <- mkReg(0); @@ -2425,7 +2425,7 @@ module mkTb (); (* descending_urgency = "y2x, x2y" *) - rule x2y; + rule x2y; y <= x + 1; // 读 x,写 y endrule rule y2x; @@ -2464,7 +2464,7 @@ cnt=6 x=4 y=5 - 激活:因为虽然 `w1._read` 无法执行,但它在 if 条件里,if 条件不成立,所以 `test` 仍然可以激活,没影响。这种观点是错误的!编译器并不会如此判断。(错误) - 不激活:编译器只看隐式条件 `w1._read` 无法执行,就给出了 `test` 不激活的结论,不管 if 是否成立。(正确) -```verilog +```bsv rule test; if(cnt < 3) // 假设 cnt < 3 不成立 $display("%d", w1) // 假设 w1 是 mkWire, 且 _read 的隐式条件不成立 @@ -2473,14 +2473,14 @@ cnt=6 x=4 y=5 同理:编译器会保守地认为 if 下的语句在任何时候都会引发冲突。比如以下代码: -```verilog +```bsv // 代码路径:src/9.RuleUrgency/Test4.bsv (部分) (* descending_urgency = "y2x, x2y" *) - rule x2y; + rule x2y; y <= x + 1; // 读 x,写 y endrule - + rule y2x; if(cnt < 3) // if 语句 cnt<3 x <= y + 1; // 读 y,写 x @@ -2504,7 +2504,7 @@ Warning: "Test5.bsv", line 16, column 9: (G0021) 我们先看看编译器能分析出冲突不会发生的情况。以下代码中,假设规则 `test1` 和 `test2` 没有显式条件,则二者发生**排序冲突**。但有如下显式条件时,编译器不认为它们有冲突 ,因为它能分析出 `cnt<3` 和 `cnt>5` 是互斥的,因此编译时不报 Warning ,仿真结果也符合我们预料。 -```verilog +```bsv rule test1 (cnt < 3); x <= x + 1; endrule @@ -2516,11 +2516,11 @@ Warning: "Test5.bsv", line 16, column 9: (G0021) 如果我们改一改两个条件,让编译器分析不出来冲突不会发生: -```verilog +```bsv // 代码路径:src/10.RuleNoConflict/MutuallyExclusive.bsv (部分) Reg#(Bit#(32)) cnt <- mkReg(1); // cnt 初始值 = 'b0001 rule shift_counter; - cnt <= cnt << 1; // cnt 每周期左移1 ,这样 cnt 的变化规律是 'b0001 -> 'b0010 -> 'b0100 + cnt <= cnt << 1; // cnt 每周期左移1 ,这样 cnt 的变化规律是 'b0001 -> 'b0010 -> 'b0100 if(cnt > 10) $finish; endrule @@ -2550,7 +2550,7 @@ Warning: "MutuallyExclusive.bsv", line 3, column 8: (G0010) 我们可以在规则 `test1` 上方上添加以下属性,来避免报 Warning ,并省略额外的选择电路。 -```verilog +```bsv (* mutually_exclusive = "test1, test2" *) ``` @@ -2562,7 +2562,7 @@ Warning: "MutuallyExclusive.bsv", line 3, column 8: (G0010) `mutually_exclusive` 具有局限性:有时候多个规则可能同时激活,但它们中的引起冲突的语句并不会同时执行。如何理解这种情况呢?看看以下例子: -```verilog +```bsv // 代码路径:src/10.RuleNoConflict/ConflictFree.bsv (部分) Reg#(int) cnt <- mkReg(0); @@ -2600,7 +2600,7 @@ Warning: "MutuallyExclusive.bsv", line 3, column 8: (G0010) 以上代码中,规则 `test1` 和 `test2` 在每周期都激活,但因为 if 语句的条件互斥,它们中会引起**排序冲突**的语句不会同时执行。假设我们希望的结果是: -- `cnt<3` 时能执行 `x<=x+1;` , `cnt>3` 时能执行 `x<=x-1;` +- `cnt<3` 时能执行 `x<=x+1;` , `cnt>3` 时能执行 `x<=x-1;` - 所有周期都能执行 `y<=y+1;` - 所有周期都能执行 `z<=z+2;` @@ -2631,7 +2631,7 @@ x=2 y=6 z=12 然后试试 `mutually_exclusive` ,取消这一句的注释: -```verilog +```bsv // 取消 src/10.RuleNoConflict/ConflictFree.bsv 中这句话的注释: (* mutually_exclusive = "test1, test2" *) ``` @@ -2658,7 +2658,7 @@ Error: "ConflictFree.bsv", line 18, column 29: (R0001) 最后,我们把 `mutually_exclusive` 替换成 `conflict_free`: -```verilog +```bsv // 取消 src/10.RuleNoConflict/ConflictFree.bsv 中这句话的注释: (* conflict_free = "test1, test2" *) ``` @@ -2677,7 +2677,7 @@ x=2 y=6 z=12 如果我们非要试试**欺骗编译器**,构造一种会发生冲突的情况,可以修改 if 中的条件,让它们在 `cnt==3` 时不互斥: -```verilog +```bsv // 把 src/10.RuleNoConflict/ConflictFree.bsv 修改成这样: (* conflict_free = "test1, test2" *) @@ -2716,21 +2716,21 @@ x=2 y=6 z=12 `preempts` 是有顺序的,该顺序隐含了紧急程度。以下例子指定规则 `r1` 抢占 `r2`,即当 `r1` 激活时,`r2` 就不能激活;当 `r1` 不激活时,`r2` 才能激活。我们可以认为 `r1` 比 `r2` 更紧急。 -```verilog +```bsv (* preempts = "r1, r2" *) ``` 还可以写的更复杂,如下,表示 `r1` 或 `r2` 激活(或2者都激活)时, `r3` 不能激活。 -```verilog +```bsv (* preempts = "(r1, r2), r3" *) -// 等效于 (* preempts = "r1, r3" *) +// 等效于 (* preempts = "r1, r3" *) // (* preempts = "r2, r3" *) ``` 第三种写法如下,表示 `r1` 激活时,`r2` 和 `r3` 都不能激活。 -```verilog +```bsv (* preempts = "r1, (r2, r3)" *) // 等效于 (* preempts = "r1, r2" *) // (* preempts = "r1, r3" *) @@ -2738,7 +2738,7 @@ x=2 y=6 z=12 以下展示一个例子: -```verilog +```bsv // 代码路径:src/11.RulePreempts/Test1.bsv (部分) module mkTb (); Reg#(Bit#(32)) cnt <- mkReg(0); @@ -2803,14 +2803,14 @@ cnt=9 x=3 y=5 z=3 `fire_when_enabled` 只能写在一个规则的定义的前面,比如规则名称是 `test1`,那么格式是: -```verilog +```bsv (* fire_when_enabled *) rule test1; ``` 一个简单的例子如下:point_down: 。 -```verilog +```bsv (* descending_urgency = "test2, test1" *) (* fire_when_enabled *) rule test1 (cnt%2 == 0); @@ -2844,7 +2844,7 @@ Error: "test.bsv", line 16, column 9: (G0005) `no_implicit_conditions` 只能写在一个规则的定义的前面,比如规则名称是 `test1`,那么格式是: -```verilog +```bsv (* no_implicit_conditions *) rule test1; ``` @@ -2888,7 +2888,7 @@ Error: "test.bsv", line 16, column 9: (G0005) `mkCReg` 是 `mkDWire` 和 `mkReg` 的复合体。它有多个 `Reg#()` 接口,允许每周期读取多次、写入多次。以下语句 :point_down: 实例化一个内容数据类型为 `int`,具有三个接口的 `creg` : -```verilog +```bsv Reg#(int) creg [3] <- mkCReg(3, 0); // Reg#(int) creg [3] 代表接口数组,数组每项元素的类型为 Reg#(int) 。 // mkCReg 的参数中,3 代表 3 个接口, 0代表寄存器的初始值是0 ``` @@ -2917,7 +2917,7 @@ Reg#(int) creg [3] <- mkCReg(3, 0); // Reg#(int) creg [3] 代表接口数组, 以下展示一个例子: -```verilog +```bsv // 代码路径:src/12.CRegTest/CRegTest.bsv (部分) module mkTb (); Reg#(int) cnt <- mkReg(23); // 计数器 cnt 从 23 到 32 @@ -2926,7 +2926,7 @@ module mkTb (); cnt <= cnt + 1; if(cnt > 32) $finish; endrule - + Reg#(int) creg [3] <- mkCReg(3, 0); rule rule_test5 (cnt%5 == 0); // 每5周期执行一次 @@ -2947,7 +2947,7 @@ module mkTb (); endmodule ``` -仿真打印如下 :point_down: +仿真打印如下 :point_down: ``` cnt=23 creg= 0 @@ -2965,14 +2965,14 @@ cnt=33 creg=10 可以看到,在 `cnt=24` 的周期,`creg` 增加了 `2` (下一周期才能读到),这是因为 `rule_test3` 和 `rule_test2` 都执行了,相当于该周期执行了两次 `+1`: -``` +```bsv creg[1] <= creg[1] + 1; creg[2] <= creg[2] + 1; ``` 同理,在 `cnt=30` 的周期,`creg` 增加了 `3`,因为相当于执行了三次 `+1` : -``` +```bsv creg[0] <= creg[0] + 1; creg[1] <= creg[1] + 1; creg[2] <= creg[2] + 1; @@ -2980,13 +2980,13 @@ creg[2] <= creg[2] + 1; :pushpin: 注意:因为 `$display("cnt=%2d creg0=%2d", cnt, creg[0]);` 打印的是 `creg[0]` ,所以只能读到上周期得到的最终值。如果想读到 `creg` 本周期更新后的最新值,可以再多加一个接口: -``` +```bsv Reg#(int) creg [4] <- mkCReg(4, 0); ``` 然后读取 `creg[3]` 即可,比如: -```verilog +```bsv // 代码路径:src/12.CRegTest/CRegTest.bsv (修改过!!) module mkTb (); Reg#(int) cnt <- mkReg(23); // 计数器 cnt 从 23 到 32 @@ -2995,7 +2995,7 @@ module mkTb (); cnt <= cnt + 1; if(cnt > 32) $finish; endrule - + Reg#(int) creg [4] <- mkCReg(4, 0); // 修改!!:新增一个接口 rule rule_test5 (cnt%5 == 0); // 每5周期执行一次 @@ -3052,7 +3052,7 @@ cnt=33 creg=11 - `mkM1` 被实例化了2次,一次是在 `mkM3` 种,一次是在 `mkM2` 中。 - 尽管 `mkM3` 的接口是 Empty ,但它仍然能被 `mkTop` 实例化。 -- 顶层模块名是 `mkTop` ,但是没有任何模块调用它。当进行仿真时,用命令 `bsvbuild.sh -bs mkTop mkTop.bsv` 相当于指定了 `mkTop` 作为顶层模块,那么它在仿真中的实例名就是默认的 `Top` +- 顶层模块名是 `mkTop` ,但是没有任何模块调用它。当进行仿真时,用命令 `bsvbuild.sh -bs mkTop mkTop.bsv` 相当于指定了 `mkTop` 作为顶层模块,那么它在仿真中的实例名就是默认的 `Top` | ![图6](./readme_image/6.module_hierarchy.png) | | :-----------------------------------------: | @@ -3062,7 +3062,7 @@ cnt=33 creg=11 接口定义的语法是: -```verilog +```bsv interface Ifc_Name; // Ifc_Name 是接口名(首字母大写) method int method1; // 定义一个值方法,返回 int 类型 method Action method2(int x); // 定义一个动作方法,接受参数 int x @@ -3081,7 +3081,7 @@ endmodule 模块定义的语法是: -```verilog +```bsv module mkModuleName (Ifc_Name); // mkModule 是模块名(习惯上以 mk 开头), Ifc_Name 是它的接口名 // rules... // 实现一些 rule // method1 implementation // 实现 Ifc_Name 中的方法1 @@ -3092,25 +3092,25 @@ endmodule 这样,实例化一个模块并获得它的接口的语法是: -```verilog +```bsv Ifc_Name instance_name <- mkName; ``` 其中接口可以是 Empty ,代表没有接口: -```verilog +```bsv module mkModuleName (Empty); ``` 或者直接省略 `Empty` 关键字: -```verilog +```bsv module mkModuleName (); ``` 模块定义还可以有参数 (parameter),比如定义一个 `int` 类型的参数: -```verilog +```bsv module mkModuleName #(parameter int param_value) (Ifc_Name); ``` @@ -3144,7 +3144,7 @@ Ifc_Name instance_name <- mkModuleName(10); 下面是个最简单的例子,只是用来展示**值方法**和**动作方法**的语法: -```verilog +```bsv interface IfcTest; // 接口名 IfcTest method Action reset; // 定义动作方法 reset (不带参数) method Action set(int value); // 定义动作方法 set (带参数) @@ -3176,7 +3176,7 @@ endmodule 以上代码的值方法 `get` 还可以用如下函数风格的写法,用 `return` 返回数据,这种写法用在内部状态需要经历复杂计算才能返回的情况。 -```verilog +```bsv method int get; // 其它语句 (不能调用子模块的动作方法或动作值方法) return rg + 1; @@ -3193,7 +3193,7 @@ endmethod - 计算**长度码**:寻找 din 左起第一个 =1 的 bit ,用该 bit 所在的下标作为长度码 len。存在一种特例: - 当 din=8'h0 时,len=3'd0 - 计算**数据码**:取输入数据的低 len 个 bit 作为数据码 trim 。存在两种特例: -- 当 din=8'h0 时,trim=1'b0 +- 当 din=8'h0 时,trim=1'b0 - 当 din=8'h1 时,trim=1'b1 - 拼接**生成码**:令 trim 在高位,len 在低位,进行拼接,得到生成码 code - 更新**遗留码**,产生**输出数据**:把 code 拼接在上一周期的遗留码 drem 上,拼接后如果长度 >=16 ,就把低 16bit 作为输出码 dout 输出,drem 则删去这低 16bit 。 @@ -3219,7 +3219,7 @@ endmethod 我们首先给出比特编码器的接口 `BitCoder` 的定义: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v1.bsv (部分) interface BitCoder; method Action put(Bit#(8) data); // 动作方法 put:输入 8 bit数据 @@ -3230,7 +3230,7 @@ endinterface 然后给出模块 `mkBitCoder` 的实现。首先编写一个函数 `getCode` (组合逻辑电路),用来获取 code 以及其长度。因为 code 最长是 10,所以用 `Bit#(10)` 存放 code ,用 `UInt#(4)` 存放 code 的长度。因为我们想返回这两个值,这里就用了二元组 `Tuple2#(Bit#(10), UInt#(4))` 类型作为返回值。 -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v1.bsv (部分) function Tuple2#(Bit#(10), UInt#(4)) getCode(Bit#(8) din); // 计算长度码 len @@ -3238,7 +3238,7 @@ function Tuple2#(Bit#(10), UInt#(4)) getCode(Bit#(8) din); for(UInt#(4) i=0; i<8; i=i+1) if(din[i] == 1) len = i; - + // 计算数据码 trim 的长度 UInt#(4) trim_len = len>0 ? len : 1; @@ -3254,13 +3254,13 @@ endfunction 然后编写模块 `mkBitCoder` 的实现如下 :point_down: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v1.bsv (部分) (* synthesize *) module mkBitCoder (BitCoder); // 流水线第一级产生的数据,默认 code 和 code 的长度都是 0 Reg#(Tuple2#(Bit#(10), UInt#(4))) in_code_and_len <- mkDReg( tuple2(0,0) ); // code 和 code 的长度 - + // 流水线第二级产生的数据 Reg#(Bit#(31)) drem <- mkReg(0); // 存放遗留码 Reg#(UInt#(5)) drem_len <- mkReg(0); // 遗留码的长度 @@ -3269,7 +3269,7 @@ module mkBitCoder (BitCoder); // 流水线第二级:更新遗留数据和输出数据 rule get_drem_and_dout; - match {.code, .code_len} = in_code_and_len; // 当流水线上一级没有数据时,mkDReg 默认读到 0 + match {.code, .code_len} = in_code_and_len; // 当流水线上一级没有数据时,mkDReg 默认读到 0 // 不会对本 rule 造成副作用 Bit#(31) data = (extend(code) << drem_len) | drem; // data = code 拼接 drem @@ -3305,7 +3305,7 @@ endmodule 最后,编写 testbench (测试程序)如下 :point_down: ,它实例化了模块 `mkBitCoder` ,实例名为 `coder` 。它调用 `coder.put` 方法依次输入 `8'h0` \~ `8'hFF` ,一共输入两轮。并在 `coder.get_valid = True` 时打印 `coder.get` 方法的值。 -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v1.bsv (部分) module mkTb(); Reg#(int) cnt <- mkReg(0); @@ -3344,14 +3344,14 @@ endmodule 在**比特编码器**的例子中,显然我们可以把 `get_valid` 方法作为 `get` 方法的隐式条件,这样还可以省略 `get_valid` 方法。我们在接口定义中删掉 `get_valid` 方法的定义。然后把 `mkBitCoder` 中的 `get` 方法实现改成如下。 -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v2.bsv (部分) method Bit#(16) get if(dout_valid) = dout; // 值方法,隐式条件为 dout_valid=True ``` 以上带隐式条件的值方法也可以写成: -```verilog +```bsv method Bit#(16) get if(dout_valid); // 另一种写法 return dout; endmethod @@ -3359,7 +3359,7 @@ endmodule 然后,在 `mkTb` (testbench)中,可以把调用 coder.get 的规则的显式条件删掉: -```verilog +```bsv rule coder_get; $display("cnt=%4d %b", cnt, coder.get); // 只在 coder.get_valid 有效周期打印输出 // 因为 coder.get 具有隐式条件,隐式条件不满足时不会打印 @@ -3374,7 +3374,7 @@ endmodule 动作方法 (Action Method) 也可以有隐式条件,格式如下: -```verilog +```bsv method Action 方法名(...) if(隐式条件表达式); // 语句... endmethod @@ -3386,7 +3386,7 @@ endmodule 修改功能要求后,我们先把 dout 相关的变量、接口类型都改成 `Bit#(8)` (步骤省略)。然后修改规则 `get_drem_and_dout` (第二级流水线)的代码,让每周期只取拼接后的低 8bit 作为输出: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v3.bsv (部分) rule get_drem_and_dout; // 流水线第二级:更新遗留数据和输出数据 match {.code, .code_len} = in_code_and_len; @@ -3408,7 +3408,7 @@ endmodule 然后,给 put 方法加入隐式条件如下: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v3.bsv (部分) method Action put(Bit#(8) din) if(drem_len <= 19); // 隐式条件保证下一周期 drem 拼接后的长度不会溢出(大于31) in_code_and_len <= getCode(din); @@ -3420,7 +3420,7 @@ endmodule ``` |-----------------------| |--------------------------| | | (传递10bit) | drem容量:31bit | -put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 个 bit |---> dout +put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 个 bit |---> dout | | | | (可输出8bit) |-----------------------| |--------------------------| 流水线第一级 流水线第二级 @@ -3441,7 +3441,7 @@ put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 实现一个动作值方法的语法如下: -```verilog +```bsv method ActionValue#(返回值类型) method_name(参数...) if(隐式条件); // 语句(可以调用子模块的动作方法或动作值方法 return 返回值; @@ -3450,7 +3450,7 @@ put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 调用一个动作值方法时,必须用**副作用赋值**符号 `<-` 获得其返回值: -```verilog +```bsv 返回值类型 变量名 <- module_instance.method_name(提供参数...); // 如果返回值不是多态接口,可以用 let 变量名 <- module_instance.method_name(提供参数...); ``` @@ -3465,7 +3465,7 @@ put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 首先,我们把 get 方法的定义改成 ActionValue : -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v4.bsv (部分) interface BitCoder; method Action put(Bit#(8) din); // 动作方法:输入 8 bit数据 @@ -3475,13 +3475,13 @@ endinterface 然后,编写模块实现: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v4.bsv (部分) module mkBitCoder (BitCoder); // 流水线第一级产生的数据,默认 code 和 code 的长度都是 0 Reg#(Tuple2#(Bit#(10), UInt#(4))) in_code_and_len <- mkReg( tuple2(0,0) ); // code 和 code 的长度 Reg#(Bool) din_valid <- mkReg(False); // 指示 in_code_and_len 是否有效 - + // 流水线第二级产生的数据 Reg#(Bit#(31)) drem <- mkReg(0); // 存放遗留码 Reg#(UInt#(6)) drem_len <- mkReg(0); // 遗留码的长度 @@ -3516,7 +3516,7 @@ module mkBitCoder (BitCoder); // 流水线第一级:获取 code 以及其长度 method Action put(Bit#(8) din) if(!din_valid); // 隐式条件保证下一周期 drem 的长度不会溢出 - din_valid <= True; + din_valid <= True; in_code_and_len <= getCode(din); // 计算 code 和 code 的长度 endmethod @@ -3531,7 +3531,7 @@ endmodule 这里使用了一种很具有 BSV 特色的写法——在方法 `put` 和规则 `get_drem_and_dout` 里都写了寄存器 `din_valid` 。虽然二者能同时激活,但由于发生冲突的语句在 if 条件的控制下并不会在同一周期执行,所以用 `conflict_free` 调度属性来避免它们之间的激活抑制: -``` +```bsv (* conflict_free = "put, get_drem_and_dout" *) ``` @@ -3544,7 +3544,7 @@ endmodule 最后,修改 testbench ,给规则 `coder_get` 加上显式条件,让它不是每周期都允许执行 `coder.get` ,此时 `coder` 中的数据会堆积,以此来验证功能的正确性。 -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v4.bsv (部分) rule coder_get (cnt%2 == 0); // 因为 coder 中能积攒数据,所以可以添加条件 // 来让一些周期不读取 dout ,也不会导致数据丢失 @@ -3574,13 +3574,13 @@ endmodule 如果你要求调用 methodA 就等效于调用 methodB (包括继承 methodB 的隐式条件),那么 methodA 的实现可以简写为: -```verilog +```bsv method methodA = methodB; // 简写 ``` 而没必要按照最常见的完整写法来写: -```verilog +```bsv method Action methodA(int x); // 不是简写 moduleB_instance.methodB(x); endmethod @@ -3596,7 +3596,7 @@ endmethod 第一版实现如下(仿真结果不是重点,不展示,可自行去跑仿真) -```verilog +```bsv // 代码路径:src/14.IncreaseReg/IncreaseReg_v1.bsv (部分) interface IncreaseReg; // 自增计数器的接口 method Action write(int x); @@ -3622,7 +3622,7 @@ endmodule 不简写则如下,也即我们熟悉的类似“函数定义”的风格: -```verilog +```bsv method Action write(int x); reg_data <= x; endmethod @@ -3646,7 +3646,7 @@ endmodule 修改代码如下: -```verilog +```bsv // 代码路径:src/14.IncreaseReg/IncreaseReg_v2.bsv (部分) module mkIncreaseReg (Reg#(int)); // 接口为 Reg#(int) @@ -3664,20 +3664,20 @@ endmodule 我们知道接口 `Reg#(int)` 有两个方法: -- `method Action _write(int x)` +- `method Action _write(int x)` -- `method int _read` +- `method int _read` 我们本可以直接用上一节的写法实现它们: -```verilog +```bsv method _write = reg_data._write; method _read = reg_data._read; ``` 但这里采用了更简便的写法: -```verilog +```bsv return reg_data; ``` @@ -3685,7 +3685,7 @@ endmodule 在调用时,直接用 `Reg#(int)` 来接受 `mkIncreaseReg` 实例化后的接口: -```verilog +```bsv Reg#(int) inc_reg <- mkIncreaseReg; ``` @@ -3706,7 +3706,7 @@ endmodule 定义接口 `IncreaseRegCfg` 如下,它嵌套了两个 `Reg#(int) val` 接口: -```verilog +```bsv // 代码路径:src/14.IncreaseReg/IncreaseRegCfg_v1.bsv (部分) interface IncreaseRegCfg; interface Reg#(int) data; // 嵌套了一个 Reg#(int) 子接口 @@ -3717,7 +3717,7 @@ endinterface `mkIncreaseRegCfg` 实现如下: -```verilog +```bsv // 代码路径:src/14.IncreaseReg/IncreaseRegCfg_v1.bsv (部分) module mkIncreaseRegCfg (IncreaseRegCfg); Reg#(int) reg_data <- mkReg(0); // 用来保存数据值的寄存器 @@ -3735,7 +3735,7 @@ endmodule 如果你有在方法里实现更复杂行为的需求,想要把子接口中的方法展开实现,当然也是可以的,请用接口表达式 `interface ... endinterface` ,在其中编写它的方法的实现。比如,以上代码的 `interface data = reg_data;` 可以展开写成: -```verilog +```bsv // interface val = value; 展开写: interface data = interface Reg#(int); method Action _write(int x); // 方法 _write 展开写了 @@ -3747,7 +3747,7 @@ endmodule 在调用时,需要用 `接口名.子接口名.方法` 这样的方式。比如: -```verilog +```bsv // 对于 let inc_reg <- mkIncreaseRegCfg; inc_reg.step <= 2; // 相当于 inc_reg.step._write(2) $display("%d", inc_reg.data); // 相当于 $display("%d", inc_reg.data._read); @@ -3768,11 +3768,11 @@ endmodule 将 `mkIncreaseRegCfg` 的接口改为接口二元组 `Tuple2#(Reg#(int), Reg#(int))` ,代码如下: -```verilog +```bsv // 代码路径:src/14.IncreaseReg/IncreaseRegCfg_v2.bsv (部分) module mkIncreaseRegCfg ( Tuple2#(Reg#(int), Reg#(int)) ); // 该模块的接口是元组,包括两个 Reg#(int) 接口 Reg#(int) reg_data <- mkReg(0); - Reg#(int) reg_step <- mkReg(1); + Reg#(int) reg_step <- mkReg(1); (* preempts = "fst._write, increase" *) // fst 代表元组第一个元素(也就是自增计数器寄存器的接口) // fst._write 自然就代表自增计数器的 _write 方法。 @@ -3787,7 +3787,7 @@ endmodule 在实例化 `mkIncreaseRegCfg` 时,可以用元组的 match 语句来获得两个接口。 -```verilog +```bsv match {.inc_reg_data, .inc_reg_step} <- mkIncreaseRegCfg; // 得到 inc_reg_data 是 Reg#(int) 类型的接口 // 得到 inc_reg_step 也是 Reg#(int) 类型的接口 // 然后 inc_reg_data 和 inc_reg_step 用起来就和普通的寄存器 一样。 @@ -3803,7 +3803,7 @@ match {.inc_reg_data, .inc_reg_step} <- mkIncreaseRegCfg; // 得到 inc_reg_data 举例如下。当规则 `r1` 和 `r2` 中出现相同的、比较复杂的语句时,可以把它们打包成一个动作函数,来降低代码量,提高可维护性。 -```verilog +```bsv module mkTb (Empty); Reg#(int) x <- mkReg (0); Reg#(int) y <- mkReg (0); @@ -3850,7 +3850,7 @@ BRAMCore 包生成的 Verilog 代码会自动适配 Xilinx 和 Altera 各种型 在使用前需要引入: -```verilog +```bsv import BRAMCore::*; ``` @@ -3858,7 +3858,7 @@ import BRAMCore::*; 对于单口 RAM,BRAMCore 包提供了一个基础的单口存储器接口 `BRAM_PORT` : -```verilog +```bsv interface BRAM_PORT#(type addr, type data); // 单口 RAM 接口 method Action put(Bool write, addr address, data datain); //每周期只允许操作一次,每次操作要么是写,要么是读 method data read(); // 获取读出的数据 @@ -3867,7 +3867,7 @@ endinterface 对于双口 RAM,提供了一个嵌套接口 `BRAM_DUAL_PORT` ,它嵌套了两个单口 `BRAM_PORT` : -```verilog +```bsv interface BRAM_DUAL_PORT#(type addr, type data); // 双口 RAM 接口 interface BRAM_PORT#(addr, data) a; // A口,是个单口 RAM 接口 interface BRAM_PORT#(addr, data) b; // B口,是个单口 RAM 接口 @@ -3878,11 +3878,11 @@ endinterface BRAMCore 包提供了 `mkBRAMCore1` 模块用来创建单口 RAM, `mkBRAMCore2` 模块用来创建双口 RAM 。它们的定义是: -```verilog -// 单口 RAM +```bsv +// 单口 RAM module mkBRAMCore1#(Integer memSize, Bool hasOutputRegister) (BRAM_PORT#(addr, data)) -// 双口 RAM +// 双口 RAM module mkBRAMCore2#(Integer memSize, Bool hasOutputRegister) (BRAM_DUAL_PORT#(addr, data)) ``` @@ -3895,7 +3895,7 @@ module mkBRAMCore2#(Integer memSize, Bool hasOutputRegister) (BRAM_DUAL_PORT#(ad 以下语句实例化一个单口 RAM 和一个 双口 RAM : -```verilog +```bsv BRAM_PORT#(Bit#(12), UInt#(40)) ram1 <- mkBRAMCore1(4096, True); // 地址类型为 Bit#(12),数据类型为 UInt#(40) // 容量为 4096 个数据,读数据经过寄存器 BRAM_DUAL_PORT#(Bit#(15), int) ram2 <- mkBRAMCore2(10000, False); // 地址类型为 Bit#(15),数据类型为 int @@ -3904,27 +3904,27 @@ BRAM_DUAL_PORT#(Bit#(15), int) ram2 <- mkBRAMCore2(10000, False); // 地址类 对于单口的 ram1 ,调用 put 方法来进行读写操作: -```verilog +```bsv ram1.put(True , 1234, 114514); // 写操作,地址 1234,写入 114514 ram1.put(False, 4000, 0); // 读操作,地址 4000, datain 参数无所谓 ``` 考虑到 ram1 读数据经过寄存器,在调用读操作的两个时钟周期后再调用 read 方法,才能保证拿出正确的读数据: -```verilog +```bsv UInt#(40) rdata = ram1.read; ``` 对于双口的 ram2 ,以下语句可以分别从 A口 和 B口 读写它: -```verilog +```bsv ram2.a.put(True , 1234, 114514); // A口写操作,地址 1234,写入 114514 ram2.b.put(False, 4000, 0); // B口读操作,地址 4000, datain 参数无所谓 ``` 考虑到 ram2 读数据不经过寄存器,在 B口调用读操作的一个时钟周期后再调用 b.read 方法,才能保证拿出正确的读数据: -```verilog +```bsv int rdata = ram2.b.read; ``` @@ -3943,11 +3943,11 @@ BRAMCore 的 put 和 read 方法都不具有隐式条件。 mkBRAMCore1Load 和 mkBRAMCore2Load 模块在仿真开始时用一个文本文件作为自身的初始数据。它们多出了两个参数: -```verilog +```bsv // 单口 RAM , 载入文件作为初始数据 module mkBRAMCore1Load#(Integer memSize, Bool hasOutputRegister, String file, Bool binary ) (BRAM_PORT#(addr, data)) - + // 双口 RAM ,载入文件作为初始数据 module mkBRAMCore2Load#(Integer memSize, Bool hasOutputRegister, String file, Bool binary ) (BRAM_DUAL_PORT#(addr, data)) @@ -3955,7 +3955,7 @@ module mkBRAMCore2Load#(Integer memSize, Bool hasOutputRegister, String file, Bo 其中 `String file` 是文本文件名字符串,是相对于 .bsv 源码文件所在的路径。例如: -- 文本文件 data.txt 与 .bsv 文件在同一个目录下,那么 `String file` 应该取 `"data.txt"` +- 文本文件 data.txt 与 .bsv 文件在同一个目录下,那么 `String file` 应该取 `"data.txt"` - 文本文件 hello.txt 在 .bsv 文件所在的目录下的 test 目录下,那么 `String file` 应该取 `"test/hello.txt"` `Bool binary=True` 代表文件由二进制数组成。以下是一个二进制文本文件内容的示例,每行一项数据,载入后,每项数据会占用 BRAM 的一个地址。 @@ -3988,7 +3988,7 @@ BRAM 包生成的 Verilog 代码会自动适配 Xilinx 和 Altera 各种型号 使用前需要引入: -```verilog +```bsv import BRAM::*; ``` @@ -3996,7 +3996,7 @@ import BRAM::*; 实例化 BRAM 时需要用到结构体类型 `BRAM_Configure` (目前还没学结构体,但不影响理解),该结构体包含以下字段: -```verilog +```bsv typedef struct { Integer memorySize; // 存储器容量。当取0时,存储器容量 = 2^地址的位宽 LoadFormat loadFormat; // 仿真初值数据,取 None, @@ -4010,7 +4010,7 @@ typedef struct { 它还提供了一个常用的默认配置 `defaultValue` ,它是该结构体类型的一个变量: -```verilog +```bsv defaultValue = BRAM_Configure { memorySize : 0, // 存储器容量 = 2^地址的位宽 loadFormat : None, // 不使用文件设置初始数据 @@ -4023,7 +4023,7 @@ defaultValue = BRAM_Configure { 向 BRAM 发起一个读写操作时,需要用到另一个结构体类型 `BRAMRequest` ,包含以下字段: -```verilog +```bsv typedef struct { Bool write; // True:写 False:读 Bool responseOnWrite; // 如果是写操作,是否允许写响应 @@ -4036,7 +4036,7 @@ typedef struct { 单口 RAM 和双口 RAM 的接口分别如下: -```verilog +```bsv // 单口 RAM 接口(只有A口) interface BRAM1Port#(type taddr, type tdata); // taddr 是地址类型, tdata 是每项数据的类型 interface BRAMServer#(taddr, tdata) portA; // BRAMServer接口,用来发起读写操作、获得读数据和写响应 @@ -4061,7 +4061,7 @@ endinterface: BRAM2Port 单口 RAM 和双口 RAM 的模块定义如下: -```verilog +```bsv // 单口 RAM 模块 module mkBRAM1Server #( BRAM_Configure cfg ) ( BRAM1Port #(taddr, tdata) ) @@ -4075,13 +4075,13 @@ module mkBRAM2Server #( BRAM_Configure cfg ) ( BRAM2Port #(taddr, tdata) ) 以下语句实例化一个单口 RAM ,地址类型是 `Bit#(10)` ,数据的类型是 `Int#(8)` 。使用默认配置,因此 BRAM 容量是默认的 2^10=1024 ,不使用文件设置初始值,读数据和写响应的延迟=1,读数据和写响应的缓存大小=3。 -```verilog +```bsv BRAM1Port#(Bit#(10) , Int#(8)) ram1 <- mkBRAM1Server(defaultValue); ``` 以下语句使用自定义配置初始化一个双口 RAM(通过构造 `BRAM_Configure` 结构体)。它的地址类型是 `UInt#(12)` ,数据的类型是 `int` 。 -```verilog +```bsv BRAM2Port#(UInt#(12), int) ram2 <- mkBRAM2Server( BRAM_Configure { // 构造一个 BRAM_Configure 类型的结构体 memorySize : 2000, // 存储器容量 = 2000 @@ -4095,7 +4095,7 @@ BRAM2Port#(UInt#(12), int) ram2 <- mkBRAM2Server( 用以下语句来向 ram2 的 B口 发起读写操作(通过构造 BRAMRequest 结构体)。 -```verilog +```bsv Bool iswrite = ...; UInt#(12) addr = ...; int wdata = ...; @@ -4111,7 +4111,7 @@ ram2.portB.request.put( 然后,用以下语句来从 ram2 的 B口中拿出读到的数据: -```verilog +```bsv int rdata <- ram2.portB.response.get; // 动作值方法 ``` @@ -4143,14 +4143,14 @@ BRAM 具有一个读数据和写响应的缓存队列(一般我们不用写响 我们实例化 BRAM 如下。注意到地址类型被我故意设计为 `Tuple3#(bit, UInt#(3), UInt#(3))` ,其中第一个元素 bit 作为块号,第二个元素 UInt#(3) 作为行号,第三个元素 UInt#(3) 作为列号。 -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) BRAM2Port#( Tuple3#(bit, UInt#(3), UInt#(3)) , int ) ram <- mkBRAM2Server(defaultValue); ``` 然后,定义写指针和读指针: -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) Reg#(Bit#(2)) wb <- mkReg(0); // 写块号指针 Reg#(UInt#(3)) wi <- mkReg(0); // 写行号 @@ -4173,7 +4173,7 @@ BRAM2Port#( Tuple3#(bit, UInt#(3), UInt#(3)) , int ) ram <- mkBRAM2Server(defaul 根据以上技巧,编写用 wb 和 rb 来判断空满的代码: -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) Wire#(Bool) empty <- mkWire; Wire#(Bool) full <- mkWire; @@ -4188,12 +4188,12 @@ BRAM2Port#( Tuple3#(bit, UInt#(3), UInt#(3)) , int ) ram <- mkBRAM2Server(defaul - 隐式条件:`!full` (不满) - 行为:把数据写入 `{块号, 行号, 列号}` 所指向的 BRAM 位置,然后按行主序的方式移动指针 -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) method Action datain(int val) if(!full); // 外界调用该方法,输入待转置(行主序)的数据流 ram.portA.request.put( BRAMRequest{write: True, responseOnWrite: False, address: tuple3(wb[0], wi, wj), datain: val }); - + // ------------ 按行主序的方式移动指针 ------------ wj <= wj + 1; // 列号先增加 if(wj == 7) begin // 列号到最大时 @@ -4209,7 +4209,7 @@ endmethod - 显式条件:`!empty` (不空) - 行为:读 `{块号, 行号, 列号}` 所指向的 BRAM 位置,然后按列主序的方式移动指针 -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) rule read_ram (!empty); ram.portB.request.put( @@ -4225,7 +4225,7 @@ endrule 然后,编写从 BRAM 读出数据的方法,作为矩阵转置器的输出数据的方法: -```verilog +```bsv // 代码路径:src/17.MatrixT/MatrixT.bsv (部分) method ActionValue#(int) dataout; // 外界调用该方法,获得转置后(列主序)的数据流 let val <- ram.portB.response.get; @@ -4249,7 +4249,7 @@ endmethod FIFO 是一类非常有用的模块。BSV 提供了几个 FIFO 包。在只进行同步时序逻辑设计时,只需了解 FIFO 、 FIFOF 、 SpecialFIFOs 、BRAMFIFO 这四个包中的各种同步 FIFO 就够。使用前根据需要引入它们: -```verilog +```bsv import FIFO::*; import FIFOF::*; import SpecialFIFOs::*; @@ -4325,7 +4325,7 @@ FIFO 模块众多,功能大同小异,概览如**表16**。(从模块名可 以下语句实例化了四个 FIFO : -```verilog +```bsv FIFOF#(Int#(6)) fifo1 <- mkFIFOF1; // 接口为 FIFOF ,内容数据是 Int#(6) 类型,容量=1 FIFO#(Tuple2(Bool,int)) fifo2 <- mkFIFO; // 接口为 FIFO ,内容数据是 Tuple2(Bool,int) 类型,容量=2 FIFO#(Int#(6)) fifo3 <- mkSizedFIFO(3); // 接口为 FIFO ,内容数据是 Int#(6) 类型,容量=3 @@ -4334,13 +4334,13 @@ FIFOF#(Bool) fifo4 <- mkSizedFIFOF(4); // 接口为 FIFOF ,内容数据 以下语句给 `fifo2` 中压入一项数据,该数据需要符合 `fifo2` 的内容数据类型: -```verilog +```bsv fifo2.enq( tuple2(True, 42) ); // fifo2 内容数据类型为二元组,这里也要压入二元组 ``` 以下语句从 `fifo4` 中拿到最旧的数据,并把最旧的数据弹出(如果不弹出,下次拿到的还是这项数据): -```verilog +```bsv let b = fifo4.first; // 拿到最旧的数据 , 类型为 Bool fifo4.deq; // 弹出最旧的数据 // 逻辑顺序由调度注解 first SB deq 来保证,因此 first 和 deq 顺序可以交换,不影响结果 @@ -4348,7 +4348,7 @@ fifo4.deq; // 弹出最旧的数据 或者可以写的更复杂一些:以下代码不断拿到并弹出 `fifo3` 的数据,但只有该数据与 `fifo1` 的数据相等时,才弹出 `fifo1` : -```verilog +```bsv fifo3.deq; let v3 = fifo3.first; let v1 = fifo1.first; @@ -4381,9 +4381,9 @@ if(v1 == v3) 首先,仍然是用数组,定义 17 个 FIFO ,并用 for 循环调用 17 次 `mkFIFO` 进行实例化: -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v2.bsv (部分) - FIFO#( Tuple2#(UInt#(32), UInt#(32)) ) fifos [17]; // 接口数组,数组长度=17 + FIFO#( Tuple2#(UInt#(32), UInt#(32)) ) fifos [17]; // 接口数组,数组长度=17 // 接口类型是 FIFO#( Tuple2#(UInt#(32), UInt#(32)) ) // FIFO 内容数据类型是 Tuple2#(UInt#(32), UInt#(32)) for(int n=16; n>=0; n=n-1) @@ -4392,7 +4392,7 @@ if(v1 == v3) 然后用 for 循环重复部署 16 个规则,每个规则从上一级的 fifo 里拿到和弹出数据,使用我们上次就实现的 `sqrtIteration` 函数(用来计算一次迭代的组合逻辑)计算后,压入下一级 fifo : -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v2.bsv (部分) for(int n=15; n>=0; n=n-1) rule pipe_stages; @@ -4407,22 +4407,22 @@ if(v1 == v3) 首先,定义模块的接口: -```verilog +```bsv module mkSqrtUInt32( FIFO#(UInt#(32)) ); ``` 然后,在模块体中实现 `FIFO#(UInt#(32))` 的所有方法:`enq`, `deq`, `first` 和 `clear` 。代码如下: -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v2.bsv (部分) method Action enq(UInt#(32) x); // 模块的 enq 方法负责: fifos[16].enq( tuple2(x, 0) ); // 把输入数据压入流水线最前级的 fifo endmethod method deq = fifos[0].deq; // 模块的 deq 方法负责:流水线最末级的 fifo deq - + method UInt#(32) first; // 模块的 first 方法负责: - match {.x, .y} = fifos[0].first; // 拿到流水线最末级的 fifo.first , 解构该 Tuple2 + match {.x, .y} = fifos[0].first; // 拿到流水线最末级的 fifo.first , 解构该 Tuple2 return y; // 返回其中的结果数据 y endmethod @@ -4434,7 +4434,7 @@ module mkSqrtUInt32( FIFO#(UInt#(32)) ); 最后,编写测试模块,如下: -```verilog +```bsv // 代码路径:src/15.Sqrt/Sqrt_v2.bsv (部分) module mkTb(); Reg#(int) cnt <- mkReg(0); @@ -4444,7 +4444,7 @@ module mkTb(); endrule Reg#(UInt#(32)) x <- mkReg(1); - + FIFO#(UInt#(32)) sqrter <- mkSqrtUInt32; // 实例化 mkSqrtUInt32,接口是 FIFO#(UInt#(32)) rule sqrter_input; // 这里也可以加入显式条件来实现不积极输入 @@ -4478,7 +4478,7 @@ endmodule 用以下语句实例化一个 mkUGFIFOF : -```verilog +```bsv FIFOF#(int) fifo <- mkUGFIFOF; ``` @@ -4488,7 +4488,7 @@ FIFOF#(int) fifo <- mkUGFIFOF; 它们的定义如下,可以发现多出了两个是否不保护的模块参数 (ugenq 和 ugdeq) : -```verilog +```bsv module mkGFIFOF#(Bool ugenq, Bool ugdeq) (FIFOF#(td)) provisos (Bits#(td, width_any)); @@ -4501,7 +4501,7 @@ module mkGSizedFIFOF#(Bool ugenq, Bool ugdeq, Integer n) (FIFOF#(td)) 举例如下,实例化了三个 FIFO: -```verilog +```bsv FIFOF#(int) fifo1 <- mkGFIFOF(True, False); // 容量=2,不保护压入,保护弹出 FIFOF#(int) fifo2 <- mkGFIFOF1(False, True); // 容量=1,保护压入,不保护弹出 FIFOF#(int) fifo3 <- mkGSizedFIFOF(True, True, 5); // 容量=5,不保护压入,不保护弹出 @@ -4518,7 +4518,7 @@ LFIFO (流水线FIFO)包括4个模块:`mkLFIFO`、`mkLFIFOF`、`mkGLFIFOF` 以下代码实例化了两个 LFIFO : -```verilog +```bsv FIFO#(int) fifo1 <- mkLFIFO; // 容量=1,接口为 FIFO ,保护压入,保护弹出 FIFOF#(int) fifo2 <- mkGLFIFOF(False, True); // 容量=1,接口为 FIFOF,保护压入,不保护弹出 ``` @@ -4567,7 +4567,7 @@ FIFOF#(int) fifo2 <- mkGLFIFOF(False, True); // 容量=1,接口为 FIFOF, 然后,只需按照**图8**编写代码即可。这里只展示四个流水线级行为和转发行为的代码如下。完整的代码见 `src/16.AccumulateRam/AccumulateRam.bsv` 。 -```verilog +```bsv // 代码路径: src/16.AccumulateRam/AccumulateRam.bsv (部分) BRAM2Port#(UInt#(12), int) ram <- mkBRAM2Server(defaultValue); @@ -4626,7 +4626,7 @@ endrule 该默认值在模块实例化时指定。比如指定该默认值为 int 型的 42: -```verilog +```bsv FIFOF#(int) fifo <- mkDFIFOF(42); // 默认值=42 ``` @@ -4648,13 +4648,13 @@ FIFOF#(int) fifo <- mkDFIFOF(42); // 默认值=42 另外,我们把比特编码器的接口改成 `FIFO#(Bit#(8))` ,而不再用自定义的新接口。最后代码如下: -```verilog +```bsv // 代码路径:src/13.BitCoding/BitCoding_v5.bsv (部分) module mkBitCoder ( FIFO#(Bit#(8)) ); FIFO#(Bit#(8)) fifo1 <- mkFIFO; - FIFOF#(Tuple2#(Bit#(10), UInt#(4))) fifo2 <- mkDFIFOF( tuple2(0, 0) ); - FIFO#(Bit#(8)) fifo3 <- mkFIFO; + FIFOF#(Tuple2#(Bit#(10), UInt#(4))) fifo2 <- mkDFIFOF( tuple2(0, 0) ); + FIFO#(Bit#(8)) fifo3 <- mkFIFO; Reg#(Tuple2#(Bit#(31), UInt#(6))) drem_reg <- mkReg( tuple2(0, 0) ); // 存放遗留码 drem 以及其长度 @@ -4667,15 +4667,15 @@ module mkBitCoder ( FIFO#(Bit#(8)) ); for(UInt#(4) i=0; i<8; i=i+1) // for循环:计算长度码 len if(din[i] == 1) len = i; - + UInt#(4) trim_len = len>0 ? extend(len) : 1; // 计算数据码 trim 的长度 - Bit#(7) trim = truncate(din) & ~('1<8.8 BypassFIFO -BypassFIFO 包括3个模块: +BypassFIFO 包括3个模块: - `mkBypassFIFO` :容量=1,接口为 FIFO - `mkBypassFIFOF` :容量=1,接口为 FIFOF @@ -4722,7 +4722,7 @@ BypassFIFO 包括3个模块: 例如,以下代码实例化了两个 BypassFIFO: -```verilog +```bsv FIFO#(int) fifo1 <- mkBypassFIFO; // 容量=1,接口为 FIFO FIFOF#(int) fifo2 <- mkSizedBypassFIFOF(4); // 容量=4,接口为 FIFOF ``` @@ -4743,7 +4743,7 @@ BypassFIFO 在为空时支持并发 enq 与 deq (参考**图7**),此时 enq 使用 BRAMFIFO 前需要引入: -```verilog +```bsv import FIFO::*; import FIFOF::*; import BRAMFIFO::*; @@ -4758,7 +4758,7 @@ BRAMFIFO 包括两个模块: 以下代码实例化了两个 BRAMFIFO : -```verilog +```bsv FIFO#(int) fifo1 <- mkSizedBRAMFIFO(8192); // 容量=8192,接口为 FIFO FIFOF#(int) fifo2 <- mkSizedBRAMFIFOF(114514); // 容量=114514,接口为 FIFOF ``` @@ -4777,7 +4777,7 @@ BSV 提供一些复杂的数据类型,包括数组 (array)、向量 (Vector) 如下例子创建了一个名为 `arr` 的数组。它有 10 项数据,每项数据的类型都是 `UInt#(16)` 。如果写在模块内,就是整个模块的局部变量(在运行时实际上是常量,因为它们不可能被硬件动态修改);如果写在规则内,就是规则内的局部变量,生命周期为当前时钟周期。注意:它不会生成一个容量为 10 的寄存器或存储器。 -```verilog +```bsv UInt#(16) arr[10]; // 数组,每个元素为 UInt#(16) 类型,长度为10,下标范围为 0~9 // 为这些数组实例赋值 , 注意数组下标从 0 开始 @@ -4794,7 +4794,7 @@ for (UInt#(16) i=0; i<10; i=i+1) 如下例子创建了一个名为 `mat` 的二维数据,并用双重循环赋值。 -```verilog +```bsv UInt#(16) mat [3][4]; // 二维数组,第1维下标范围为 0~2 , 第2维下标范围为 0~3 for (UInt#(16) i=0; i<3; i=i+1) for (UInt#(16) j=0; j<4; j=j+1) @@ -4803,7 +4803,7 @@ for (UInt#(16) i=0; i<3; i=i+1) 可以用 `{}` 来给数组各元素赋初值: -```verilog +```bsv int arr3 [3] = {1, 7, 6}; // 三个元素分别为 1,7,6 int arr4 [2][3] = {{2,5,9}, {3,7,1}}; ``` @@ -4812,7 +4812,7 @@ int arr4 [2][3] = {{2,5,9}, {3,7,1}}; 如下例子创建了一个长度为 4 的寄存器接口数组,并用 for 循环分别给数组每项都实例化了 `mkRegU` 。 -```verilog +```bsv Reg#(int) reg_arr [4]; // 寄存器接口数组,每个元素为 Reg#(int) 类型 for (int i=0; i<4; i=i+1) reg_arr[i] <- mkRegU; @@ -4820,7 +4820,7 @@ Reg#(int) reg_arr [4]; // 寄存器接口数组,每个元素为 Reg#(int 这四个寄存器是独立的,在一个规则内,可以全部写入,也可以写入部分几个或一个: -```verilog +```bsv rule test; reg_arr[1] <= ’h10; reg_arr[3] <= reg_arr[2] + 2; @@ -4829,7 +4829,7 @@ endrule 也可以用一个运行时的变量(比如来自另一个寄存器)作为可变地址来访问。 -```verilog +```bsv Reg#(UInt#(2)) addr <- mkReg(0); rule test; reg_arr[addr] <= reg_arr[addr] + 1; @@ -4844,13 +4844,13 @@ endrule 向量是一种复合数据类型,能提供比数组更强大的功能。使用前需要引入: -``` +```bsv import Vector::*; ``` 以下语句创建了一个长度为 4,每项元素为 `Int#(16)` 的 Vector 。然后你可以用下标访问它们,分别为它们赋值。注意:与数组相同,如果写在模块内,就是整个模块的局部变量(在运行时实际上是常量,因为它们不可能被硬件动态修改);如果写在规则内,就是规则内的局部变量,生命周期为当前时钟周期。注意:它们不会生成一个容量为 4 的寄存器或存储器。 -```verilog +```bsv Vector#(4, Int#(16)) vec1; // Vector,长度为4,下标范围为 0~3 vec1[0] = 2; vec1[1] = -1; @@ -4859,7 +4859,7 @@ vec1[1] = -1; 当 Vector 内的元素派生自 Bits 类型类时, 整个 Vector 也是派生自 Bits 类型类的,可以用 pack 函数转化成 `Bit#(n)` 类型 。也可以用 unpack 函数把 `Bit#(n)` 类型转化为 Vector 。 Vector 中下标小的元素对应 `Bit#(n)` 中的低位,下标大的元素对应 `Bit#(n)` 中的高位: -```verilog +```bsv Vector#(4, UInt#(4)) vec2; vec2[0] = 1; vec2[1] = 2; @@ -4877,14 +4877,14 @@ Vector#(4, UInt#(4)) vec3 = unpack('h5678); // 得到: vec3 的四个元素 你可以用 `replicate` 函数创建一个元素全都为某个值的 Vector : -```verilog +```bsv Vector#(4, Int#(16)) vec0 = replicate(0); // 每个元素都是 0 Vector#(10, Bit#(10)) vec1 = replicate(7); // 每个元素都是 7 ``` 你还可用 `map` 和 `genVector` 函数创建一个元素从0开始值递增的数组: -```verilog +```bsv Vector#(5, Int#(16)) vec2 = map( fromInteger, genVector ); // 相当于: // vec2[0] = 0; @@ -4902,14 +4902,14 @@ Vector#(5, Int#(16)) vec2 = map( fromInteger, genVector ); 再比如,用 `map` 函数和自定义的平方函数,你可以创建每个元素为自身下标的平方的 Vector : -```verilog +```bsv function Integer square(Integer x) = x * x; // 自定义的平方函数 Vector#(64, int) vec3 = map( fromInteger, map( square, genVector ) ); // 调用两次map ,构建平方数 Vector ``` `reverse` 函数可以把元素顺序颠倒。`rotate` 函数可以把元素沿着下标降低的顺序移动1下,比如 -```verilog +```bsv Vector#(4, int) vec4 = map( fromInteger, genVector ); // vec4: 0,1,2,3 Vector#(4, int) vec5 = reverse( vec4 ); // vec5: 3,2,1,0 Vector#(4, int) vec6 = rotate( vec4 ); // vec6: 1,2,3,0 @@ -4921,7 +4921,7 @@ Vector#(4, int) vec6 = rotate( vec4 ); // vec6: 1,2,3,0 以下例子创建了二维的 Vector ,名为 vec34,并给部分数据赋值: -```verilog +```bsv Vector#(3, Vector#(4, int)) vec34; // 二维Vector,第1维下标范围为 0~2 , 第2维下标范围为 0~3 vec34[0][0] = 3; vec34[1][2] = -9; @@ -4937,7 +4937,7 @@ Vector#(4, int) tmp = vec34[1]; 就像数组那样,Vector 的元素类型也可以是寄存器接口,以此来构成寄存器阵列。以下代码定义并实例化了长度为 8 的寄存器向量,每个寄存器接口类型都是 `Reg#(int)` ,并被 `mkReg(1)` 实例化。 -```verilog +```bsv Vector#(8, Reg#(int)) reg_vec <- replicateM( mkReg(1) ); // 8 个独立的寄存器 ``` @@ -4945,7 +4945,7 @@ Vector#(8, Reg#(int)) reg_vec <- replicateM( mkReg(1) ); // 8 个独立的寄 这8个寄存器是独立的,在一个 rule 内,可以全部写入,也可以写入部分几个或一个: -```verilog +```bsv rule test; reg_vec[1] <= 10; reg_vec[3] <= reg_vec[6] + 2; @@ -4954,7 +4954,7 @@ endrule 也可以用一个运行时的变量(比如来自另一个寄存器)作为可变地址来访问。 -```verilog +```bsv Reg#(UInt#(3)) addr <- mkReg(0); rule test; reg_vec[addr] <= reg_vec[addr+1] + 1; @@ -4963,7 +4963,7 @@ endrule 除了寄存器,你还可以创建其它硬件模块的数组,比如 Wire 、 FIFO 、 BRAM 的数组。你可以定义 FIFO Vector 如下: -```verilog +```bsv Vector#(5, FIFO#(int)) fifo_vec <- replicateM( mkFIFO ); // 5 个独立的 FIFO ``` @@ -4971,7 +4971,7 @@ Vector#(5, FIFO#(int)) fifo_vec <- replicateM( mkFIFO ); // 5 个独立的 FI 需要区分 **寄存器 Vector** 和 **存放 Vector 的寄存器** 的概念,比如以下: -```verilog +```bsv Vector#(8, Reg#(int)) reg_vec <- replicateM(mkReg(1)); // 寄存器 Vector ,注意这里使用的是 replicateM Reg#(Vector#(8, int)) vec_in_reg <- mkReg(replicate(1)); // 存放 Vector 的寄存器,注意这里使用的是 replicate ``` @@ -4980,7 +4980,7 @@ Reg#(Vector#(8, int)) vec_in_reg <- mkReg(replicate(1)); // 存放 Vector 的 而 `vec_in_reg` 中的 8 个元素只能同时写入,即使你想实现部分写入的效果,也要把整个 `Vector#(8, int)` 类型的变量读出来,修改后,再整体写回去。比如: -```verilog +```bsv rule test; // vec_in_reg 的类型是 Reg#(Vector#(8, int)) Vector#(8, int) vec = vec_in_reg; // 整体读出来 vec[4] = vec[5] + 1; // 修改 @@ -4996,7 +4996,7 @@ endrule 最简单的定义新类型如下,它把 32 bit 的无符号数定义为 Uint : -```verilog +```bsv typedef UInt#(32) Uint; ``` @@ -5010,7 +5010,7 @@ typedef UInt#(32) Uint; 枚举类型的变量只能取规定的标签。比如红绿灯类型只能取 Red, Green, Yellow ,则可以定义枚举类型: -```verilog +```bsv typedef enum {Green, Yellow, Red} Light deriving(Eq, Bits); ``` @@ -5018,7 +5018,7 @@ typedef enum {Green, Yellow, Red} Light deriving(Eq, Bits); 然后就可以定义该枚举类型的变量,并且给它赋值: -```verilog +```bsv // 代码路径:src/18.EnumTest/EnumTest.bsv (部分) Light va = Green; // 定义变量 va $display("%b", va); // 打印出 00 ,说明 Green 被编码为 2'b00 @@ -5032,14 +5032,14 @@ $display("%b", va); // 打印出 10 ,说明 Yellow 被编码为 2'b10 这种编码规则决定了枚举变量会被 pack 函数转化为怎样的 `Bit#(n)` 类型。比如: -```verilog +```bsv Light vb = Red; let vb_bits = pack(vb); // vb_bits 是 Bit#(2) 类型的 2'b10 ``` 可以指定一部分标签的编码,而未指定编码的标签则顺延它左边的标签的编码递增: -```verilog +```bsv typedef enum {Green, Yellow=5, Red} Light deriving(Eq, Bits); // Green 编码为 0, Yellow 为 5 , Red 为 6 // 如此定义的 Light 占 3 bit,因为 3 bit足够表示 0, 5 和 6 @@ -5047,16 +5047,16 @@ typedef enum {Green, Yellow=5, Red} Light deriving(Eq, Bits); 当然,也可以给所有标签指定编码: -```verilog +```bsv typedef enum {Green=125, Yellow=20, Red=85} Light deriving(Eq, Bits); // 如此定义的 Light 占 7 bit,因为 7 bit足够表示 125, 20 和 85 ``` :pushpin: 注意一个坑!:因为枚举变量不能取它未定义的编码值,如果把未定义的 `Bit#(n)` 类型的编码值用 `unpack()` 强行赋值给枚举变量,会导致不可预测的变化,因此要避免这种情况的出现。举例: -```verilog +```bsv typedef enum {Green=125, Yellow=20, Red=85} Light deriving(Eq, Bits); -Light va = unpack(0); // 行为不确定!! 因为编码 0 在 Light 类型中不存在 。pack(va) 甚至不再是 7'b0 !! +Light va = unpack(0); // 行为不确定!! 因为编码 0 在 Light 类型中不存在 。pack(va) 甚至不再是 7'b0 !! Light vb = unpack(20); // 行为确定,因为编码 20 在 Light 类型中对应 Yellow ,会得到 vb = Yellow ``` @@ -5067,7 +5067,7 @@ Light vb = unpack(20); // 行为确定,因为编码 20 在 Light 类型中 例如,RISC-V RV32I CPU 的 7 bit opcode 规定了 9 类指令,可以定义为以下枚举类型: -```verilog +```bsv typedef enum { AUIPC = 7'b0010111, // AUIPC 类的指令的 opcode 的编码是 7'b0010111 LUI = 7'b0110111, // LUI 类的指令的 opcode 的编码是 7'b0110111 JAL = 7'b1101111, // ... 以此类推 @@ -5082,7 +5082,7 @@ typedef enum { AUIPC = 7'b0010111, // AUIPC 类的指令的 opcode 的编码 然后就可以把指令 `instruction` 中的这 7 bit 用 `unpack()` 赋值给 `OpCode` 类型的变量,然后就可以优雅地根据指令类型干一些事: -```verilog +```bsv // 设 instruction 是 Bit#(32) 类型的,取到的指令 Opcode opcode = unpack( instruction[6:0] ); if(opcode == BRANCH) @@ -5108,7 +5108,7 @@ else if(opcode == LOAD) 以下代码定义了两个结构体: -```verilog +```bsv typedef struct { // 一个二维坐标结构体,类型名为 Coord ,具有两个成员变量 x 和 y int x; int y; @@ -5132,11 +5132,11 @@ typedef struct{ // 一个像素结构体,嵌套了 Vector 和 你可以定义结构体变量,并且用如下的结构体构造方法为它赋值: -```verilog +```bsv Pixel pixel; pixel = Pixel{ // 构造结构体的语法 - color:replicate('hFF), // color[0]='hFF color[1]='hFF color[2]='hFF + color:replicate('hFF), // color[0]='hFF color[1]='hFF color[2]='hFF transparent: False, // transparent=False coord: Coord{x:1, y:2} // coord.x=1 coord.y=2 }; @@ -5144,9 +5144,9 @@ pixel = Pixel{ // 构造结构体的语法 当然,如果是在定义时赋值,因为知道右值的类型为 Pixel ,可以用 let 语句: -```verilog +```bsv let pixel = Pixel{ - color:replicate('hFF), // color[0]='hFF color[1]='hFF color[2]='hFF + color:replicate('hFF), // color[0]='hFF color[1]='hFF color[2]='hFF transparent: False, // transparent=False coord: Coord{x:1, y:2} // coord.x=1 coord.y=2 }; @@ -5154,7 +5154,7 @@ let pixel = Pixel{ 还可以用以下语句为成员变量单独赋值: -```verilog +```bsv pixel.color = replicate('hAB); pixel.transparent = True; pixel.coord = Coord{x:1, y:2}; @@ -5167,7 +5167,7 @@ pixel.coord.x = 8; 标签联合体 (union tagged) 是由成员变量组成的复合类型,区别于结构体,标签联合体每次只能取一种成员变量的值。比如,以下定义一个标签联合体名为 Pixel ,它要么取无效 None ,要么取黑白 16 bit Alpha ,要么取彩色 RGB (用一个嵌套的结构体来RGB)。 -```verilog +```bsv // 代码路径:src/19.UnionTaggedTest/UnionTaggedTest.bsv (部分) typedef union tagged { void None; // 要么取无效 (注意要首字母大写) @@ -5194,7 +5194,7 @@ RGB: | 10 | red | green | blue | 以下代码定义了三个 Pixel 类型的变量: -```verilog +```bsv // 代码路径:src/19.UnionTaggedTest/UnionTaggedTest.bsv (部分) Pixel pixel1 = tagged None; // 标签为 None Pixel pixel2 = tagged Alpha 100; // 标签为 Alpha ,取值为 100 @@ -5203,7 +5203,7 @@ Pixel pixel3 = tagged RGB {r:6, g:2, b:9}; // 标签为 RGB ,取值为 r=6, 可以在 if 中用 matches 语句匹配一个 pixel 是哪种标签,并且得到它的取值: -```verilog +```bsv // 代码路径:src/19.UnionTaggedTest/UnionTaggedTest.bsv (部分) rule test; if ( pixel1 matches tagged Alpha .alpha ) // 如果是 Alpha 标签,取值放在变量 alpha 里(UInt#(16)) @@ -5217,7 +5217,7 @@ endrule 或者也可以用 case matches 语句: -```verilog +```bsv // 代码路径:src/19.UnionTaggedTest/UnionTaggedTest.bsv (部分) rule test; case (pixel3) matches @@ -5230,7 +5230,7 @@ endrule 我们在 5.8 节学过的 Maybe 类型实际上是 BSV 预定义的一种标签联合体: -```verilog +```bsv typedef union tagged { void Invalid; // 取该标签时,无效 data_t Valid; // 取该标签时,有效 @@ -5250,7 +5250,7 @@ BSV 具有 case 语句和 case 表达式: 以下 case 语句根据变量 x 的取值,执行语句来给变量 y 赋值。 -```verilog +```bsv // 代码路径:src/20.CaseTest/CaseTest.bsv (部分) Bit#(4) x = 'b1110; int y; @@ -5267,7 +5267,7 @@ endcase 如果 `case` 匹配后不止一个表达式,比如还要给另一个变量 z 赋值,则需要用 `begin...end` 结构: -```verilog +```bsv case(x) 'b0000 : begin y = -87; // 如果 x='b0000 ,则执行 x=-87 @@ -5280,7 +5280,7 @@ case(x) 以下 `case...endcase` 构成一个表达式。对于每一个匹配值,使用 return 语句返回一个值,作为整个 case 表达式的取值。 -```verilog +```bsv // 代码路径:src/20.CaseTest/CaseTest.bsv (部分) Bit#(4) x = 'b1110; int y; @@ -5306,7 +5306,7 @@ y= case(x) 因此,使用模糊匹配如下。注意 `?` 代表 dont-care ,即 `?` 可以匹配 0 也可以匹配 1 。 -```verilog +```bsv // 代码路径:src/20.CaseTest/CaseTest.bsv (部分) y = case(x) matches 'b000? : return -87; @@ -5350,7 +5350,7 @@ endfunction 编译器会在函数调用时再推断出 td 具体应该是什么类型,比如: -```verilog +```bsv Bool a = True; int b = 114514; let c = do_nothing(a); // 推断出 c 为 Bool @@ -5361,7 +5361,7 @@ let d = do_nothing(b); // 推断出 d 为 int 以下代码实现一个多态函数来判断两个参数是否相等,两个参数使用同样的类型变量 td 。另外,需要用 provisos (**补充要求**)关键字要求 td 是派生自 Eq 类型类的,这样才能使用 == 判断是否相等。 -```verilog +```bsv // 代码路径:src/21.PolyFunc/EqualFunc.bsv (部分) function Bool equal( td i, td j ) provisos( Eq#(td) ); // 派生要求:要求 td 派生自 Eq 。 provisos 本身构成了函数定义的一部分,不能省略 @@ -5373,12 +5373,12 @@ endfunction 以下 bit_equal 函数就有点意义了,它判断两个参数在编码层面是否相等。它允许两个参数具有不同的类型 td1 和 td2 ,把它们用 pack 函数转化成 Bit#() 类型后,再比较是否相等。 注意到 `provisos` 中有: -- **派生要求**:要求 td1 派生自 Bits 类型类,并获取它的位宽 sz1 -- **派生要求**:要求 td2 派生自 Bits 类型类,并获取它的位宽 sz2 +- **派生要求**:要求 td1 派生自 Bits 类型类,并获取它的位宽 sz1 +- **派生要求**:要求 td2 派生自 Bits 类型类,并获取它的位宽 sz2 - **关系要求**:用关系要求 `Add#(sz1, 0, sz2)` 要求 sz1+0==sz2 ,也即 sz1 == sz2 - **数值类型** :sz1 和 sz2 都是**数值类型**,是一种用来构造类型和接口的整数类型,比如 `Bit#(n)` 和 `Vector#(n, td)` 中的 n 都是数值类型。 -```verilog +```bsv // 代码路径:src/21.PolyFunc/EqualFunc.bsv (部分) function Bool bit_equal( td1 i, td2 j ) provisos( @@ -5400,7 +5400,7 @@ Bool eq = bit_equal(a, b); // 允许比较两个不同的类型 a 和 b , 比如,以下 bit_ext_equal 函数判断两个参数在编码层面是否相等,如果两个参数位宽不一,就把短位宽的变量进行零扩展(高位补零)再比较。在函数体内,我们定义临时变量 bi 和 bj 的位宽为 `TMax#(sz1,sz2)` ,也就是 sz1 和 sz2 的最大值。这里的 `TMax#()` 就是一个**数值函数**。 -```verilog +```bsv // 代码路径:src/21.PolyFunc/EqualFunc.bsv (部分) function Bool bit_ext_equal( td1 i, td2 j ) provisos( @@ -5415,7 +5415,7 @@ endfunction 或者,你还可以用**伪函数** SizeOf 来获取一个类型的位宽,进而用来在函数体里定义临时变量。以上代码可以写成另一个版本: -```verilog +```bsv // 代码路径:src/21.PolyFunc/EqualFunc.bsv (部分) function Bool bit_ext_equal_v2( td1 i, td2 j ) provisos( @@ -5513,7 +5513,7 @@ endfunction `valueOf()` 伪函数也非常常用,比如在对多态的 `Vector#(len, td)` 操作时(len是Vector长度,是数值类型),要写一个循环来遍历每个元素,就需要用 `valueOf(len)` 获得 Integer 类型的 Vector 长度,然后用 Integer 变量作为 for 循环变量即可,比如: -```verilog +```bsv function td vectorSum(Vector#(len, td) vec) for(Integer i=0; i=readTimes ? 0 : rcnt + 1; // 移动读计数 if( rcnt+1>=readTimes ) // 如果读计数+1=读次数 rblock <= rblock + 1; // 读块号+1, 即去读下一块 - + return tuple2( // 构造 tuple2 作为返回值 rcnt, // 读计数 regVector2Vector( buffer[ rblock[0] ] ) // 从缓冲区读取的块 @@ -5842,18 +5842,18 @@ module mkDoubleBuffer#( UInt#(32) readTimes ) ( DoubleBuffer#(n, td) ) endmethod ``` -以上,模块 `mkDoubleBuffer` 就设计完了 。 +以上,模块 `mkDoubleBuffer` 就设计完了 。 实例化 `mkDoubleBuffer` 的方法举例如下: -```verilog +```bsv DoubleBuffer#(5, UInt#(16)) doublebuffer <- mkDoubleBuffer(3); // n=5, 数据元素类型是UInt#(16), readTimes=3 ``` 最后,编写 testbench 如下: -```verilog +```bsv // 代码路径:src/23.DoubleBuffer/DoubleBuffer.bsv (部分) module mkTb (); // 时钟周期计数器 cnt ------------------------------------------------------------------------ @@ -5926,13 +5926,13 @@ StmtFSM 是 BSV 提供的一个非常实用的包,用来以顺序执行的结 使用前要先引入: -```verilog +```bsv import StmtFSM::*; ``` 以下我们用 Stmt 类型描述一个执行流程,也就是状态机的行为描述。用 `seq ... endseq` 描述一个顺序执行的结构,其中每一句都会对应状态机的一个状态。 -```verilog +```bsv // 行为描述 Stmt test = seq @@ -5947,14 +5947,14 @@ Stmt test = 然后,用以上行为描述构建一个状态机模块 mkFSM : -```verilog +```bsv // 构建状态机 FSM testFSM <- mkFSM( test ); ``` 通常我们把行为描述和构建状态机写在一起: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMTest.bsv (部分) // 行为描述 + 构建状态机 FSM testFSM <- mkFSM( @@ -5967,7 +5967,7 @@ FSM testFSM <- mkFSM( mkFSM 模块的接口为 FSM ,我们可以看到它有哪些方法: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMTest.bsv (部分) // 对于模块 mkFSM , 它的接口: interface FSM; @@ -5980,7 +5980,7 @@ endinterface: FSM 所以,我们可以这样控制状态机的运行: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMTest.bsv (部分) rule r1; // 效果:一旦状态机空闲,就启动它 (这样状态机每次运行完就只空闲一个周期) testFSM.start; // 隐式条件:状态机空闲 @@ -6002,7 +6002,7 @@ endrule mkAutoFSM 模块没有接口,不需要调用 start 方法就直接开始运行,且运行完后自动运行 `$finish;` 因此 mkAutoFSM 往往用来构建 testbench 流程。比如: -```verilog +```bsv // 代码路径:src/24.FSMTest/AutoFSMTest.bsv (部分) // 行为描述 + 构建状态机 mkAutoFSM( seq @@ -6027,7 +6027,7 @@ mkAutoFSM 模块没有接口,不需要调用 start 方法就直接开始运行 比如,以下状态机中,我们不仅写 fifo ,还嵌套调用了另一个状态机。 -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures.bsv (部分) // 设 fifo 是个 mkFIFO , sfsm 是另一个 mkFSM mkFSM mfsm <- mkFSM( seq @@ -6046,12 +6046,12 @@ mkAutoFSM 模块没有接口,不需要调用 start 方法就直接开始运行 `await` 是 StmtFSM 包中的一个函数,它会根据一个 Bool 类型创作一个具有隐式条件的动作,只有 Bool 类型 = True 时,该动作才会执行。在 FSM 中,它可以起到 “等待某个 Bool 变量为 True 为止” 的效果。比如以上代码中 `sfsm.waitTillDone` 也可以写成: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures.bsv (部分) mkFSM mfsm <- mkFSM( seq // ... await( sfsm.done ) // 直到 sfsm 空闲才能跳到下一个周期,可能占多个周期 - // 等效于 sfsm.waitTillDone; + // 等效于 sfsm.waitTillDone; // ... endseq ); ``` @@ -6060,9 +6060,9 @@ mkAutoFSM 模块没有接口,不需要调用 start 方法就直接开始运行 `delay(n)` 是 StmtFSM 包中的一个函数,允许在状态机中延迟 n 个周期,再跳转到下一个状态。比如: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures.bsv (部分) - mkFSM mfsm <- mkFSM( seq + mkFSM mfsm <- mkFSM( seq $display("fsm started"); // 状态1:占1个周期 delay(100); // 状态2:占100个周期 $display("delay done"); // 状态3:占1个周期 @@ -6075,9 +6075,9 @@ action 可以把多个动作语句合并成一个动作。 如果想在同一周期执行多个动作,就把它们放在 `action...endaction` 中。比如,想要在一个周期内同时写两个寄存器 regx 和 regy ,同时还要打印一个信息,则可以: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures.bsv (部分) - mkFSM mfsm <- mkFSM( seq + mkFSM mfsm <- mkFSM( seq // ... action // 一个 action 只占一个状态,在同一周期执行 regx <= regy; @@ -6090,9 +6090,9 @@ action 可以把多个动作语句合并成一个动作。 `action...endaction` 中的语句也能有隐式条件,当具有多个隐式条件时,只有在所有隐式条件都满足时,`action...endaction` 才会执行。比如,我们想等到 fifo 能 enq,且子状态机 sfsm 能运行时,同时进行 fifo.enq 和 sfsm.start ,可以这么写: -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures.bsv (部分) - mkFSM mfsm <- mkFSM( seq + mkFSM mfsm <- mkFSM( seq // ... action // 只有在所有状态都满足时,才一并执行 action 中的所有语句 fifo.enq(53); @@ -6115,8 +6115,8 @@ action 可以把多个动作语句合并成一个动作。 `repeat(n) ...` 可以把一个结构重复执行 n 次,比如可以把一个 seq 顺序结构重复三次: -```verilog - mkFSM mfsm <- mkFSM( seq +```bsv + mkFSM mfsm <- mkFSM( seq // ... repeat(3) seq $display("cnt=[%3d] repeat", cnt); @@ -6128,8 +6128,8 @@ action 可以把多个动作语句合并成一个动作。 或者把一个 action 重复四次: -``` - mkFSM mfsm <- mkFSM( seq +```bsv + mkFSM mfsm <- mkFSM( seq // ... repeat(4) action $display("cnt=[%3d] repeat", cnt); @@ -6145,7 +6145,7 @@ action 可以把多个动作语句合并成一个动作。 以下例子创建了三个线程,我们观察到三个线程的 `$display()` 是乱序执行的(因为 `fifo.enq` 花费的时间不固定),这说明线程间是互相独立地执行的。 -```verilog +```bsv // 代码路径:src/24.FSMTest/FSMStructures2.bsv (部分) FSM mfsm <- mkFSM( seq par @@ -6182,7 +6182,7 @@ action 可以把多个动作语句合并成一个动作。 以下代码描述了一个含分支结构的状态机: -```verilog +```bsv FSM mfsm <- mkFSM( seq // ... if(cnt%3 == 0) seq // 该分支消耗1个周期 @@ -6216,7 +6216,7 @@ action 可以把多个动作语句合并成一个动作。 以下是一个例子: -``` +```bsv FSM mfsm <- mkFSM( seq // ... while(cnt % 5 != 0) seq @@ -6234,7 +6234,7 @@ action 可以把多个动作语句合并成一个动作。 `seq...endseq` 结构中出现的 for 循环: -```verilog +```bsv seq for( 语句A ; cond ; 语句B ) 循环体... endseq @@ -6242,7 +6242,7 @@ endseq 等效于: -```verilog +```bsv seq 语句A; while(cond) seq @@ -6258,7 +6258,7 @@ endseq `for` 循环通常用寄存器作为循环变量,来进行循环。比如,以下代码让寄存器 regx 从 0 开始循环到 regy-1 ,循环体内进行一些动作: -```verilog +```bsv // regx 和 regy 都是寄存器 FSM mfsm <- mkFSM( seq // ... @@ -6278,7 +6278,7 @@ endseq 在顺序结构 `seq..endseq` 中,不能定义和使用局部变量,因为局部变量并不能将自身的值保存到下周期,而顺序结构是按周期顺序执行的,在顺序结构中使用局部变量没有意义。比如: -```verilog +```bsv seq // int x=0; // 错误! // x = x + 1; // 错误! @@ -6289,12 +6289,12 @@ endseq 而在 `action...endaction` 中,则可以使用局部变量,因为 action 只在一个时钟周期执行,其中所有的局部变量的生命周期仅仅在当前时钟周期。 -```verilog +```bsv // 设 regx 是寄存器 seq action // 这样是可以的: - int x=0; - x = regx; + int x=0; + x = regx; for(int y=0; y<4; y=y+1) x = x + y; regx <= x; @@ -6321,7 +6321,7 @@ SPI 发送器 `mkSPIWriter` 的代码如下。实现思路是: - 状态机内,用 while 循环 8 次,每次拿出 wdata 中的一个 bit 赋给 mosi 信号。同时也要正确地控制 ss 和 sck 信号。 - 在 spi 方法里,直接把 ss, sck, mosi 三个信号引出。 -```verilog +```bsv // 代码路径:src/3.SPIWriter/SPIWriter.bsv (部分) module mkSPIWriter (SPIWriter); // BSV SPI 发送(可综合!!), 模块名称为 mkSPIWriter Reg#(bit) ss <- mkReg(1'b1); @@ -6362,7 +6362,7 @@ endmodule - 因为 `spi_writer.write` 方法具有隐式条件,所以每条 `spi_writer.write` 会等到条件允许(也就是上次发送结束)后再执行。 - `mkAutoFSM` 执行结束后会自动 `$finish;` ,它不会等 `8'h00` 数据发送完,因此我们仿真时看不到 `8'h00` 的发送过程,只能看到前两个字节的发送过程。 -```verilog +```bsv // 代码路径:src/3.SPIWriter/TbSPIWriter.bsv (部分) module mkTb (); let spi_writer <- mkSPIWriter; @@ -6453,7 +6453,7 @@ endmodule `mkBitCoder`它的接口直接借用了 `FIFO#(Bit#(8))` ,而不是新定义的接口,它有以下四个方法: -```verilog +```bsv interface FIFO#( Bit#(8) ); method Action enq (Bit#(8) x); // 动作方法 , 压入数据 method Action deq; // 动作方法 , 弹出数据 @@ -6466,7 +6466,7 @@ endinterface: FIFO 打开生成的 Verilog 文件 `mkBitCoder.v` ,看到模块输入输出信号如下,BSV 还帮我们生成了相关的描述注释: -```verilog +```bsv // Ports: // 笔者注释:关注以下输入输出描述 // Name I/O size props // RDY_enq O 1 reg @@ -6524,13 +6524,13 @@ module mkBitCoder(CLK, 对于动作方法 enq : - output RDY_enq : 代表当前时钟周期 enq 的隐式条件是否满足,即模块是否接受外界输入 enq_1 数据 -- input EN_enq : 代表当前时钟周期外界是否想输入 enq_1 数据。只允许在 RDY_enq=1 的周期=1 +- input EN_enq : 代表当前时钟周期外界是否想输入 enq_1 数据。只允许在 RDY_enq=1 的周期=1 - input [7 : 0] enq_1 : 是 8bit 的输入数据,每当 RDY_enq=EN_enq=1 (握手成功)时输入一次 对于动作方法 deq : - output RDY_deq : 代表当前时钟周期 deq 的隐式条件是否满足,即模块是否有可弹出的数据 -- input EN_deq : 代表外界是否想弹出数据。只允许在 RDY_deq=1 的周期=1 +- input EN_deq : 代表外界是否想弹出数据。只允许在 RDY_deq=1 的周期=1 - 因为没有参数所以没有输入信号 对于值方法 first : @@ -6550,9 +6550,9 @@ module mkBitCoder(CLK, 为了印证以上分析,打开 `.vcd` 波形文件查看这些信号。如**图12**,可以看出: -- 调用者积极地调用 enq 方法:即只要 RDY_enq=1 ,就让 EN_enq=1 +- 调用者积极地调用 enq 方法:即只要 RDY_enq=1 ,就让 EN_enq=1 - 只要 RDY_enq=EN_enq=1 (握手成功),就令输入数据 enq_1 更新为下一个待输入数据 -- 调用者积极地调用 deq 方法:即只要 RDY_deq=1 ,就让 EN_deq=1 +- 调用者积极地调用 deq 方法:即只要 RDY_deq=1 ,就让 EN_deq=1 - 每当 RDY_deq=EN_deq=1 (握手成功),直到下次 RDY_deq=1 ,输出数据 first 会更新,这是因为成功弹出了上一个输出数据 - RDY_deq 与 RDY_first 永远相等,因为 deq 与 first 隐式条件相同。在编写 Verilog 时任选一个来用即可。 @@ -6604,7 +6604,7 @@ module mkBitCoder(CLK, 比如 `src/13.BitCoding/BitCoding_v5.bsv` 中的 `mkBitCoder` 模块的 `clear` 方法没有隐式条件,我们在 `mkBitCoder` 模块的定义前面加上如下 `always_ready` 属性: -```verilog +```bsv (* synthesize *) (* always_ready="clear" *) // 用 always_ready 来删除 clear 方法的 RDY 信号 module mkBitCoder ( FIFO#(Bit#(8)) ); @@ -6613,7 +6613,7 @@ module mkBitCoder ( FIFO#(Bit#(8)) ); 也可以和 `synthesize` 属性写在一起: -```verilog +```bsv (* synthesize, always_ready="clear" *) ``` @@ -6627,7 +6627,7 @@ module mkBitCoder ( FIFO#(Bit#(8)) ); 比如给模块 `mkModule` 的 `methodA` 加上 `always_enabled` ,写作: -```verilog +```bsv (* synthesize *) (* always_enabled="methodA" *) // 用 always_enabled 来删除 methodA 方法的 EN 和 RDY 信号 module mkModule ... @@ -6635,7 +6635,7 @@ module mkModule ... 也可以和 `synthesize` 属性写在一起: -```verilog +```bsv (* synthesize, always_enabled="methodA" *) ``` @@ -6669,7 +6669,7 @@ Verilog 代码库的路径包括: 例如:`src/13.BitCoding/BitCoding_v5.bsv` 生成的代码文件 `mkBitCoder.v` 里,搜索 "submodule" 关键字可以搜到: -```verilog +```bsv // submodule fifo1 FIFO2 #(.width(32'd8), .guarded(32'd1)) fifo1(.RST(RST_N), .CLK(CLK), @@ -6710,7 +6710,7 @@ Verilog 代码库的路径包括: 运行命令生成仿真波形: -``` +```bash bsvbuild.sh -vw mkTb SPIFlashController.bsv ```