0. 前言
- 本篇博客主要的内容都是在阅读difuzz框架,由于目前docker用不了,我用的是processorfuzz的docker镜像进行阅读讲解。
- 本博客最核心关注的点在于:
- 如何生成risc-v处理器,如rocket-chip,boom的处理器指令?
- 如何利用cocotb把生成的处理器指令放入这些设计中进行仿真?
1. 框架设置
- 打开github,搜索ProcessorFuzz,下载对应的docker。
- docker镜像拉取的方法用:docker load < processorfuzz_docker_img.tar。
- 根据自己的需要设置开发路径和方法。
2. 框架阅读
2.1. 处理器指令是怎么生成的?
difuzz主要是通过类rvMutator
进行指令生成,生成的指令类型是RV64G:self.inst_generator = rvInstGenerator('RV64G')
。
处理器指令分成三个部分,分别是PREFIX,MAIN和SUFFIX。PREFIX和SUFFIX的数量分别是3和5:
self.num_prefix = 3
self.num_words = 100
self.num_suffix = 5
2.1.q1 问题:为什么用的是RV64G?
- 通用性与完整性:RV64G 是一个通用的、功能完整的 RISC-V 指令集,包含了整数运算、浮点运算、原子操作等指令,适合广泛的应用场景。
2.1.q2. 问题:哪里看Rocket使用的是RV64G的指令集
2.1.1. PREFIX指令
这里的words指的是一个系列处理器指令的集合;因为如PREFIX的指令,它会随机从rv_zicsr
中选择一个opcode:
def get_word(self, part):
if part == PREFIX:
opcode = random.choice(list(rv_zicsr.keys()))
label_num = self.prefix_num
self.prefix_num += 1
从rv_zicsr
中选择一个后opcode
后,这个opcode
需要在opcodes_words里面匹配和其对应的指令,如csrrw
(属于rv_zicsr),它在csr_r
指令中
# csr_r: CSR Read/Write Instructions
# - 处理 CSR 读/写指令,如 'csrrw', 'csrrs', 'csrrc'
# - 指令格式: 'csrrw rd, csr, rs1'
# - 操作数:
# - rd: 目标寄存器,存储读取的 CSR 值
# - csr: CSR 寄存器地址
# - rs1: 源寄存器,提供要写入 CSR 的新值
'csr_r': (['csrrw', 'csrrs', 'csrrc'], word_csr_r),
他则需要按照方法word_csr_r
处理周围的指令,包括在这个word里面添加如insts内的这些指令:
def word_csr_r(opcode, syntax, xregs, fregs, imms, symbols):
csr = random.choice(csr_names)
if 'pmpaddr' in csr:
tpe = MEM_R
insts = [ 'la xreg1, symbol',
'srai xreg1, xreg1, 1',
syntax.format(csr) ]
symbols.append('symbol')
else:
tpe = CSR
insts = [ 'xor xreg1, xreg1, xreg1']
for i in range(random.randint(0, 3)):
set_bits = random.choice([1, 3])
offset = random.randint(0, 31)
insts = insts + \
['addi xreg{}, zero, {}'.format(i+2, set_bits),
'slli xreg{}, xreg{}, {}'.format(i+2, i+2, offset),
'add xreg1, xreg1, xreg{}'.format(i+2)
]
xregs.append('xreg{}'.format(i+2))
insts.append(syntax.format(csr))
return (tpe, insts)
总结PREFIX指令的生成就是:
- 随机从
rv_zicsr
中选择一个opcode - 在opcodes_words里面匹配和其对应的指令
- 在这个word里面添加如insts内的这些指令
2.1.1q1 rv_zicsr是什么?
在 RISC-V 架构中,zicsr 是一个标准的指令集扩展,专门用于控制和状态寄存器(CSR,Control and Status Registers)的操作。该扩展定义了一组指令,用于访问和操作这些寄存器,主要用于系统级编程和操作系统开发。
详细解释
Z:在 RISC-V 的指令集命名中,Z 表示一个标准的子集或扩展。
I:表示此扩展与整数指令集(I)相关联。
CSR:指 Control and Status Registers,也就是控制和状态寄存器。
2.1.1q2 为什么要在启动的时候调用rv_zicsr?是不是用risc-v编译.c文件的PREFIX都是rv_zicsr?
2.1.2. MAIN指令
MAIN指令则从全部指令中随机选择一个。
首先,全部的指令包括’trap_ret’,‘rv32i’,‘rv64i’,‘rv32a’,‘rv64a’,‘rv32f’,‘rv64f’,‘rv_zifencei’,‘rv_zicsr’,‘rv32m’,‘rv64m’中的总计118个指令,这些指令都会放在self.opcodes_map中。
假设我们在随机选择中选中了指令’sret’,它是最简单的指令之一,用于从操作系统的超级用户模式(Supervisor Mode)返回到用户模式(User Mode)。
对于所有的’ret’指令,都需要遵循word_ret的规则:
# ret: Return Instructions
# - 处理返回指令,如 'mret', 'sret', 'uret'
# - 指令格式: 'mret'(机器模式返回), 'sret'(超级用户模式返回), 'uret'(用户模式返回)
'ret': (['mret', 'sret', 'uret'], word_ret),
def word_ret(opcode, syntax, xregs, fregs, imms, symbols):
tpe = CF_RET
if syntax == 'mret': epc = 'mepc'
elif syntax == 'sret': epc = 'sepc'
else: epc = 'uepc'
insts = [ 'la xreg0, symbol',
'csrrw zero, {}, xreg0'.format(epc),
syntax ]
xregs.append('xreg0')
symbols.append('symbol')
return (tpe, insts)
2.1.3. SUFFIX
略,大概的流程和上面一样。
2.2. 使用words构建.si(simulation input?), .S(assembly), .symbols文件等
这个构建的流程大致是这样:
- 先把之前的data(就是之前生成的random data,直接存到内存中的)存到.si文件。
- 用现在得到的words+Template(使用的是rv64-p-u.S??)构建assembly文件(.input_1_gen.S)。
- 用这个指令
riscv64-unknown-elf-gcc -march=rv64g -mabi=lp64 -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles -I Template/include -T Template/include/link.ld -I Template/include/p batch1/tests/.input_1_gen.S -o batch1/tests/.input_1_gen.elf
,来构建elf文件以及symbols文件。 - 然后用
riscv64-unknown-elf-elf2hex --bit-width 64 --input batch1/tests/.input_1_gen.elf --output batch1/tests/.input_1_gen.hex
,来构建hex文件。 - 用于rtl仿真的最主要的就是hex文件了。
2.2.q1. 为什么要设置6个num_data_sections
for n in range(num_data_sections):
start = n * section_size
end = start + section_size
if '_random_data{}'.format(n) in line:
k = 0
for i in range(start, end, 2):
label = ''
if i > start + 2 and i < end - 4:
label = 'd_{}_{}:'.format(n, k)
k += 1
assembly.append('{:<16}.dword 0x{:016x}, 0x{:016x}\n'.\
format(label, data[i], data[i+1]))
2.3. 如何使用hex文件来进行RTL的仿真
- 首先需要读取symbols文件里的各个变量的值,这些值标定了各个值在内存的位置。
- 然后设置bootrom,并也把bootrom的内容load进memory中,大致的意思应该是设置一些关键的状态和地址,以便引导过程能顺利进行。这些寄存器可能包括程序计数器(PC)、堆栈指针(SP)、硬件线程 ID(hart ID)等。
- 同时跳转到内存对应的_start位置,准备执行对应的代码:
def set_bootrom(self):
bootrom_addrs = []
memory = {}
bootrom = [ 0x00000297, # auipc t0, 0x0
0x02028593, # addi a1, t0, 32
0xf1402573, # csrr a0, mhartid
0x0182b283, # ld t0, 24(t0)
0x00028067, # jr t0
0x00000000, # no data
0x80000000, # Jump address
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000 ] # no data
for i in range(0, len(bootrom), 2):
bootrom_addrs.append(0x10000 + i * 4)
memory[0x10000 + i * 4] = (bootrom[i+1] << 32) | bootrom[i]
return (bootrom_addrs, memory)
- 这段代码的意思应该是把bootrom相关的代码都load进了memory中,然后记录了bootrom相关的地址,以便在启动的时候直接使用。
- 然后经过了一段的reset等操作后之后,终于到往memory中注入程序的时候。
2.3.1. 如何用目前得到的memory进行仿真
- 它们的框架构造了一个Rocket Tile Link的Slave,也即响应Tilelink的请求,向Tilelink发送memory内的内容的“模拟总线”。
- 用目前获取的memory信息进行仿真的代码如下所示:
def drive_input(self, memory):
assert memory.__class__.__name__ == 'dict', \
'tlAdapter.drive_input need dict'
block_perm = {}
# TODO, check the resolution of block permissions
for addr in set([i & self.block_mask for i in memory.keys()]):
block_perm[addr] = TIP
self.b_queue.clear()
self.d_queue.clear()
d_sink_list = [i for i in range(0, 4)]
d_sinks = FreeList('d_sinks', d_sink_list)
b_src_list = [i for i in range(0, 1)] # TODO, BoomTile has 3 b_src
b_srcs = FreeList('b_srcs', b_src_list)
b_callback = srcToCallback('b_callback', b_src_list)
self.a_monitor = cocotb.fork(self.a_port_monitor(memory, block_perm, d_sinks, \
b_srcs, b_callback))
self.c_monitor = cocotb.fork(self.c_port_monitor(memory, block_perm, b_srcs, \
b_callback))
self.e_monitor = cocotb.fork(self.e_port_monitor(memory, d_sinks))
self.d_driver = cocotb.fork(self.d_port_driver())
self.b_driver = cocotb.fork(self.b_port_driver())
self.retriever = cocotb.fork(self.data_retriever(block_perm, b_srcs, b_callback))
self.host_if = cocotb.fork(self.host_interface(block_perm, b_srcs, b_callback))