索引置顶

SpinalHDL开发

Lambda演算python实现

函数式编程

Scala 笔记

RISCV 开发调试

Chisel 笔记

EDA工具

通信

想法

其他

Scala 单例对象的三个应用

一 进度条

有时候我们在编写代码时希望能够查看运行时间,最简单的方法:

1
2
3
4
5
6
def main(args: Array[String]) {
val startTime: Long = System.currentTimeMillis
待测试的代码块
val endTime: Long = System.currentTimeMillis
System.out.println("程序运行时间: " + (endTime - startTime) + "ms")
}

但这个方法显然不是那么优雅。如果你在使用Scala的时候注意运行log,你会发下进度条更漂亮直观的。

1
2
3
4
5
6
7
[Runtime] SpinalHDL v1.3.9    git head : a4cb4aadf0820174c1b48023bfcd3e9981de1d4a
[Runtime] JVM max memory : 1820.5MiB
[Runtime] Current date : 2020.01.23 10:44:55
[Progress] at 0.000 : Elaborate components
[Progress] at 0.513 : Checks and transforms
[Progress] at 0.705 : Generate Verilog
[Done] at 0.806

实现非常简单,首先得创建一个单例对象,单例对象一旦被实例化以后,startTime就会确定,不管Driver后面被调用多少次只会用到第一次被实例化的对象,startTime也不会改变。

1
2
3
4
object Driver {
val startTime = System.currentTimeMillis()
def executionTime: Double = (System.currentTimeMillis - startTime) / 1000.0
}

再创建一个名叫“MyProgress”的用户接口, 每次执行“MyProgress”时调用单例对象Driver的executionTime打印当前时刻。

1
2
3
4
object MyProgress {
def apply(message: String) =
println(s"${MyLog.tag("Progress", Console.BLUE)} at ${f"${Driver.executionTime}%1.3f"} : $message")
}

以下是一个基带仿真的case,每个关键节点调用MyProgress, 每步耗费的时间一目了然(单位s)。

1
2
3
4
5
6
7
8
9
[Progress] at 0.000 : Coherence correlation start
[Progress] at 0.076 : DopplerOffset HertzNumber(250.0)
[Progress] at 0.296 : Sample done
[Progress] at 0.481 : add AWGN noise
[Progress] at 0.488 : Normalized rx power to 0 dB
[Progress] at 0.489 : Receive data with AWGN, Sampled at 2.048/40.96MHz
[Progress] at 0.529 : Quantization to 2bit done
[Progress] at 0.538 : 2 bit AD data remaping done
[Progress] at 2.243 : carrier Mixing done

我也听到有人抱怨Rocket生成时间巨长,自己也搞不清楚时间到底耗费在什么地方,有可能是编译,也有可能是逻辑代码,也有可能是firrtl的解析生成。 现在你就可以用MyProgress来诊断瓶颈到底出在哪儿。

我自己用Scala编写算法参考平台时就遇到一个怪异的问题,按理说Scala是静态语言比Python更快,但是大致相同的算法,运行时间奇慢无比,后来就用MyProgress的进行二分法,找到问题的症结,我在打印文件时代用折叠的方式拼接字符串,foldLeft在功能上并没有问题,但是在字符串拼接是时间复杂度应该不是线性的(具体我没有深究,有兴趣的同学可以研究一下),导致很慢。

1
fp.write(content.foldLeft("")(_+"\n"+_))

正确的方法是用mkString,速度回复正常,问题解决

