1. Foreword
Chisel Style Guide是一份简单的代码编写指南,统一编码风格以及遵循一些Chisel的最佳实践统升团队对代码的可读性,提升代码质量。因为输入的Verilog的命名与Chisel命名有关,所以Chisel中的一些命名参考了Verilog编码规范。
2. Style
2.1. Name
2.1.1. Reg、Wire、Port、Module Instances
因为Chisel编译器在生成Verilog时,会在变量中添加下划线,所以Chisel强烈不建议用户以全小写下划线的风格命名变量。
- 小写驼峰命名。
- Reg需要用后缀标识。
- 输入和输出端口需要用in和out标识。
- 低电平有效加后缀n标识。
- 示例
class WriterIO(size: Int) extends Bundle {
val inWrite = Input(Bool())
val outFull = Output(Bool())
val rstn = Input(Bool()) // rstn默认输入,不用特别标识,_n表示低电平复位
val clk = Input(Bool()) // clk默认输入,不用特别标识
val din = Input(UInt(size.W)) // din习惯用法表示datain
val dout = Output(UInt(size.W)) // dout习惯用法表示dataout
var inWrdata = Input(UInt(size.W)) // wrdata习惯用法不用分开
var outRddata = Output(UInt(size.W)) // rddata习惯用法不用分开
}
val countReg = RegInit(0.U(32.W)) // 标识是Reg类型,与时序电路相关
val grayCounter = Module(new GrayCounter())
2.1.2. Module
模块名使用大写驼峰表示,命名尽量简单清晰。示例:
class MyCustomBundle extends Bundle {
...
}
// Companion object to MyCustomBundle
object MyCustomBundle {
...
}
2.1.3. Function
函数名使用小写驼峰表示。示例:
def isEven(n: Int): Boolean = (n % 2) == 0
def isOdd(n: Int): Boolean = !isEven(n)
2.1.4. constant value
常量使用全大写,单词下划线分隔。示例:
val CONST_VALUE = 0x5A.U(8.W)
val MASK_VALUE = 0xF0.U(8.W)
2.1.5. Blackbox Parameter
Blackbox的参数与Verilog保持一直,全大写,下划线分隔。示例:
class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE",
"IOSTANDARD" -> "DEFAULT")) {
}
2.1.6. Package
包名与目录名保持一样,全小写,即同一目录下所有scala文件属于同一个Package。示例:
package cmm
package utils
2.1.7. Other
关于全大写缩写词,推荐以下风格。
Prose form | UpperCamelCase | lowerCamelCase | Incorrect |
find GCD | FindGcd | findGcd | findGCD |
state for FSM | StateForFsm | stateForFsm | stateForFSM |
mock dut | MockDut | mockDut | MockDUT |
FIFO Generator | FifoGenerator | fifoGenerator | FIFOGenerator |
2.2. Comments
Chisel的注释与C/Java等一样。示例:
// This is a single-line comment
val myVar = 42 // This is also a single-line comment
/*
* This is a
* multi-line
* comment.
*/
val myVar = 42
2.3. Formatting
2.3.1. indentation
Chisel的缩进保持和Scala一致,2个空格。VSCode中安装Chisel/Scala的语法插件,自动实现2空格缩进排版。
示例:
class YsCounter extends Module {
val io = IO(new Bundle {
val outCount = Output(UInt(32.W))
val inSig = Input(Bool())
})
val countReg = RegInit(0.U(32.W))
when (inSig) {
countReg := 0.U
}.otherwise {
countReg := count_f + 1.U
when (count_reg >= 3.U) {
countReg := 3.U
}
}
io.outCount := countReg
}
2.4. Curly Braces
Chisel追求紧凑的代码风格,{不换行。示例:
when (inSig) {
countReg := 0.U
}.otherwise {
countReg := count_f + 1.U
when (count_reg >= 3.U) {
countReg := 3.U
}
}
2.4.1. Blank
when/switch等关键字后空一格,操作符左右各空一格。示例:
when (inSig) {
countReg := 0.U
}.otherwise {
countReg := countReg + 1.U
when (count_reg >= 3.U) {
countReg := 3.U
}
}
2.5. File Name
文件和主模块名保持一致,一个文件内可以有多个模块。
3. Best Practices
3.1. Comments
添加有意义的注释,无意义的注释不要添加。注释是为了表达为什么这样做,不是表达做了什么。
3.2. Reg
Chisel的Reg默认自带Reset复位以及时钟信息上升沿取值,所以不要再手动判断上升沿来更新Reg变量。
3.3. Gray Code
在信号质量要求的场景中使用格雷码编码。
3.4. Sequential Logic
一般情况下使用同步时钟电路,优先使用非阻塞赋值方式。
3.5. Combinational Logic
组合逻辑不要太长,过长的组合逻辑增加电路延迟,影响电路稳定。可以将长组合逻辑分解为时序逻辑电路。
3.6. Delay
设计代码中不要使用延时,延时代码无法综合。
3.7. Bulk Connection
能用块连接的地方尽量使用块连接,提升效率,降低误接的风险。
3.8. Explicit Width
val cntReg = RegInit(0.U(4.W)) // Good
val cntReg = RegInit(0.U) // Bad
3.9. Advanced Feature
枚举、派生、参数化、函数式编程等高级特性的运用,可以提升代码的表达能力,提升编码效率。
3.10. Verification
每一个Module都要有足够的验证,并且需要生成波形文件辅助验证。
3.11. printf
多用printf打印信息来增加代码的可调试性,如:
printf(p"width: ${Reg.getWidth}\n") // 打印寄存器的宽度
printf(p"value: ${Reg}\n") // 打印寄存器的值
3.12. Verilog
SystemVerilog的可综合性无法保证,所以默认都是生成Verilog,并且禁用随机化宏。示例:
object fsmMain extends App {
emitVerilog(
new SimpleFsm,
Array(
"--emission-options=disableMemRandomization,disableRegisterRandomization",
"--target-dir=hdl",
"--full-stacktrace"
)
)
}