From 8733e272976c714628044b94cb6d933be3e83ace Mon Sep 17 00:00:00 2001 From: WangXuan95 <629708558@qq.com> Date: Tue, 28 Mar 2023 17:18:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E8=AE=A2=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 272 +++++++++++++++++++++++++++++------------------------- 1 file changed, 148 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 590fcba..7653a1d 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,13 @@ BSV 中文教程 ![图1](./readme_image/0.bluespec.png) -同步更新至: +当前版本 2023/3/28 。同步更新至: - GitHub : https://github.com/WangXuan95/BSV_Tutorial_cn - Gitee : https://gitee.com/wangxuan95/BSV_Tutorial_cn +  + # 1 前言 ## 为什么要 BSV?Verilog 不好用? @@ -68,11 +70,9 @@ BSV生成的Verilog和手写Verilog相比,资源量和时钟频率不差多少 [1] R. Nikhil and K. Czeck. BSV by Example: The next-generation language for electronic system design. Bluespec, Inc. 2010. http://csg.csail.mit.edu/6.S078/6_S078_2012_www/resources/bsv_by_example.pdf. -[2] Bluespec SystemVerilog Reference Guide. Bluespec, Inc. 2017. https://web.ece.ucsb.edu/its/bluespec/doc/BSV/reference-guide.pdf. - -- [2] 是内容最全 BSV 的官方资料,可作为查阅文档。 - +[2] Bluespec SystemVerilog Reference Guide. Bluespec, Inc. 2017. https://web.ece.ucsb.edu/its/bluespec/doc/BSV/reference-guide.pdf. (是内容最全 BSV 的官方资料,可作为查阅文档) +  ## 目录 :page_with_curl: @@ -170,7 +170,9 @@ BSV生成的Verilog和手写Verilog相比,资源量和时钟频率不差多少 - [13.2 RISC-V 流水线 CPU](#head187) - [13.3 JPEG 图像压缩器](#head190) +  +  # 2 BSV概览 @@ -326,7 +328,7 @@ Chisel 和 SpinalHDL 也可以归类为高级硬件描述语言(HL-HDL)。 > > :point_right: BSV 的语法是专门针对硬件设计而设计的,不依附于任何现有语言。不像 Chisel 和 SpinalHDL 是基于现有语言 Scala 的,可能存在一些只适合软件设计思路的干扰性语法。 - +  ## 2.3 BSV vs. HLS @@ -336,7 +338,7 @@ Chisel 和 SpinalHDL 也可以归类为高级硬件描述语言(HL-HDL)。 > :point_right: 基于 C/SystemC 的 HLS 被许多业内人视为发展方向。不幸的是,这存在一定的问题。众所周知,高性能软件的关键是好的算法,好的算法是由算法工程师设计的,而不是由自动工具设计的(编译器只会优化我们编写的算法,而不会提升算法本身)。同样,面积小、时序好的硬件也是由硬件工程师设计的。因此,至关重要的是要赋予硬件工程师最大的表达架构的能力。HLS 却恰恰相反——它掩盖了架构,而是尝试用启发式方法选择架构。设计人员对此活动只有间接的控制能力,例如约束硬件资源限制和指定优化方法(unrolling, peeling, fusion 等),很难(常常涉及猜测)将产生一个好的架构。 - +  ## 2.4 总结 @@ -346,7 +348,9 @@ Chisel 和 SpinalHDL 也可以归类为高级硬件描述语言(HL-HDL)。 | :---: | | **图2**:比较 Verilog, VHDL, Chisel, HLS 与 BSV | +  +  # 3 准备工作 @@ -362,7 +366,7 @@ Chisel 和 SpinalHDL 也可以归类为高级硬件描述语言(HL-HDL)。 > :point_right: 提示:开启 WSL 后,在 Windows 的某个目录(文件夹)下打开 WSL 命令行的方式是:在”文件资源管理器“空白处摁住shift+右键 → ”在此处打开 PowerShell 窗口“ → 在 PowerShell 中输入 wsl + 回车 → 即可进入 Linux 环境。 - +  ## 3.1 安装 bsc 编译器 @@ -413,7 +417,7 @@ $ bsc 如果笔者提供的 bsc 在你的 Linux 上不能工作,请前往[Bluespec官方bsc仓库](https://github.com/B-Lang-org/bsc),自行按照 README 的指示编译 bsc 编译器。注:WSL 下编译 bsc 可能面临各种依赖问题,因此建议用 Linux 实体机或虚拟机编译 bsc 。 - +  ## 3.2 安装 iverilog 和 Tcl @@ -437,7 +441,7 @@ Usage: iverilog [-ESvV] [-B base] [-c cmdfile|-f cmdfile] [-W class] [-y dir] [-Y suf] source_file(s) ``` - +  ## 3.3 安装 gtkwave @@ -461,7 +465,7 @@ $ gtkwave wave.vcd #今后用该命令查看波形文件 wave.vcd 你不能在 WSL 中安装 gtkwave,因为 gtkwave 是一个图形界面 (GUI),而 WSL 是没有 GUI 的。替代办法就是直接在 Windows 上安装 gtkwave。请前往 [**gtkwave官网** ](http://gtkwave.sourceforge.net/)下载 ZIP 压缩包,把它解压到你想安装的目录下,找到其中的 `gtkwave/bin` 目录里面的 **gtkwave.exe** ,运行它,如果打开了一个窗口,则安装成功。 - +  ## 3.4 部署 bsvbuild.sh 脚本 @@ -485,7 +489,7 @@ $ bsvbuild.sh # 如果打印如下,说明 bsvbuild.sh 正常工作 第4章会通过例子展示 **bsvbuild.sh** 的使用方法。 - +  ## 3.5 找一款顺手的代码编辑器 @@ -499,7 +503,7 @@ BSV 的代码文件名后缀为 .bsv ,尽管用记事本都能编写,但没 启用该插件后,重启 vscode ,再打开的 .bsv 文件就有 BSV 的高亮。 - +  # 4 项目组织与构建 @@ -543,7 +547,7 @@ bsvbuild.sh - [] [] [] [4.2 单模块项目 @@ -620,7 +624,7 @@ $ bsvbuild.sh -bs mkTb Hello.bsv display.txt 当你需要大量仿真打印时,可以像这样指定一个仿真打印文件,而不是打印在屏幕上。 - +  ## 4.3 单包多模块项目 @@ -721,7 +725,7 @@ $ bsvbuild.sh -vs mkTb DecCounter.bsv 如果删除 `mkDecCounter` 上方的 `(* synthesis *)` 属性,则上述命令只会产生1个 Verilog 模块 `mkTb` ,而 `mkDecCounter` 则被嵌入 `mkTb` 中。这能帮助我们缩减 Verilog 模块的数量——当一个 BSV 模块过于复杂时,为了提升可读性,把它拆分成多个 BSV 模块来实现;当生成 Verilog 后,这些 BSV 模块只产生一个 Verilog 模块,作为黑箱使用或发布。 - +  ## 4.4 多包项目 @@ -740,7 +744,7 @@ $ bsvbuild.sh -bs mkTb TbSPIWriter.bsv > 注:规范的 Verilog 项目中,每个 .v 文件只能包含一个模块。而规范的 BSV 项目中,每个 .bsv 文件只能包含一个包,但每个包可以包含多个模块,这些模块往往共同实现某个功能。 - +  ## 4.5 生成与查看波形 @@ -790,7 +794,9 @@ $ gtkwave mkTb_vw.vcd # 只有有 GUI 的 Linux 实体机/虚拟机 能运 > :point_right: 调试 BSV 代码时,建议结合仿真打印(来自$display()等)和查看波形这两种方式。很多硬件工程师习惯只看波形,虽然波形涵盖海量的细节信息,但令人眼花缭乱。而 $display() 能帮你快速打印你想要的信息。该经验也适用于 Verilog 调试。 +  +  # 5 类型与变量 @@ -812,7 +818,7 @@ SPIWriter spi_writer <- mkSPIWriter; 后续我们会理解为什么接口也是类型。在 BSV 中**万物皆变量或类型** 。 - +  ## 5.1 类型类 @@ -880,7 +886,7 @@ BSV 中常用的类型转换函数如**表3**。注意 :如果代码中包含 | `signExtend` | `BitExtend` | 高位符号扩展,比如把 Int#(16) 扩展为 Int#(32) 。具体扩展为多少位,取决于 `=` 左值的类型。 | | `extend` | `BitExtend` | 高位扩展,根据类型自动选择采用 `zeroExtend` 还是 `signExtend` | - +  ## 5.2 基本数据类型 @@ -1068,7 +1074,7 @@ bit b6 = b4 & b5; // 按位与 所有类型的大小比较都会得到 `Bool` 类型,`if(cond)` 、 `while(cond)` 、`for(... ;cond ;...)` 中的条件表达式 `cond` 必须是 `Bool` 类型。 - +  ## 5.3 Integer 与 String 类型 @@ -1119,7 +1125,7 @@ endrule | `\"` | 双引号,ASCII 码是 0x22 | | `\xHH` | ASCII 码是 0xHH 的任意字符 | - +  ## 5.4 使用 $display 打印 @@ -1153,7 +1159,7 @@ $display("a=%d b=%3d c=%08x d=%x e=%b", a, b, c, d, e ); | `%nb` | 以二进制打印,占用 n 个字符,不够则用空格补齐 | `%8b` | | `%0nb` | 以二进制打印,占用 n 个字符,不够则用 '0' 补齐 | `%032b` | - +  ## 5.5 变量定义与赋值 @@ -1237,7 +1243,7 @@ ra = 4, rb = 4, rc = 8 ra = 5, rb = 5, rc = 10 ``` -> :pushpin: 注意:寄存器更新语句 `ra <= ra + 1` 中用到了写入符号 (`<=`),该符号不是赋值符号,是寄存器写入方法 `ra._write(ra + 1)` 的简写(后续5.1节会细讲),要与 `=` 和 `<-` 区分开。 +> :pushpin: 注意:寄存器更新语句 `ra <= ra + 1` 中用到了写入符号 (`<=`),该符号不是赋值符号,是寄存器写入方法 `ra._write(ra + 1)` 的简写,要与 `=` 和 `<-` 区分开 (后续6.1节会细讲)。 ### let 语句 @@ -1284,7 +1290,7 @@ module mkTb (); endmodule ``` - +  ## 5.6 组合逻辑电路 @@ -1464,7 +1470,7 @@ import GrayCode_v5::*; 如果需要函数具有“多个返回值”的效果,可以使用 Tuple 数据类型,将在 8.3 节讲到。 - +  ## 5.7 元组 Tuple @@ -1522,7 +1528,7 @@ Tuple2#(Bit#(8), Bit#(5)) tsplit = split(b13); match {.b8, .b5} = tsplit; // 得到 b8='b10111001 b5=01100 ``` - +  ## 5.8 Maybe 类型 @@ -1556,9 +1562,9 @@ let v2 = isValid(value2); // 得到 v2 是 Bool 类型的 True let d2 = fromMaybe(-99, value2); // 得到 d2 是 Int#(9) 类型的 42 ``` +  - - +  # 6 时序逻辑电路 @@ -1778,7 +1784,7 @@ Reg#( Tuple2#(UInt#(32), UInt#(32)) ) dregs [17]; // 这是一个寄存器接 > :triangular_flag_on_post: 目前我们没学模块定义和调用,所以该代码的实现和测试是放在同一个模块中的,没什么实际使用价值。8.2 节中我们将把它实现为模块,并使用 FIFO 给他加入反压 (back-pressure) 功能。 - +  ## 6.2 读写顺序与调度注解 @@ -1907,7 +1913,7 @@ test1 test2 ``` - +  ## 6.3 线网 Wire @@ -1994,7 +2000,7 @@ cnt= 4 w1= 4 r1= 2 - _read **CF** _read:两次 `_read` 之间不存在冲突,可以以任意的顺序排列。 - _read **SAR** _write:代表 `_read` 必须排到 `_write` 之后,保证 `_read` 到新值(与 `Reg` 的顺序正好相反)。且 `_read` 和 `_write` 不能放在同一个规则内。 - _write **SBR** _read:也就是上一条调度注解反过来。 -- _write **C** _write: 代表两次 `_write` 不能在同一周期执行,且不能放在规则内 。 +- _write **C** _write: 代表两次 `_write` 不能在同一周期执行,且不能放在同一规则内 。 ### mkWire @@ -2161,7 +2167,7 @@ cnt=5 w1_v=0 w1_d=0 w2_v=0 cnt=6 w1_v=1 w1_d=6 w2_v=1 ``` - +  ## 6.4 规则 rule @@ -2370,7 +2376,7 @@ Warning: "Test1.bsv", line 20, column 9: (G0021) 编译器是这样处理冲突的(无论是**资源冲突**还是**排序冲突**):冲突的多个规则永远不能同时激活。为了决定该激活哪个规则,编译器会为这些规则选择一个固定的紧急程度(或者由用户指定)。当冲突的规则在同一周期都激活时,紧急的规则激活;不紧急的规则不激活。 - +  ## 6.5 调度属性 @@ -2545,8 +2551,8 @@ cnt=6 x=4 y=5 分析如下: - 规则 `x2y` 和 `y2x` 有排序冲突。 -- 在前2个周期,`x<=y+1;` 被执行,这是因为规则 `y2x` 隐式条件 `w1._read` 被满足,且 `y2x` 紧急程度高于 `x2y` ,所以激活 `y2x` 而不是 `x2y` 。 -- 在 cnt>=2 的周期,`y<=x+1;` 被执行,这是因为隐式条件 `w1._read` 不再满足,无法激活 `y2x` ,转而激活与之冲突的 `x2y` 。 +- 在前2个周期,`x<=y+w1;` 被执行,这是因为规则 `y2x` 隐式条件 `w1._read` 被满足,且 `y2x` 紧急程度高于 `x2y` ,所以激活 `y2x` 而不是 `x2y` 。 +- 在 `cnt>=2` 的周期,`y<=x+1;` 被执行,这是因为隐式条件 `w1._read` 不再满足,无法激活 `y2x` ,转而激活与之冲突的 `x2y` 。 ### 不能用 if 语句影响冲突的判断 @@ -2958,7 +2964,7 @@ Error: "test.bsv", line 16, column 9: (G0005) **规则间顺序执行约束**:在同一个周期内,多个规则间在逻辑上是顺序执行的。 -结合**规则的原子性**和**规则间顺序执行约束**得出:一个规则的所有语句都执行完后,下一个规则才能执行。编译器会根据规则中的方法的调度注解来排列规则的执行顺序——哪个规则必须排在哪个之前,哪个两规则的顺序是无所谓的。 +结合**规则的原子性**和**规则间顺序执行约束**得出:一个规则的所有语句都执行完后,下一个规则才能执行。编译器会根据规则中的方法的调度注解来排列规则的执行顺序——哪个规则必须排在哪个之前,哪两个规则的顺序是无所谓的。 **排序冲突**:在某个周期内,对于所有显式条件和隐式条件都满足的规则,无法给出一个顺序,既满足**规则间顺序执行约束**,又满足各方法的调度注解,就发生排序冲突。 @@ -2973,7 +2979,7 @@ Error: "test.bsv", line 16, column 9: (G0005) `preempts` :给两个规则强制加上冲突(即使他们之间不冲突),同时隐含地指定了紧急程度。 - +  ## 6.6 并发寄存器 mkCReg @@ -2989,8 +2995,8 @@ Reg#(int) creg [3] <- mkCReg(3, 0); // Reg#(int) creg [3] 代表接口数组, `creg[0]`,`creg[1]`,`creg[2]` 代表该寄存器的第一个、第二个、第三个接口。在同一个时钟周期内,`creg` 行为如下: - `creg[0]._read` 可以获得上个周期存放在 `creg` 中的值; -- 如果执行了 `creg[0]._write(v1)` ,则本周期可以立即从 `creg[1]._read` 上读到新值 `v1` ,否侧 `creg[1]._read` 获得和 `creg[0]._read` 一样的值。 -- 如果执行了 `creg[1]._write(v2)` ,则本周期可以立即从 `creg[2]._read` 上读到新值 `v2` ,否侧 `creg[2]._read` 获得和 `creg[1]._read` 一样的值。 +- 如果执行了 `creg[0]._write(v1)` ,则本周期可以立即从 `creg[1]._read` 上读到新值 `v1` ,否则 `creg[1]._read` 获得和 `creg[0]._read` 一样的值。 +- 如果执行了 `creg[1]._write(v2)` ,则本周期可以立即从 `creg[2]._read` 上读到新值 `v2` ,否则 `creg[2]._read` 获得和 `creg[1]._read` 一样的值。 - 如果执行了 `creg[2]._write(v3)` ,则本周期结束后 `creg` 存放的值就是该新值 `v3` ;否则存放和 `creg[2]._read` 一样的值。 因此, `mkCReg` 具有如**表12**的调度注解。 @@ -3127,7 +3133,9 @@ cnt=33 creg=11 用`mkCReg` 我们可以优雅地实现固定优先级调度器,可以用 `creg[<最大下标>]` 来立即获得本周期的调度结果;或者用 `creg[0]` 延迟一周期获得。 +  +  # 7 模块与接口 @@ -3167,7 +3175,7 @@ endmodule 其中方法包括3种: - **值方法 (Value method)** :返回一个变量,不会改变被调用模块内的状态。一般用于获取模块数据(output)。 -- **动作方法 (Action method) **:接受一个或多个变量作为参数,会改变调用模块内的状态。一般用于给模块输入数据(input)。 +- **动作方法 (Action method)**:接受一个或多个变量作为参数,会改变调用模块内的状态。一般用于给模块输入数据(input)。 - **动作值方法 (ActionValue method)** :接受一个或多个变量作为参数,会改变调用模块内的状态,也会返回一个变量。 ### 模块定义语法 @@ -3215,7 +3223,7 @@ module mkModuleName #(parameter int param_value) (Ifc_Name); Ifc_Name instance_name <- mkModuleName(10); ``` - +  ## 7.2 值方法与动作方法 @@ -3276,7 +3284,7 @@ method int get; endmethod ``` - +  ### e.g. 比特编码器 v1 @@ -3427,7 +3435,7 @@ endmodule 以上 testbench 仿真打印的结果不做赘述,读者可自行检查。我们发现打印完最后一个输出数据是在第 514 个时钟周期,这是因为 512 个周期输入数据 + 2周期的流水线延迟。 - +  ## 7.3 方法的隐式条件 @@ -3526,7 +3534,7 @@ put--->| 如果put,最多产生10bit |------------->| 当前含有 drem_len 读者可自行运行 `src/13.BitCoding/BitCoding_v3.bsv` 看结果。我们会发现运行总时间增加到了 593 周期,这是因为 dout 修改为 8bit 位宽后构成了性能瓶颈。 - +  ## 7.4 动作值方法 @@ -3654,7 +3662,7 @@ endmodule 如果要在支持**输出反压**的要求下,同时支持高并发的流水线,目前仅靠我们学过的模块(Reg 和 Wire)写起来不太简洁。等第 8 章学习队列 (FIFO) 时,再给出一个优雅的实现。 - +  ## 7.5 方法实现的简写 @@ -3729,7 +3737,7 @@ endmodule //method int read = reg_data; // 这个不算简写,虽然也很方便 ``` - +  ## 7.6 使用现成的接口 @@ -3786,7 +3794,7 @@ endmodule `inc_reg` 用起来就和普通的寄存器一样,可以使用写入符号 `<=` 作为 `_write` 方法的简写;用接口名本身作为 `_read` 方法的简写。 - +  ## 7.7 接口嵌套 @@ -3848,7 +3856,7 @@ endmodule $display("%d", inc_reg.data); // 相当于 $display("%d", inc_reg.data._read); ``` - +  ## 7.8 用元组返回多个接口 @@ -3888,7 +3896,7 @@ match {.inc_reg_data, .inc_reg_step} <- mkIncreaseRegCfg; // 得到 inc_reg_data // 然后 inc_reg_data 和 inc_reg_step 用起来就和普通的寄存器 一样。 ``` - +  ## 7.9 动作函数 @@ -3923,7 +3931,9 @@ module mkTb (Empty); endmodule ``` +  +  # 8 存储与队列 @@ -3935,7 +3945,7 @@ endmodule - 小容量 FIFO:用来构建具有反压 (back-pressure) 或停顿 (stall) 能力的弹性流水线电路,**非常实用!!** - 大容量 FIFO:即 BRAMFIFO ,在输入和输出节奏不一致时,用来堆积大量数据。 - +  ## 8.1 BRAMCore @@ -4073,7 +4083,7 @@ BEEF BSV 生成的 Verilog 代码中,它们会分别使用的是 `$readmemb()` 和 `$readmemh()` 函数来载入文本文件。 - +  ## 8.2 BRAM @@ -4225,7 +4235,7 @@ BRAM 具有一个读数据和写响应的缓存队列(一般我们不用写响 注意:以上叙述都是指相同的口。而双口 RAM 的 A口 和 B口是完全独立的,各自有各自的读缓存,互不影响。 - +  ### e.g. 矩阵转置 @@ -4335,9 +4345,9 @@ method get = ram.portB.response.get; - 如果外界不积极地调用 `put` 方法,双缓冲经常为空,导致 `read_ram` 规则不激活,从而不再读出数据。 - 如果外界不积极地调用 `get` 方法,则 `ram.portB` 的读缓冲区积攒数据,导致 `ram.portB.request.put` 被阻塞,导致 `read_ram` 规则不激活,导致双缓冲经常处于满的状态,最终导致 `put` 方法不能执行。 +  - - +  ## 8.3 队列 FIFO 概览 @@ -4393,9 +4403,9 @@ FIFO 模块众多,功能大同小异,概览如**表16**。(从模块名可 | ![图7](./readme_image/7.fifos.png) | | :----------------------------------------------------------: | -| **图7**:mkFIFO、mkFIFO1、mkLFIFO、mkBypassFIFO 的并发行为。 | - +| **图7**:mkFIFO、mkFIFO1、mkLFIFO、mkBypassFIFO 的并发读写行为。 | +  ## 8.4 常规 FIFO @@ -4465,7 +4475,7 @@ if(v1 == v3) | notEmpty | SB | CF | CF | SB | CF | SA | | clear | SA | SA | SA | SA | SA | SBR | - +  ### e.g. 开平方计算流水线 v2 @@ -4555,9 +4565,9 @@ endmodule 读者可自行运行 `src/15.Sqrt/Sqrt_v2.bsv` 来验证。 -> :question: 思考:本例中,如果 rule sqrt_output 没有显式条件,那么把 `mkFIFO` 换成 `mkFIFO1` 会怎样?换成 `mkSizedFIFO(3)` 又会怎样?对 `mkSqrtUInt32` 的和性能是否有影响? - +> :question: 思考:本例中,如果 `rule sqrt_output` 没有显式条件,那么把 `mkFIFO` 换成 `mkFIFO1` 会怎样?换成 `mkSizedFIFO(3)` 又会怎样?对 `mkSqrtUInt32` 的性能是否有影响? +  ## 8.5 不保护 FIFO @@ -4604,7 +4614,7 @@ FIFOF#(int) fifo3 <- mkGSizedFIFOF(True, True, 5); // 容量=5,不保护压 不保护 FIFO 的用途举例:假设我们要把 FIFO 的 deq 写在 if(条件) 下,我们希望在 条件=False 时,不要因为 FIFO 为空而导致整个规则不能激活,那么此时就可以用不保护弹出的 FIFO 。 - +  ## 8.6 LFIFO @@ -4708,7 +4718,7 @@ rule stage4_forward; endrule ``` - +  ## 8.7 DFIFOF @@ -4803,7 +4813,7 @@ module mkBitCoder ( FIFO#(Bit#(8)) ); endmodule ``` - +  ## 8.8 BypassFIFO @@ -4830,7 +4840,7 @@ BypassFIFO 在为空时支持并发 enq 与 deq (参考**图7**),此时 enq | :----------------------------------------------------------: | | **图10** :流水线式 CPU 的 IF 和 ID 阶段,用 mkBypassFIFO 来支持取指令总接线口的停顿(例如cache miss) | - +  ## 8.9 大容量的 BRAMFIFO @@ -4858,7 +4868,9 @@ FIFOF#(int) fifo2 <- mkSizedBRAMFIFOF(114514); // 容量=114514,接口为 FIF BRAMFIFO 用起来和常规 FIFO 行为相同。 +  +  # 9 高级数据类型 @@ -4931,7 +4943,7 @@ endrule 除了寄存器,你还可以创建其它硬件模块的数组,比如 Wire 、 FIFO 、 BRAM 的数组。 - +  ## 9.2 向量 Vector @@ -5081,7 +5093,7 @@ rule test; // vec_in_reg 的类型是 Reg#(Vector#( endrule ``` - +  ## 9.3 typedef 关键字 @@ -5097,7 +5109,7 @@ typedef UInt#(32) Uint; 但是注意,对于用 `typedef` 定义的两个结构体 (struct),即使成员变量完全一样,也不互为别名,不能直接互相赋值(但可以用前面讲过的 `b = unpack(pack(a))` 进行类型转换后再赋值)。 - +  ## 9.4 枚举 enum @@ -5107,7 +5119,7 @@ typedef UInt#(32) Uint; typedef enum {Green, Yellow, Red} Light deriving(Eq, Bits); ``` -> :triangular_flag_on_post: 复习一下 5.1 节的知识:`deriving(Eq, Bits)` 代表 Light1 派生自 `Eq` 和 `Bits` 类型类。`Eq` 表明该类型中的变量可以判断是否相等;`Bits` 表明该类型中的变量可以编码为若干 bit,这样才可综合。 +> :triangular_flag_on_post: 复习一下 5.1 节的知识:`deriving(Eq, Bits)` 代表 `Light` 派生自 `Eq` 和 `Bits` 类型类。`Eq` 表明该类型中的变量可以判断是否相等;`Bits` 表明该类型中的变量可以编码为若干 bit,这样才可综合。 然后就可以定义该枚举类型的变量,并且给它赋值: @@ -5184,7 +5196,7 @@ else if(opcode == LOAD) // 如果是 LOAD 指令,就干某些事 ``` - +  ## 9.5 结构体 struct @@ -5254,7 +5266,7 @@ pixel.coord = Coord{x:1, y:2}; pixel.coord.x = 8; ``` - +  ## 9.6 标签联合体 union tagged @@ -5330,7 +5342,7 @@ typedef union tagged { } Maybe#(type data_t) deriving (Eq, Bits); ``` - +  ## 9.7 case 语句与表达式 @@ -5409,7 +5421,9 @@ y = case(x) matches endcase; ``` +  +  # 10 多态 @@ -5420,7 +5434,7 @@ y = case(x) matches 当然,这里的适应是指编译期适应,在编译时根据需要确定类型和位宽,而**不是**在仿真运行或硬件运行时动态适应。 - +  ## 10.1 多态中的基本概念 @@ -5450,7 +5464,7 @@ let c = do_nothing(a); // 推断出 c 为 Bool let d = do_nothing(b); // 推断出 d 为 int ``` -下面,我们用通过编写一些“判断相等”的多态函数,来引入多态中的几个基本概念:**数值类型** (numeric type)、**派生要求** (overloading provisos)、 **关系要求** (relationship provisos)、 **数值函数** (numeric type functions) 和 **伪函数** (pseudo functions) 。 +下面,我们通过编写一些“判断相等”的多态函数,来引入BSV多态中的几个基本概念:**数值类型** (numeric type)、**派生要求** (overloading provisos)、 **关系要求** (relationship provisos)、 **数值函数** (numeric type functions) 和 **伪函数** (pseudo functions) 。 以下代码实现一个多态函数来判断两个参数是否相等,两个参数使用同样的类型变量 td 。另外,需要用 provisos (**补充要求**)关键字要求 td 是派生自 Eq 类型类的,这样才能使用 == 判断是否相等。 @@ -5536,7 +5550,7 @@ endfunction - **数值函数** (numeric type functions):参数和返回值都是数值类型的函数。 - **伪函数** (pseudo functions):参数和返回值可能是数值类型,但不全是。 - +  ### 派生要求一览 @@ -5557,43 +5571,43 @@ endfunction ​ **表19**:关系要求一览表。 -| 关系要求格式 | 说明 | 备注 | -| ------------- | ------------------ | ----------------------------------------------- | -| Add#(n, m, k) | 要求 n+m=k | | -| Mul#(n, m, k) | 要求 n*m=k | | -| Div#(n, m, k) | 要求 n/m=k | 是整数除法,忽视商的小数部分 | -| Max#(n, m, k) | 要求n和m的最大值=k | | -| Min#(n, m, k) | 要求n和m的最小值=k | | -| Log#(n, m) | 要求 log2(n-1)+1=m | 忽视m的小数部分。或者说,编码数字n-1至少要m比特 | +| 关系要求格式 | 说明 | 备注 | +| ------------- | -------------------- | ---------------------------- | +| Add#(n, m, k) | 要求 n+m=k | | +| Mul#(n, m, k) | 要求 n*m=k | | +| Div#(n, m, k) | 要求 n/m=k | 是整数除法,忽视商的小数部分 | +| Max#(n, m, k) | 要求n和m的最大值=k | | +| Min#(n, m, k) | 要求n和m的最小值=k | | +| Log#(n, m) | 要求 ceil(log2(n))=m | ceil() 意为向上取整 | ### 数值函数一览 ​ **表20**:数值函数一览表。 -| 数值函数格式 | 得到 | 备注 | -| ------------ | ------------ | ---------------------------- | -| TAdd#(n, m) | n+m | | -| TSub#(n, m) | n-m | | -| TMul#(n, m) | n*m | | -| TDiv#(n, m) | n/m | 是整数除法,忽视商的小数部分 | -| TLog#(n) | log2(n-1)+1 | 忽视结果的小数部分 | -| TExp#(n) | 2^n | | -| TMax#(n, m) | n和m的最大值 | | -| TMin#(n, m) | n和m的最小值 | | +| 数值函数格式 | 得到 | 备注 | +| ------------ | ------------- | ---------------------------- | +| TAdd#(n, m) | n+m | | +| TSub#(n, m) | n-m | | +| TMul#(n, m) | n*m | | +| TDiv#(n, m) | n/m | 是整数除法,忽视商的小数部分 | +| TLog#(n) | ceil(log2(n)) | ceil() 意为向上取整。 | +| TExp#(n) | 2^n | | +| TMax#(n, m) | n和m的最大值 | | +| TMin#(n, m) | n和m的最小值 | | -`Log#(n, m)` 和 `TLog#(n)` 的公式 `log2(n-1)+1` 比较绕,但又很常用,列举如**表21**。 +`Log#(n, m)` 和 `TLog#(n)` 的公式是 `ceil(log2(n))` ,其中 `ceil()` 代表向上取整,列举如**表21**。该公式很常用,因为 `ceil(log2(n))` 刚好代表了 (n-1) 这个数字的二进制表示需要用多少个 bit 。 ​ **表21**:公式 `log2(n-1)+1` 的输入值和输出值列举。 -| n 值 | log2(n-1)+1 的值 | -| ------ | ---------------- | -| 1 | 0 | -| 2 | 1 | -| 3\~4 | 2 | -| 5\~8 | 3 | -| 9\~16 | 4 | -| 17\~32 | 5 | -| ... | ... | +| n 值 | log2(n-1)+1 的值 | 备注 | +| ------ | ---------------- | --------------------------------------------- | +| 1 | 0 | | +| 2 | 1 | 2-1=1 的二进制表示为 0b1 ,需要 1bit 来表示 | +| 3\~4 | 2 | 3-1=2 的二进制表示为 0b10 ,需要 2bit 来表示 | +| 5\~8 | 3 | 5-1=4 的二进制表示为 0b100 ,需要 3bit 来表示 | +| 9\~16 | 4 | | +| 17\~32 | 5 | | +| ... | ... | | ### 伪函数一览 @@ -5613,7 +5627,7 @@ function td vectorSum(Vector#(len, td) vec) endfunction ``` - +  ## 10.2 多态函数举例 @@ -5624,7 +5638,7 @@ endfunction function Integer vectorLen(Vector#(n, td) vec) = valueOf(n); ``` - +  **例二**:把寄存器向量中的数据读出来,转化成数据向量: @@ -5642,7 +5656,7 @@ function Vector#(n, td) regVector2Vector( Vector#(n, Reg#(td)) reg_vec ) endfunction ``` - +  **例三**:计算两个变量的平方和: @@ -5656,7 +5670,7 @@ function td squareSum2(td a, td b) endfunction ``` - +  **例四**:获取一个 `Bit#()` 变量的最高位的1的下标: @@ -5682,7 +5696,7 @@ let pos = highestOnePosition(cnt); // 根据highestOnePosition的provis > 思考:能否编写一个多态函数,输入一个 Bit#(n) 类型,从最高位开始删去所有的0,返回一个 Bit#(m) 类型 (m<=n) 。为什么? - +  **例五**:计算 Vector 每项元素之和: @@ -5699,7 +5713,7 @@ function td vectorSum(Vector#(len, td) vec) endfunction ``` - +  **例六**:计算 Vector 每项元素之和(能避免求和溢出,自动扩展 Vector 每项的位宽,扩展 TLog#(len) 位,len 是 Vector 长度,例如,若 Vector 长度=8,则扩展 3 位)。 @@ -5732,7 +5746,7 @@ UInt#(35) sum = vectorSumAutoExtend(vec1); // 自动扩展了 3 bit > 以上 vectorSumAutoExtend 无法处理有符号数的情况,因为在位扩展时永远进行的是零扩展,我试图用 provisos BitExtend#() 时总是报错,在 BSV 官方文档 [1], [2] 也没找到 provisos BitExtend#() 的具体示例。所以目前只能退而求其次。 - +  ## 10.3 多态模块举例 @@ -5782,7 +5796,7 @@ Reg#(int) wire_reg <- mkWireReg(0); // 实例化一个 mkWireReg ,他有一 另外我还编写了测试模块 mkTb ,读者可自行仿真测试。 - +  ### e.g. 自定义双缓冲模块 @@ -5996,7 +6010,9 @@ cnt=[ 47] rcnt=[ 2] data={ 40 41 42 43 44} 另外, `src/23.DoubleBuffer/DoubleBuffer.bsv` 中还实现了另一种基于 BRAM 的多态双缓冲模块 `mkReorderDoubleBuffer` (矩阵重排序双缓冲),它是 8.2 节的矩阵转置双缓冲模块的通用化改进,不仅支持任意数据类型;支持可配置的行数和列数;还支持行主序、列主序、行逆序、列逆序。这里不再讲解其原理,读者可以自行阅读代码。 +  +  # 11 状态机 StmtFSM @@ -6092,7 +6108,7 @@ mkAutoFSM 模块没有接口,不需要调用 start 方法就直接开始运行 endseq ); // 运行完自动运行 $finish ``` - +  ## 11.2 执行结构 @@ -6248,13 +6264,13 @@ action 可以把多个动作语句合并成一个动作。 `par...endpar` 和 `seq...endseq` 可以互相嵌套。 - +  ## 11.3 分支和循环 -根据之前所学,我们知道 BSV 中的 if 分支、while 循环、for 循环都是在单周期内执行的动作,循环会被完全展开。 +根据之前所学,我们知道 BSV 中的 `if` 分支、`while` 循环、`for` 循环都是在单周期内执行的动作,循环会被完全展开,就像 Verilog 里那样。 -然而,如果在 `seq...endseq` 结构中描述分支和循环,它们会变成顺序的。 +然而,如果在 `seq...endseq` 结构中描述分支和循环,它们会变成由状态机控制的顺序执行的逻辑。 ### if 分支 @@ -6352,7 +6368,7 @@ endseq > 注意区分另一种情况:如果 `for` 循环被嵌套在 `action...endaction` 中,那么整个循环就是在单周期内执行的,也就是会被编译器完全地进行循环展开。 - +  ## 11.4 区别局部变量和寄存器 @@ -6382,7 +6398,7 @@ seq endseq ``` - +  ## 11.5 举例 @@ -6463,7 +6479,9 @@ module mkTb (); endmodule ``` +  +  # 12 BSV for Verilog @@ -6481,7 +6499,7 @@ BSV 编译器可以把 BSV 代码以模块为单位转化成符合 Verilog-2001 - 泛型模块不能作为生成 Verilog 代码的顶层,不能加 `(* synthesis *)` 属性,且只能被其它模块调用。 2. 编写完成后,用命令行 `bsvbuild.sh -vw` 生成 Verilog 代码,并生成仿真 `.vcd`波形文件。 3. 查看生成的 Verilog 模块的输入输出接口: - - 生成的 Verilog 文件 (.v) 可能有很多个,每个对应文件一个模块,包括一些子模块,以及仿真代码生成的仿真模块。你需要查看的是与你想要的 BSV 模块同名的 Verilog 模块。 + - 生成的 Verilog 文件 (.v) 可能有很多个,每个文件对应一个模块,包括一些子模块,以及仿真代码生成的仿真模块。你需要查看的是与你想要的 BSV 模块同名的 Verilog 模块。 4. 打开 .vcd 波形文件,找到你想要的模块的调用者,查看它调用该模块的波形,理解各信号的意义。 - 如果你想要删除一些不必要的 EN 和 RDY 握手信号,在 BSV 代码文件中的模块上加上 `always_enabled` 和 `always_ready` 属性,重新生成 Verilog 。 5. 根据如上理解,以及你的项目需要,编写 Verilog 调用者代码来调用该模块。 @@ -6494,7 +6512,7 @@ BSV 编译器可以把 BSV 代码以模块为单位转化成符合 Verilog-2001 - 删除不必要信号的方法,即属性 `always_ready` 和 `always_enabled` 的使用方法。 - 生成的 Verilog 可能还依赖一些 BSV 提供的 Verilog 源代码库文件,如何找到并添加它们? - +  ## 12.1 输入输出信号 @@ -6509,7 +6527,7 @@ BSV 编译器可以把 BSV 代码以模块为单位转化成符合 Verilog-2001 - `input RST_N` 全局复位:当 `CLK` 的上升沿,若 `RST_N=0` ,模块中需要复位的寄存器复位。若 `RST_N=1` ,则正常工作 - 这是典型的标准同步复位 - +  ### 方法生成的信号 @@ -6531,7 +6549,7 @@ BSV 编译器可以把 BSV 代码以模块为单位转化成符合 Verilog-2001 > >:x: :不会生成信号 - +  ### 举例:mkBitCoder 模块的信号 @@ -6632,7 +6650,7 @@ module mkBitCoder(CLK, - input EN_clear : 代表外界是否想清空 - 因为没有参数所以没有输入信号 - +  为了印证以上分析,打开 `.vcd` 波形文件查看这些信号。如**图12**,可以看出: @@ -6683,7 +6701,7 @@ module mkBitCoder(CLK, 我们编写 Verilog 来调用 BSV 生成的 Verilog 模块时,需要依照以上原则,根据模块输出的 RDY 和 输出数据 信号来正确地产生 EN 和 输入数据信号。 - +  ## 12.2 删除不必要的信号 @@ -6708,7 +6726,7 @@ module mkBitCoder ( FIFO#(Bit#(8)) ); 对于一个加上 `always_ready` 属性的方法,如果隐式条件不总是 = True ,编译器就会报断言错误。 - +  ### always_enabled 属性 @@ -6734,7 +6752,7 @@ module mkModule ... `always_enabled` 属性不像 `always_ready` 用得那么多,一般只用在想要组合逻辑输入的情况下,比如该方法把参数写入了一个 BypassWire 内。 - +  ## 12.3 引入缺少的 Verilog 库文件 @@ -6775,7 +6793,9 @@ Verilog 代码库的路径包括: **方法二**是直接编译项目 ,看如何报错,缺少哪个模块名,就去 Verilog 代码库的路径下找同名文件即可。 +  +  # 13 样例研究 @@ -6783,7 +6803,7 @@ Verilog 代码库的路径包括: 本章配套代码已全部完成并测试,部分文字尚未完成,留待后续补充。 - +  ## 13.1 SPIFlash 读写器 @@ -6819,7 +6839,9 @@ $ bsvbuild.sh -vw mkTb SPIFlashController.bsv | :--------------------------------------------: | | **图15**:仿真产生的页擦除、页写 (部分) 的波形 | +  +  ## 13.2 RISC-V 流水线 CPU @@ -6999,7 +7021,9 @@ DataRAM[00000008] = 36611 生成的 `mkRv32iCPU.v` 在 Altera Cyclone IV EP4CE115F29C8 上占用 5312 LE (logic elements),占总量的5%。时钟频率达到 77MHz 。 +  +  ## 13.3 JPEG 图像压缩器