如何覆盖/扩展Chisel信号命名规则?自定义Verilog信号前缀需求
这确实是个很实际的需求——毕竟很多团队都有自己固定的Verilog信号命名规范,Chisel默认的io_前缀确实不太适配。我这里有几个从局部到全局的解决方案,你可以根据自己的项目规模选择:
方案一:自定义Module基类(适合单个项目快速适配)
如果只是想在自己的项目里实现这个规则,不用改Chisel库源码,可以写一个自定义的Module基类,帮你自动处理端口命名:
import chisel3._ abstract class CustomModule extends Module { // 工具函数:给Input信号添加i_前缀 protected def i[T <: Data](data: T): T = { data.suggestName(s"i_${data.getSuggestedName}") data.asInput } // 工具函数:给Output信号添加o_前缀 protected def o[T <: Data](data: T): T = { data.suggestName(s"o_${data.getSuggestedName}") data.asOutput } // 重写io的生成,后续配合字符串替换去掉默认io前缀 override val io = IO(new Bundle {}) }
然后你写模块的时候就可以这么用:
class MyTop extends CustomModule { // 替换原来的Input(...)和Output(...) val io = IO(new Bundle { val addr = i(UInt(32.W)) val data_in = i(UInt(32.W)) val data_out = o(UInt(32.W)) val en = i(Bool()) }) // 模块逻辑示例 io.data_out := Mux(io.en, io.data_in, 0.U) }
最后一步去掉Chisel自动加上的io_前缀,生成Verilog时做简单的字符串替换:
object MyTopDriver extends App { val verilog = chisel3.util.experimental.getVerilogString(new MyTop) // 替换前缀,同时保留时钟复位的原始命名 val modifiedVerilog = verilog .replaceAll("io_i_", "i_") .replaceAll("io_o_", "o_") // 写入目标文件 import java.io._ val pw = new PrintWriter(new File("MyTop.v")) pw.write(modifiedVerilog) pw.close() }
这个方法简单粗暴,适合小项目快速落地,不需要深入Chisel底层。
方案二:扩展VerilogEmitter(库级全局修改,无需每个模块改代码)
如果你想让整个Chisel项目都自动遵循这个命名规则,不用每个模块都写i()/o(),那可以扩展Chisel的VerilogEmitter,从FIRRTL层面修改端口命名逻辑:
首先创建自定义Emitter:
import chisel3._ import chisel3.stage._ import firrtl._ import firrtl.ir._ class CustomVerilogEmitter extends VerilogEmitter { override def emitPort(port: Port, namespace: Namespace): String = { val originalName = port.name // 判断端口类型,跳过时钟复位信号 val newName = if (originalName.startsWith("io_")) { val baseName = originalName.drop(3) port.direction match { case Input if baseName != "clock" && baseName != "reset" => s"i_$baseName" case Output => s"o_$baseName" case _ => originalName // 保持时钟复位和其他特殊端口不变 } } else { originalName } // 调用父类方法生成端口代码,传入修改后的名字 super.emitPort(port.copy(name = newName), namespace) } }
然后在生成Verilog时指定使用这个Emitter:
object CustomDriver extends App { val args = Array("--target-dir", "generated") Driver.execute(args, () => new MyTop) { (firrtlCompiler, firrtlCircuit) => val emitter = new CustomVerilogEmitter emitter.emit(firrtlCircuit, firrtlCompiler.getLoweredCircuit(firrtlCircuit)) } }
这个方案属于库级别的全局配置,一旦设置好,所有模块生成的Verilog都会自动替换前缀,非常适合大型项目或者需要统一规范的团队,而且不需要修改Chisel源码。
方案三:使用FIRRTL Transform(更灵活的全局修改)
如果还需要更复杂的命名规则(比如某些信号例外、按模块类型区分规则),可以写一个FIRRTL Transform来遍历所有端口并修改名字:
import firrtl._ import firrtl.ir._ import firrtl.transforms._ class PortRenameTransform extends Transform { override def prerequisites = Seq.empty override def optionalPrerequisites = Seq.empty override def optionalPrerequisiteOf = Seq.empty override def invalidates(a: Transform) = false override def execute(state: CircuitState): CircuitState = { val circuit = state.circuit val renamedModules = circuit.modules.map { case m: Module => val renamedPorts = m.ports.map { port => port.name match { case name if name.startsWith("io_") => val baseName = name.drop(3) port.direction match { case Input if baseName != "clock" && baseName != "reset" => port.copy(name = s"i_$baseName") case Output => port.copy(name = s"o_$baseName") case _ => port } case _ => port } } m.copy(ports = renamedPorts) case other => other } state.copy(circuit = circuit.copy(modules = renamedModules)) } }
然后在Driver中添加这个Transform:
object TransformDriver extends App { val args = Array("--target-dir", "generated") Driver.execute(args, () => new MyTop) { (compiler, circuit) => val transformedCircuit = compiler.transform(circuit, Seq(new PortRenameTransform)) VerilogEmitter.emit(transformedCircuit) } }
这个方法的优势是灵活性极高,你可以根据需求添加任意多的判断条件,比如给特定模块的端口设置不同前缀、跳过某些特殊信号等。
需要注意的是,不建议直接修改Chisel核心库的源码(比如修改默认的VerilogEmitter),这会导致你的项目依赖自定义的Chisel版本,后续升级会非常麻烦。上面的方案都是基于Chisel公开API实现的,兼容性更好,也更易维护。
内容的提问来源于stack exchange,提问作者Hoohoo