1
fp.write(content.mkString("("\n")))

最后这里的MyProgress正是SpinalHDL的SpinalProgress,有兴趣可以阅读其源码

二 开关

开关应用还是比较广泛,用过Matlab你就会比较熟悉

1
2
grid on
grid off

我举另外一个例子,算法在设计完功能和性能仿真以后要做定点性能仿真,定点的过程是需要修改代码,但是算法需要经常在全精度和定点之间切换调试,因为有时候性能不过,我们不清楚是逻辑的问题还是定点的问题。

当一个很大的project中定点地方可能非常多,我们不可能每次去来回修改定点代码,优雅的方式应该设置一个开关,而不是去修改代码。

首先设置一个开关单例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
protected object FixSwitch {
private var switch: Boolean = true
def state: Boolean = switch
def on: Boolean = {
SpinalInfo("FixPoint Switch on")
switch = true
state
}
def off: Boolean = {
SpinalInfo("FixPoint Switch off")
switch = false
state
}

然后设置2个开关动作来改变单例对象的状态。

1
2
3
4
5
6
7
object FixOn{
def apply(): Boolean = FixSwitch on
}

object FixOff{
def apply(): Boolean = FixSwitch off
}

定点源码中加入开关状态

1
2
3
4
5
6
7
8
9
10
11
class FixData(...){
...
def fixProcess(): Double ={
if(FixSwitch.state){
fixPoint logic .....
} else {
raw
}
}
...
}

性能仿真

1
2
3
4
object PerformanceRegression extends App{
FixOff() //关闭定点,默认定点开启
Simulation5G() //代码中的定点自动失效,不需要手动修改
}

有些人发现本质上这就是设置了一个全局可变的变量而已,

1
var FixSwitchState: Boolean = true

但是我们为什么不用全局变量, 而是单例对象,留给大家思考。

三 全局默认参数

同样也是定点上的一个例子,SpinalHDL提供定点工厂函数

1
val wire5bit = wire10bit.fixTo( 6 downto 2, roundType = FLOOR, symmetric = true)

但是一个Project可能有成百上千的定点处理, 每个定点都添加roundType, symmetric显然很累赘,当然你可以选择默认参数

1
val wire5bit = wire10bit.fixTo( 6 downto 2)

不同的团队,不同的项目,可能使用不同的Round方式还有对称方式,我们希望默认参数也是可配置的。SpinalHDL UInt/SInt定点源码:

1
2
def fixTo(x: Range.Inclusive): IQ = 
fixTo(x, round = getFixRound(), sym = getFixSym())

默认的round,和 symmetric 参数通过getFixRound, getFixSym从全局获得。

一般一个工程种round 和 symmetric都是固定的,直接在SpinalConfig种设置即可。

1
2
SpinalConfig(mode = Verilog,
fixPointConfig = FixPointConfig(ROUNDUP, true))

当然也可以在通过以下方式来刷新默认配置。

1
FixPointConfig(ROUNDUP, true).flush()

具体实现参见源码

Spinal-sim Verilator install on Windows

Verilator install on Windows

Step1 : install MSYS32

到官网https://www.msys2.org/ 下载最近安装文件 msys2-x86_64-20190524.exe

运行安装到d:/msys64,安装完毕后打开 mingw64.exe

在命令行输入 pacman -Suyy

如果遇到以下异常

1
2
3
4
5
# pacman -Syuu
错误:无法初始化事务处理 (无法锁定数据库)
错误:无法锁定数据库:File exists
如果你确认软件包管理器没有在运行,
你可以删除 /var/lib/pacman/db.lck。

解决办法,删掉之前的文件: /var/lib/pacman/db.lck
出现错误的原因是,之前同步的时候,由于异常中断,导致之前进程锁文件未被释放。

Step2 Change mirrors for China user

如果更新库很慢导致失败

1
2
3
4
5
6
7
# pacman -Syuu
:: 正在同步软件包数据库...
错误:无法从 repo.msys2.org : Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds 获取文件 'mingw32.db'] 49%
错误:无法从 sourceforge.net : Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds 获取文件 'mingw32.db' 3%
错误:无法从 www2.futureware.at : Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds 获取文件 'mingw32.db'7%
错误:无法从 mirror.yandex.ru : Operation too slow. Less than 1 bytes/sec transferred the last 10 seconds 获取文件 'mingw32.db' 10%
错误:无法升级 mingw32 (下载数据库出错)

请更新 清华镜像

编辑 /etc/pacman.d/mirrorlist.mingw32 ,在文件开头添加:

1
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686

编辑 /etc/pacman.d/mirrorlist.mingw64 ,在文件开头添加:

1
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64

编辑 /etc/pacman.d/mirrorlist.msys ,在文件开头添加:

1
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch

然后执行 pacman -Sy 刷新软件包数据即可。

Step3: Install Verilator

1
2
3
4
5
6
pacman -Syuu
#Close the MSYS2 shell once you're asked to
pacman -Syuu
pacman -S --needed base-devel mingw-w64-x86_64-toolchain \
git flex\
mingw-w64-x86_64-cmake
1
pacman -S mingw-w64-x86_64-verilator

Step4: Add to ENV

Add D:\msys64\usr\bin;D:\msys64\mingw64\bin to you windows PATH

Step4: Spinal simulation by verialtor

1
2
3
4
5
6
7
8
9
10
11
12
[Progress] Verilator compilation started
VDFT2Cell.mk:67: /mingw64/share/verilator/include/verilated.mk: No such file or directory
make: *** No rule to make target '/mingw64/share/verilator/include/verilated.mk'. Stop.
Exception in thread "main" java.lang.AssertionError: assertion failed: Verilator C++ model compilation failed
at scala.Predef$.assert(Predef.scala:170)
at spinal.sim.VerilatorBackend.compileVerilator(VerilatorBackend.scala:376)
at spinal.sim.VerilatorBackend.<init>(VerilatorBackend.scala:429)
at spinal.core.sim.SpinalVerilatorBackend$.apply(SimBootstraps.scala:120)
at spinal.core.sim.SpinalSimConfig.compile(SimBootstraps.scala:400)
at spinal.core.sim.SpinalSimConfig.compile(SimBootstraps.scala:364)
at FFT.DFT2CellTest$.main(FFTsim.scala:27)
at FFT.DFT2CellTest.main(FFTsim.scala)

显示verilated.mk路径找不到,我忍为应该是VERILATOR_ROOT目录设置有误, 查找verilated.mk的目录所在地,
然后在windows Env 中添加系统变量 VERILATOR_ROOT= /d/msys64/mingw64/share/verilator

任然遇到问题

1
2
3
4
5
6
7
8
9
x86_64-w64-mingw32-g++.exe: error: /d/msys64/mingw64/share/verilator/include/verilated.cpp: No such file or directory
x86_64-w64-mingw32-g++.exe: fatal error: no input files
compilation terminated.
make: *** [/d/msys64/mingw64/share/verilator/include/verilated.mk:192: verilated.o] Error 1
make: *** Waiting for unfinished jobs....
x86_64-w64-mingw32-g++.exe: error: /d/msys64/mingw64/share/verilator/include/verilated_vcd_c.cpp: No such file or directory
x86_64-w64-mingw32-g++.exe: fatal error: no input files
compilation terminated.
Exception in thread "main" java.lang.AssertionError: assertion failed: Verilator C++ model compilation failed

x86_64-w64-mingw32-g++.exe 不能访问/d/myss64/….,后来重新安装MSYS2到 C盘,并且将系统变量删除 VERILATOR_ROOT

任然遇到问题:
检查环境变量Paht的值为C:\Users\Administrator\.babun\cygwin\bin;D:\Program\emacs-26.2\bin;C:\Users\Administrator\.babun;C:\Users\Administrator\AppData\Roaming\npm;%IntelliJ IDEA Community Edition%;C:\Users\Administrator\AppData\Local\Pandoc\;c:\msys64\usr\bin\;c:\msys64\mingw64\bin\ 发现很乱 ,删除一些不用的
D:\Program\emacs-26.2\bin;C:\Users\Administrator\.babun;C:\Users\Administrator\AppData\Local\Pandoc\;c:\msys64\usr\bin\;c:\msys64\mingw64\bin\;
更新PATH

重新开启Project , 运行Spinal-sim ,Wow 居然成了, 非常痛苦, 饶了一大圈,居然是PATH变量的问题。总算是解决了

最后:

特别注意:

尽量把MSYS2安装到C盘 在环境变量Path末尾追加C:\msys64\usr\bin;C:\msys64\mingw64\bin

不要多此一举设置VERILATOR_ROOT, Spinal会默认识别到 /mingw64/share/verilator

如果运行不成功,请检查你的PATH,设置是不是非常杂乱,请删除不用的,尽量保持干净。

浮点避坑

浮点数相加

我们知道浮点数尽量避免不同量纲的数相加

1
2
> 1023 + 0.00000000000001
Double = 1023.0

浮点数除法

不同量纲之间的除法也会得到不一样的结果

1
2
3
4
> 1.023/2.047
Double = 0.49975574010747426
> 1023.0/2047.0
Double = 0.49975574010747437

因此对于有些应用可能引入意向不到的结果
比如

1
2
3
4
> 1023.0/2047.0*2.047 == 1.023
Boolean = false
> 1023.0/2047.0*2047 == 1023
Boolean = true

避免不同量纲的数做加减法,还要注意不同量纲的数做乘除法带来的意外结果

典型的例子

1
2
3
0.3 - 0.1
floor( 0.3*10 - 0.1 *10 ) //2.0
floor((0.3 - 0.1)*10 ) //1.0

Double Eps 精度

1
2
Float  (B=2 / p=24): Eps = 2^(-24) = 5.9604644775390625E-8
Double (B=2 / p=53): Eps = 2^(-53) = 1.1102230246251565E-16

也就是说计算机能表示的最小绝对值

1
2
0+1E-16 = E-16
1+1E-16 = 1.0

他的精度跟指数位有关,总共E-16次方的误差范围,指数位占n位,那么误差就等与E(-16+n)

1
2
3
4
1000+1e-16= 1000.0
1000+1e-15= 1000.0
1000+1e-14= 1000.0
1000+1e-13= 1000.0000000000001

只要指数位和分数位之和不要超过16,都可以表示

1
2
3
4
5
6
7
1E10+1E-6 = 1.0000000000000002E10   //能表示1E-6不能表示

1E10+1E-7 = 1.0E10 //误差1E-7不能表示
1E8+1E-7 = 1.000000000000001E8 //指数位8,误差1E-7可以表示
1000000 +1E-10 = 100000.0000000001 //可以表示1E-10
10000000 +1E-10 = 10000000.0 //不能表示1E-10误差,指数位占了7
1E18 + 13 = 1e18

所以你在用浮点计算的时候,要看数的量纲,量纲越大,Eps要设置的大一些, 否则就失去了意义

Scala EPS

所以在判断一个数是否为0是, 不能 a == 0 而是要 abs(a) <= Eps ,
同样在比较两个数是否相等时,不能 a == b 而是要 abs(a - b) <= Eps

BigDecimal

Double的问题是精度有限

1
2
3
val a = 0.02
val b = 0.03
val c = b - a

c的结果不是0.01,而是c: Double = 0.009999999999999998
因此有些系统设计中不能用double,不如金融银行金额计算
为什么会这样呢? 因为float和double都是浮点数, 都有取值范围, 都有精度范围. 浮点数与通常使用的小数不同, 使用中, 往往难以确定.
常见的问题是定义了一个浮点数, 经过一系列的计算, 它本来应该等于某个确定值, 但实际上并不是!
double相减会转换成二进制,因double有效位数为 16位这就会出现存储小数位数不够的情况,这种情况下就会出现误差,解决方法就是使用BigDecimal,它的有效长度足够长可存储小数位数。
因此可代替double来进行加减乘除, 金额必须是完全精确的计算, 故不能使用double或者float, 而应该采用java.math.BigDecimal.

1
2
3
val a = BigDecimal(0.02)
val b = BigDecimal(0.03)
val c = b - a

c的结果得到0.01

1
2
3
val a =  BigInt("1234567890123456").toLong
val b = 0.001
val c = a + b

c的值为1234567890123456,0.01丢失

1
2
3
val a =  BigDecimal("1234567890123456")
val b = BigDecimal(0.001)
val c = a + b

c的值为1234567890123456.001,精度并不会丢失

BigDecimal(value), value可以用数字初始化,太大的值只能用字符串初始化如“123456789012345678”

1
2
3
4
5
6
7
val a =  BigDecimal("1234567890123456001")
val b = BigDecimal(0.1231)
val c = a * b
c.precision
c.rounded
c.scale
c.pow(2)
1
2
3
val c= BigDecimal("1234.5000")
c.doubleValue
c.round(new java.math.MathContext(4, java.math.RoundingMode.HALF_UP))

Scala3-Macro系统Tasty进展

Scala3 重新设计Macro系统,这是官网英文原文
翻译的很烂,全当学习笔记而已,仅供参考

Or: Scala in a (Tasty) Nutshell

如何迁移到 Scala 3这篇博文中提到最大的一个问题是关于宏的问题。
目前我们正在努力将Tasty和macros对齐,接下来谈一谈我们的想法.

What is Tasty?

Tasty是Scala3的高级交换格式。它基于类型化的抽象语法树
这些树在某种意义上包含了Scala程序中的所有信息。
它们表示程序的语法结构,还包含有关类型和位置的完整信息.
Tasty在语法检查之后(这样所有的类型都显式的知道了,并且隐式的东西都已经解释过了)给代码做一个快照,
但是在快照之前不会经过任何转换,因此所有的信息都不会丢失。
放语法树的文件为了紧凑会压缩优化(有点像javascript的压缩),这意味着我们可以在任何编译器运行期间
生成完整的Tasty语法树,即便是单独编译也不依赖任何其他东西。

为什么"简单"如此复杂

软件工程中”简单”的复杂性

简单可能比复杂更难:你必须努力使思维清晰才能简化它。但最终这一切都是值得的,因为它能使你翻越高山。
—— Steve Jobs

我相信有两种设计软件的方式:一种是使软件足够简单而明显没有缺陷;另一种是使它如此的复杂,以至于没有明显的(可被轻易发现的)缺陷。

—— Tony
Hoare(译者注:英国计算机科学家、图灵奖得主、快速排序算法的发明人、哲学家就餐问题的提出者……)

简单,软件工程的圣杯。业界的前辈一直在驱策我们去追求它。

(软件设计)是一门手艺……相对”复杂”而言,它赋予”简单”更高的价值
—— Barbara Liskov(译者注:2008年图灵奖得主)

Lisp之根

The Root of List

约翰麦卡锡于1960年发表了一篇非凡的论文,他在这篇论文中对编程的贡献有如 欧几里德对几何的贡献.1 他向我们展示了,在只给定几个简单的操作符和一个 表示函数的记号的基础上, 如何构造出一个完整的编程语言. 麦卡锡称这种语 言为Lisp, 意为List Processing, 因为他的主要思想之一是用一种简单的数据 结构表(list)来代表代码和数据.

值得注意的是,麦卡锡所作的发现,不仅是计算机史上划时代的大事, 而且是一种 在我们这个时代编程越来越趋向的模式.我认为目前为止只有两种真正干净利落, 始终如一的编程模式:C语言模式和Lisp语言模式.此二者就象两座高地, 在它们 中间是尤如沼泽的低地.随着计算机变得越来越强大,新开发的语言一直在坚定地 趋向于Lisp模式. 二十年来,开发新编程语言的一个流行的秘决是,取C语言的计 算模式,逐渐地往上加Lisp模式的特性,例如运行时类型和无用单元收集.

在这篇文章中我尽可能用最简单的术语来解释约翰麦卡锡所做的发现. 关键是我 们不仅要学习某个人四十年前得出的有趣理论结果, 而且展示编程语言的发展方 向. Lisp的不同寻常之处–也就是它优质的定义–是它能够自己来编写自己. 为了理解约翰麦卡锡所表述的这个特点,我们将追溯他的步伐,并将他的数学标记 转换成能够运行的Common Lisp代码.

slides(revealjs 导出PDF)

1. export HTML to slides

$: unzip reveal.js.master.zip
$: cd reveal.js.master

2. Edit index.html

Export to reveal.js from https://slides.com/yourname/yourslide/edit
Presentation made with Slides are powered by the reveal.js open source presentation framework. This export lets you migrate your slide content to a fresh reveal.js install.
a. Download a copy of the reveal.js framework and make the following edits to its index.html.
b. In the , replace the theme CSS with:
c. In the , replace the

node with:
d. In the Reveal.initialize call towards the bottom of your index.html, append these config values:

3. Edit index pdf config

a. edit index.html
add. Reveal.Configure before Reveal.initialize

1
2
3
Reveal.configure({ pdfSeparateFragments: false,
pdfMaxPagesPerSlide: 1 });
Reveal.initialize({ ...

4. Export pdf

open index.html by Chrome like http://xxxx.index.html?print-pdf
Ctrl-P(windows) or COMMAND-P(Mac-OS)
Save

FAQ

  1. Save Fail(Preview Failure)
    print range - full
    I found Chrome export pdf raise Error when pages number bigger than 56
    So you should export it twice 1-56, 57~64

分型与混沌

中文字体
abcdABCD

参考资料

CPS变换

为什么函数调用需要保存状态?

add(1,2) mul(3,4) 这种调用明显不需要保存状态
而add(1,mul(1,2)) 这种计算是需要保存1级函数add的变量,再计算2级函数mul返回值和保存相加最终返回

得出一个结论:函数处在参数位置上,调用后需要返回的函数调用才需要保存状态
而什么是尾调用?无需返回的函数调用
一个简单的判定原则 即函数不在参数位置上