Scala 初探.一(main函数)

SCALA 程序的几种运行方式

方法1

脚本式的使用,scala a.scala 可正常打印

1
2
3
4
def main() = {
println("Hello, Scala way1")
}
main()

但这种写法不能被 scalac a.scala正确编译,可能JVM对象必须要有个main入口函数

RISCV 矢量处理器

RISC-V向量的工作过程

  • setp1 : 首先要确定向量的类型

    比如你要做一个向量运算 Y = a*X + Y ,如果X, Y 都是双精度的浮点的向量,那么就需要申请两个F64类型的向量v0和v1,
    如果是 Z = a*X + Y ,同样X,Y是双精度的浮点的向量,那么就需要至少申请3个F64类型的向量v0和v1,v2分别给X,Y,Z使用
    RV32V 向量寄存器类型的编码如下

Type Floating Poing Signed Interger Unsigned Interger
Width Name vetype Name vetype Name vetype
8bit — — X8 10100 X8U 11100
16bit F16 01101 X16 10101 X16U 11101
32bits F32 01110 X32 10110 X32U 11110
64bits F64 01111 X64 10111 X64U 11111
  • setp2 : 然后申请向量的个数
    假如向量寄存器总共有1024Byte ,如果只给2个向量使用,那么对半分,每个向量可以分得512Byte的空间,F64=8Byte,所以
    每个向量可以有512/8=64个F64类型的元素,也就是mvl(max_vecotr_length)=64, 一次可以计算的个数是64个元素。
    如果我们的X,Y的长度假设是100,那么完成 Y = a*X + Y 需要2次向量操作,
    第一次vl选64, 计算64个F64向量的运算
    第二次vl选100-64= 36, 计算剩余的36个F64向量的运算

