英伟达深度学习加速器开源项目nvdla(NVIDIA Deep Learning Accelerator)中用到了一个python脚本epython。源代码地址:https://github.com/nvdla/hw/blob/master/tools/bin/epython 。epython全称embeded python utility,简单地说,就是用来预处理verilog文件中嵌入的python脚本。利用python语法的简洁性,来自动生成一些重复的、有规律的代码。
提出问题
我们在写Verilog代码时,状态机的编码一般是由parameter定义,如下:
localparam IDLE = 3'd0;
localparam START = 3'd1;
...
localparam START = 3'd7;
如果中间需要插入一个新状态,就可能要修改两部分。一是位宽可能加1,二是新插入的后面所有的状态都需要加1。
二进制、十进制、十六进制,可以利用Vim或Emacs列编辑、数据填充、格式化来重新编码。如果是格雷码或者独热码就会麻烦很多。
那么有没有一种比较简便的方法呢?
epython的一个应用
我们知道epython可以让verilog中嵌入python脚本,那么我们可以写一个脚本来做这个事情。
假设我们有若干个状态,用python的列表来描述就是:
st = ['IDLE', 'START', 'NEW', ‘DO_SOMETHING’, 'DONE']
那么我们是否可以仅跟据这个列表来自动产生localparam的定义呢?我们的目标是下面这个样子:
st = ['IDLE', 'START', 'NEW', 'DO_SOMETHING', 'DONE']
stat_gen()
我的方法步骤如下:
1. 计算列表长度,也就是状态个数stat_num
2. 计算状态编码的位宽,log2(stat_num),再向上取整
3. 把0, ..., stat_num-1进行编码转换,转成二、十、十六进制、gray、onehot等
4. 格式化输出
是不是很简单?
为了verilog中的嵌入python的简洁,我把主要的实现过程放在了python的module里,再import进来。最终手写verilog代码如下:
//:| import vloglib
//:|
//:| st = ['IDLE', 'FULL', 'WIN', 'PROT', 'WR_FULL', 'WR_WIN']
//:| vloglib.stat_gen(st, 'bin')
//:|
//:) epython: generated_beg
//:) epython: generated_end
执行epython后,如下:
//:| import vloglib
//:|
//:| st = ['IDLE', 'START', 'NEW', 'DO_SOMETHING', 'DONE']
//:| vloglib.stat_gen(st, 'bin')
//:|
//:) epython: generated_beg (DO NOT EDIT BELOW)
localparam IDLE = 3'b000;
localparam START = 3'b001;
localparam NEW = 3'b010;
localparam DO_SOMETHING = 3'b011;
localparam DONE = 3'b100;
//:) epython: generated_end (DO NOT EDIT ABOVE)
格雷码效果如下:
//:| import vloglib
//:|
//:| st = ['IDLE', 'START', 'NEW', 'DO_SOMETHING', 'DONE']
//:| vloglib.stat_gen(st, 'gray')
//:|
//:) epython: generated_beg (DO NOT EDIT BELOW)
localparam IDLE = 3'b000;
localparam START = 3'b001;
localparam NEW = 3'b011;
localparam DO_SOMETHING = 3'b010;
localparam DONE = 3'b110;
//:) epython: generated_end (DO NOT EDIT ABOVE)
独热码效果如下:
//:| import vloglib
//:|
//:| st = ['IDLE', 'START', 'NEW', 'DO_SOMETHING', 'DONE']
//:| vloglib.stat_gen(st, 'onehot')
//:|
//:) epython: generated_beg (DO NOT EDIT BELOW)
localparam IDLE = 5'b00001;
localparam START = 5'b00010;
localparam NEW = 5'b00100;
localparam DO_SOMETHING = 5'b01000;
localparam DONE = 5'b10000;
//:) epython: generated_end (DO NOT EDIT ABOVE)
是不是效果还可以?
vloglib.py代码分析
代码如下:
import os
import sys
import re
import math
import bin2gray2bin
def stat_gen(l_stat, code='dec'):
#calc bit width
stat_num = len(l_stat)
bit_width = math.ceil(math.log2(stat_num))
#get the max lenght of strings in list
l = [len(i) for i in l_stat]
maxlenth = max(l)
#print verilog code
for i in range(len(l_stat)):
n = ' localparam {}'.format(l_stat[i]).ljust(13 + maxlenth)
c = ''
if code == 'bin':
t = '{:b}'.format(i)
t = t.rjust(bit_width, '0')
c = ' = {}\'b{};'.format(bit_width, t)
elif code == 'dec':
c = ' = {}\'d{};'.format(bit_width, i)
elif code == 'hex':
t = '{:x}'.format(i)
t = t.rjust(math.ceil(bit_width/4), '0')
c = ' = {}\'h{};'.format(bit_width, t)
elif code == 'gray':
t = '{:b}'.format(i)
t = t.rjust(bit_width, '0')
t = bin2gray2bin.bin2gray(t, bit_width)
c = ' = {}\'b{};'.format(bit_width, t)
elif code == 'onehot':
if i == 0:
t = '0' * (stat_num - 1) + '1'
else:
t = t[1:] + '0'
c = ' = {}\'b{};'.format(stat_num, t)
print(n + c)
要点:
-
math是数学库,使用了log2()和ceil()两个函数来计算位宽
-
string的ljust(len)是指定宽度len,左对齐,不足的用空格填充。len由状态字符串的最大长度决定。
-
二、十、十六进制可以用sting.format()来格式化,再rjust(len)右对齐,填充‘0’
-
格雷码调用了一个第三方库bin2gray2bin
-
独热码采用字符串序列的拼接,来实现左移的效果
源码下载
下载地址:
https://github.com/chenfengrugao/vloglib
或者
git clone https://github.com/chenfengrugao/vloglib.git