从Shell传递变量给verilog的两种方法

  • 通过define宏传递

    首先在命令行中定义define

    1
    2
    3
    4
    5
    6
    7
    vcs -sverilog -debug_all +define%s+CASENAME=\"%s\" 
    -timescale=1ns/100ps
    +notimingcheck
    +nospecify +v2k +memcbk
    -fsdb
    -l com.log
    -f top.f

    尤其要注意CASENAME=\"%s\"的转义“\”的应用,比如命令行被包含在python脚本内部,需要两次转义, 而shell可能只要一次

    1
    2
    3
    def run(case,defines=""):
    os.system('vcs -sverilog -debug_all +define%s+CASENAME=\\"%s\\" -timescale=1ns/100ps +notimingcheck +nospecify +v2k +memcbk -fsdb -l com.log -f top.f'%(defines,case))
    os.system('./simv -l sim.log +notimingcheck +nospecify +loopreport +memcbk +novopt')

    否则verilog文件是无法获取CASENAM的意思

    然后在verilog中通过宏取出

    1
    fp_reg = $fopen({"./case/",`CASENAME,"/source_dpp.txt"},"r");

    这种方式比较繁琐,而且转义嵌套难以理解,另外一种方便的方法如下

Chisel-Verilog查找表优先级问题讨论

Chisel 查找表电路优先级问题

verilog ROM的两种写法及区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
方法1
always @(posedge clk )
begin
case(addr0)
6'd0 : data0 <= 8'd214;
6'd1 : data0 <= 8'd213;
6'd2 : data0 <= 8'd212;
6'd3 : data0 <= 8'd211;
6'd4 : data0 <= 8'd210;
6'd5 : data0 <= 8'd209;
6'd6 : data0 <= 8'd208;
6'd7 : data0 <= 8'd207;
方法2
always @(posedge clk )
begin
if(addr1 == 6'd0 ) data1 <= 8'd150;
else if(addr1 == 6'd1 ) data1 <= 8'd149;
else if(addr1 == 6'd2 ) data1 <= 8'd148;
else if(addr1 == 6'd3 ) data1 <= 8'd147;
else if(addr1 == 6'd4 ) data1 <= 8'd146;
else if(addr1 == 6'd5 ) data1 <= 8'd145;
else if(addr1 == 6'd6 ) data1 <= 8'd144;
else if(addr1 == 6'd7 ) data1 <= 8'd143;

方法2是不是带优先级? 实际上是不带优先级
逻辑上if取的条件都是addr1的值,一定是互斥的,综合工具也能自动识别出这种if else
它有别于以下这种情况,这种情况下是真实带有优先级的电路,其中another_cond_A,another_cond_B
是独立的两个输入条件,和addr1的取值可能同时发生,所以不能被综合器当做查找表来对待。

Chisel 实例问题汇总

chisel-example \ chisel-tutorial 同样代码,生成器输出不一致问题

同样的一份代码
src/main/scala/GCD.scala
src/test/scala/GCDTester.scala

其中生成vcd波形的代码

1
2
3
4
5
object GCDTester extends App {
iotesters.Driver.execute(Array("--target-dir", "generated", "--fint-write-vcd"), () => new GCD){
c => new GCDTests(c)
}
}

IDEA Scala开发环境配置

IDEA

    1. 下载社区版本是免费的,scala开发够用,专业版面向web部分是收费的
    1. 启动idea ,import project 按钮倒入 .sbt 文件
    1. setting java JDK, File>>Project Structure>>Project>>Project SDK, 按new ,选择JAVA jdk.

      默认会自动识别,否则手动下载指定xxx/1.8.0-openjdk-amd64)(一般要求1.8版本)

Makefile 笔记

规则

伪目标

变量

变量定义的3种方式

  • 1 foo = bar (递归展开式变量)
    会在引用$(foo)地方原地替换,直到不能再替换,这种方式称之为递归展开式变量

    1
    2
    3
    4
    foo = $(bar)
    bar = $(ugh)
    ugh = Huh?
    all @echo $(foo)

    以上例子会在@echo $(foo) 地方递归展开
    缺点一 : 是当嵌套定义 foo = $(foo) bar 时讲进入死循环,导致Make失败
    其他例子
    x = $(y)
    y = $(x) $(z)
    同样会陷入死循环,所以这种定义方法只推荐在不引用变量的时候应用
    缺点二 这种定义中如果使用函数,只会在变量被引用时展开,而不是在定义时展开
    会使得Make的效率降低,另外有可能会在变量函数的引用会出现非预期结果,特别当变量
    定义引用到shell wildcard 函数的情况下,出现不可控的结果

    1
    2
    3
    4
    5
    6
    x = foo
    y = $(x) bar //$(x) 不会被立即替换,只有在$(y)被用到的地方才用x的值替换
    x = later //将会覆盖 原来的 x ,
    等价于
    y = later bar
    x = later

RISCV 编译 Link探究

问题: 如何将代码编译到指定的mem地址?

在用$RISCV/bin/riscv64-unknown-elf-gcc hello.c时生成的目标文件默认的都是从0x0000_1000地址开始,
如何自定义指定到从0x8000_0000地址开始?
那么就得熟悉连接器的原理

默认链接脚本

gcc在编译和链接的时候,如果不指定链接脚本,会指向默认的链接脚本
默认的链接脚本可以用 $RISCV/bin/riscv64-unknown-elf-ld -verbose 来查看

RISCV GDB debug调试

RISCV-GDB启动步骤

hello.c

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
long a = 0x876543210 ;
float pi = 3.1415926 ;
printf("Hello %s!\n","World");
a = a*pi;
return a ;
}

如何生成汇编代码

riscv64-unknown-elf-gcc -S hello.c 只会生成汇编代码hello.s 不会生成.out文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    .file   "hello.c"
.option nopic
.text
.section .rodata
.align 3
.LC2:
.string "World"
.align 3
.LC3:
.string "Hello %s!\n"
.text
.align 1
.globl main
.type main, @function
main:
addi sp,sp,-32
sd ra,24(sp)
sd s0,16(sp)
addi s0,sp,32
lui a5,%hi(.LC0)
ld a5,%lo(.LC0)(a5)
sd a5,-24(s0)
lui a5,%hi(.LC1)
flw fa5,%lo(.LC1)(a5)
fsw fa5,-28(s0)
lui a5,%hi(.LC2)
addi a1,a5,%lo(.LC2)
lui a5,%hi(.LC3)
addi a0,a5,%lo(.LC3)
call printf
ld a5,-24(s0)
fcvt.s.l fa4,a5
flw fa5,-28(s0)
fmul.s fa5,fa4,fa5
fcvt.l.s a5,fa5,rtz
sd a5,-24(s0)
ld a5,-24(s0)
sext.w a5,a5
mv a0,a5
ld ra,24(sp)
ld s0,16(sp)
addi sp,sp,32
jr ra
.size main, .-main
.section .rodata
.align 3
.LC0:
.dword 36344967696
.align 2
.LC1:
.word 1078530010
.ident "GCC: (GNU) 8.1.0"

如何反汇编.out文件

首先生成.out文件riscv64-unknown-elf-gcc -o hello.out hello.c

然后反汇编 riscv64-unknown-elf-objdump -d hello.out > hello_disassemble.s

即可查看反汇编代码,搜索main函数如下,当然也包括其他内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
hello.out:     file format elf64-littleriscv
Disassembly of section .text:
00000000000100b0 <_start>:
100b0: 0000d197 auipc gp,0xd
100b4: fd818193 addi gp,gp,-40 # 1d088 <__global_pointer$>
100b8: 84818513 addi a0,gp,-1976 # 1c8d0 <_edata>
100bc: 8d818613 addi a2,gp,-1832 # 1c960 <_end>
.....
...

000000000001038c <printf>:
1038c: 711d addi sp,sp,-96
1038e: f832 sd a2,48(sp)
10390: fc36 sd a3,56(sp)
....
..

0000000000010198 <main>:
10198: 1101 addi sp,sp,-32
1019a: ec06 sd ra,24(sp)
1019c: e822 sd s0,16(sp)
1019e: 1000 addi s0,sp,32
101a0: 67e9 lui a5,0x1a
101a2: a687b783 ld a5,-1432(a5) # 19a68 <__clzdi2+0x54>
101a6: fef43423 sd a5,-24(s0)
101aa: 67e9 lui a5,0x1a
101ac: a707a787 flw fa5,-1424(a5) # 19a70 <__clzdi2+0x5c>
101b0: fef42227 fsw fa5,-28(s0)
101b4: 67e9 lui a5,0x1a
101b6: a5078593 addi a1,a5,-1456 # 19a50 <__clzdi2+0x3c>
101ba: 67e9 lui a5,0x1a
101bc: a5878513 addi a0,a5,-1448 # 19a58 <__clzdi2+0x44>
101c0: 1cc000ef jal ra,1038c <printf>
101c4: fe843783 ld a5,-24(s0)
101c8: d027f753 fcvt.s.l fa4,a5
101cc: fe442787 flw fa5,-28(s0)
101d0: 10f777d3 fmul.s fa5,fa4,fa5
101d4: c02797d3 fcvt.l.s a5,fa5,rtz
101d8: fef43423 sd a5,-24(s0)
101dc: fe843783 ld a5,-24(s0)
101e0: 2781 sext.w a5,a5
101e2: 853e mv a0,a5
101e4: 60e2 ld ra,24(sp)
101e6: 6442 ld s0,16(sp)
101e8: 6105 addi sp,sp,32
101ea: 8082 ret
....
...

gdb 查看CPU寄存器

-print $x0
-p $x0
-p $pc
-info reg
-display $x0 ;每跑一步都会显示寄存器
-ni ;next inst

IPC进程通信、消息队列

计算机进程通信原理、IPC、消息队列,

常见的方法

  • 共享内存模式
  • 消息传递模式
  • 共享文件模式

主要使用的是前两种方法

多处理器之间通信

处理器之间不能通过消息传递模式通信,只能共享内存
处理器本身是异步执行的,消息队列没办法实现(接受消息和消耗消息有可能会冲突)
而共享内存的方法也有可能会导致冲突,可以为不同处理器划分不同地址空间来避免冲突

01

单处理器进程通信

进程之间的通信最常见最安全的方法就是消息队列。
每个进程会维护一个消息队列(常见的是FIFO队列,也有带优先级的队列本文不做讨论)

02

进程A给进程B发消息,首先获取进程B消息队列(链表)的地址,将消息插入到链表后面。
当进程B执行是会去取消息队列最前面的消息然后执行。
由于进程A进程B是在同一处理器上占不同的时间片(跟操作系统进程的调度算法有关),
同一时刻消息队列不可能即recive 又 consume,所以不存在队列读写冲突的问题。

消息可以是简单的数据编码标志,也可以携带数据(数据起始地址以及长度),或者其他数据结构体格式

03

scala 的Actor的消息传递也是用此方式实现。