Skip to content

Commit

Permalink
add support for Verilog Bin/Hex standard file initialization
Browse files Browse the repository at this point in the history
  • Loading branch information
soronpo committed Aug 15, 2024
1 parent e153a41 commit 81a09db
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 65 deletions.
50 changes: 50 additions & 0 deletions compiler/ir/src/main/scala/dfhdl/compiler/ir/DFType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dfhdl.internals.*

import scala.collection.immutable.{ListMap, ListSet}
import scala.reflect.ClassTag
import scala.util.boundary, boundary.break

sealed trait DFType extends Product, Serializable, HasRefCompare[DFType] derives CanEqual:
type Data
Expand Down Expand Up @@ -112,6 +113,55 @@ final case class DFBits(widthParamRef: IntParamRef) extends DFType:

object DFBits extends DFType.Companion[DFBits, (BitVector, BitVector)]:
def apply(width: Int): DFBits = DFBits(IntParamRef(width))
def dataFromBinString(
bin: String
): Either[String, (BitVector, BitVector)] = boundary {
val (valueBits, bubbleBits) =
bin.foldLeft((BitVector.empty, BitVector.empty)) {
case (t, '_' | ' ') => t // ignoring underscore or space
case ((v, b), c) =>
c match // bin mode
case '?' => (v :+ false, b :+ true)
case '0' => (v :+ false, b :+ false)
case '1' => (v :+ true, b :+ false)
case x =>
break(Left(s"Found invalid binary character: $x"))
}
Right((valueBits, bubbleBits))
}
private val isHex = "[0-9a-fA-F]".r
def dataFromHexString(
hex: String
): Either[String, (BitVector, BitVector)] = boundary {
val (valueBits, bubbleBits, binMode) =
hex.foldLeft((BitVector.empty, BitVector.empty, false)) {
case (t, '_' | ' ') => t // ignoring underscore or space
case ((v, b, false), c) =>
c match // hex mode
case '{' => (v, b, true)
case '?' => (v ++ BitVector.low(4), b ++ BitVector.high(4), false)
case isHex() =>
(
v ++ BitVector.fromHex(c.toString).get,
b ++ BitVector.low(4),
false
)
case x =>
break(Left(s"Found invalid hex character: $x"))
case ((v, b, true), c) =>
c match // bin mode
case '}' => (v, b, false)
case '?' => (v :+ false, b :+ true, true)
case '0' => (v :+ false, b :+ false, true)
case '1' => (v :+ true, b :+ false, true)
case x =>
break(Left(s"Found invalid binary character in binary mode: $x"))
}
if (binMode) Left(s"Missing closing braces of binary mode")
else Right((valueBits, bubbleBits))
}
end DFBits

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
Expand Down
228 changes: 227 additions & 1 deletion compiler/ir/src/main/scala/dfhdl/compiler/ir/InitFileFormat.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,230 @@
package dfhdl.compiler.ir
import scala.io.Source
import scala.util.matching.Regex
import java.io.FileNotFoundException
import scala.util.control.Exception._
import dfhdl.internals.CommonOps.bitsWidth
import dfhdl.internals.*

enum InitFileFormat derives CanEqual:
case Auto, VerilogBin, VerilogHex, AMDXilinxCOE, AMDXilinxMEM, IntelAlteraMIF, IntelAlteraHEX
case Auto, VerilogBin, VerilogHex
// AMDXilinxCOE, IntelAlteraMIF, IntelAlteraHEX
// LatticeMEM, AMDXilinxMEM

object InitFileFormat:
import InitFileFormat.*
def readInitFile(
fileName: String,
fileFormat: InitFileFormat,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
val source =
try Source.fromResource(fileName)
catch
case _: FileNotFoundException =>
try Source.fromFile(fileName)
catch
case _: FileNotFoundException =>
throw new IllegalArgumentException(
s"Init file not found: $fileName\nmake sure either to place the file in your Scala project resource folder or provide a proper relative/absolute path."
)

val fileContents = source.getLines().mkString("\n")
val detectedFormat = fileFormat match
case Auto => detectAutoFormat(fileName, fileContents, dataWidth)
case _ => fileFormat
try
(detectedFormat: @unchecked) match
case VerilogBin => readVerilogBin(fileContents, arrLen, dataWidth)
case VerilogHex => readVerilogHex(fileContents, arrLen, dataWidth)
// case AMDXilinxCOE => readAMDXilinxCOE(fileContents, arrLen, dataWidth)
// case IntelAlteraMIF => readIntelAlteraMIF(fileContents, arrLen, dataWidth)
// case IntelAlteraHEX => readIntelAlteraHEX(fileContents, arrLen, dataWidth)
// case AMDXilinxMEM => readAMDXilinxMEM(fileContents, arrLen, dataWidth)
// case LatticeMEM => readLatticeMEM(fileContents, arrLen, dataWidth)
catch
case e: DataError =>
import e.*
throw new IllegalArgumentException(
s"Init file error detected in $detectedFormat formatted ${fileName}:$lineNum\n$msg"
)
end try
end readInitFile

private val verilogCommentPattern = """//.*|/\*.*?\*/""".r
private val validBinPattern =
"[01]+".r // currently not supporting [xXzZ_] characters for simplification
private val validHexPattern =
"[0-9a-fA-F]+".r // currently not supporting [xXzZ_] characters for simplification
class DataError(val msg: String, val lineNum: Int) extends IllegalArgumentException(msg)
extension (content: String)
private def contentCleanup(
singleLineCommentPattern: String = "",
multiLineCommentPattern: String = ""
): String =
val replaceWithNewlines = (comment: String) =>
val lineCount = comment.count(_ == '\n') // Count the number of newlines in the comment
"\n" * lineCount // Replace with the same number of newline characters
// first, remove multiline comments
val noMultiLineComments =
if (multiLineCommentPattern.isEmpty) content
else multiLineCommentPattern.r.replaceAllIn(content, m => replaceWithNewlines(m.matched))
// second, remove singleline comments
val noSingleLineComment =
if (singleLineCommentPattern.isEmpty) noMultiLineComments
else singleLineCommentPattern.r.replaceAllIn(noMultiLineComments, " ")
// third, cleanup removing multi-spaces
val noMultiSpaces = noSingleLineComment.replaceAll("""[ \t]+""", " ").trim
// finally, trim all lines
noMultiSpaces.linesIterator.map(_.trim).mkString("\n")
end contentCleanup
private def verilogCleanup: String = contentCleanup(
singleLineCommentPattern = """//.*(\n|\r|\r\n)""",
multiLineCommentPattern = """/\*[\s\S]*?\*/"""
)
end extension

extension (word: String)
private def isDataBin: Boolean = validBinPattern.matches(word)
private def isDataHex: Boolean = validHexPattern.matches(word)

private def detectAutoFormat(
fileName: String,
fileContents: String,
dataWidth: Int
): InitFileFormat =
val suffix = fileName.split("\\.").last.toLowerCase()
suffix match
// case "coe" => AMDXilinxCOE
// case "mif" => IntelAlteraMIF
// case "mem" =>
// if ("""\#Format\s+=""".r.matches(fileContents)) LatticeMEM
// else AMDXilinxMEM
// case "hex" =>
// if (fileContents.startsWith(":")) IntelAlteraHEX
// else VerilogHex
case _ =>
val firstData =
fileContents.verilogCleanup.linesIterator
.filter(line => !line.startsWith("@") && line.nonEmpty)
.nextOption().getOrElse("").split(" ").headOption.getOrElse("")
if (firstData.isDataBin && firstData.length() == dataWidth) VerilogBin
else if (firstData.isDataHex) VerilogHex
else
throw new IllegalArgumentException(
s"Could not automatically detect the init file format of $fileName"
)
end match

end detectAutoFormat

private def readVerilogStdFile(
contents: String,
arrLen: Int,
dataWidth: Int,
isBinary: Boolean
): Vector[(BitVector, BitVector)] =
// array initialization with everything as invalid
val bubbleCell = (BitVector.low(dataWidth), BitVector.high(dataWidth))
val result = Array.fill(arrLen)(bubbleCell)
// starting at address zero
// in the verilog standard the address refers to the array index
var currentAddress = 0
contents.verilogCleanup.linesIterator.zipWithIndex.forall {
case (line, lineNum) if currentAddress < arrLen =>
if (line.nonEmpty)
line.split(" ").foreach { word =>
def invalidDataCharacterError() =
throw new DataError(s"Invalid data character detected: $word", lineNum)
def invalidDataWidthError(wordWidth: Int) = throw new DataError(
s"Invalid data width detected (expected $dataWidth but found $wordWidth): $word",
lineNum
)
// address
if (word.startsWith("@"))
val addressStr = word.drop(1)
catching(classOf[NumberFormatException]).opt(Integer.parseInt(addressStr, 16)) match
case Some(addr) if addr < arrLen => currentAddress = addr
case _ => throw new DataError(s"Invalid address specification: $word", lineNum)
// binary data
else if (isBinary)
if (!word.isDataBin) invalidDataCharacterError()
val dfhdlWord = word.replaceAll("[zZxX]", "?")
val wordData = DFBits.dataFromBinString(dfhdlWord).toOption.get
val wordWidth = wordData._1.lengthOfValue.toInt
// for binary we require exact character width
if (dfhdlWord.length != dataWidth) invalidDataWidthError(wordWidth)
result(currentAddress) = wordData
currentAddress += 1
// hexadecimal data
else
if (!word.isDataHex) invalidDataCharacterError()
val dfhdlWord = word.replaceAll("[zZxX]", "?")
val wordData = DFBits.dataFromHexString(dfhdlWord).toOption.get
val wordWidth = wordData._1.lengthOfValue.toInt
// hex words can be resized if they are smaller than the expected data width
if (wordWidth > dataWidth) invalidDataWidthError(wordWidth)
result(currentAddress) =
(wordData._1.resize(dataWidth), wordData._2.resize(dataWidth))
currentAddress += 1
end if
}
end if
true
case _ => false // already reached the end of the array, so stopping loop
}
result.toVector
end readVerilogStdFile

/* https://docs.amd.com/r/2023.1-English/ug901-vivado-synthesis/Initializing-RAM-Contents */
/* https://docs.amd.com/r/2023.1-English/ug901-vivado-synthesis/Specifying-RAM-Initial-Contents-in-an-External-Data-File */
/* https://docs.amd.com/r/2023.1-English/ug901-vivado-synthesis/Initializing-Block-RAM-From-an-External-Data-File-Verilog */
private def readVerilogBin(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
readVerilogStdFile(contents, arrLen, dataWidth, isBinary = true)
private def readVerilogHex(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
readVerilogStdFile(contents, arrLen, dataWidth, isBinary = false)
/* https://docs.amd.com/r/en-US/ug896-vivado-ip/COE-File-Syntax */
private def readAMDXilinxCOE(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
???
/* https://docs.amd.com/r/en-US/ug1580-updatemem/Memory-Files */
private def readAMDXilinxMEM(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
???
/* https://www.intel.com/content/www/us/en/programmable/quartushelp/current/index.htm#reference/glossary/def_mif.htm */
private def readIntelAlteraMIF(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
???
/* https://www.intel.com/content/www/us/en/programmable/quartushelp/current/index.htm#reference/glossary/def_hexfile.htm */
private def readIntelAlteraHEX(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
???
/* https://www.gsp.com/cgi-bin/man.cgi?topic=srec_mem */
private def readLatticeMEM(
contents: String,
arrLen: Int,
dataWidth: Int
): Vector[(BitVector, BitVector)] =
???
end InitFileFormat
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ class PrintCodeStringSpec extends StageSpec:
val DATA_WIDTH: Int <> CONST = 4,
val ADDR_WIDTH: Int <> CONST = 4
) extends EDDesign:
val ram = Bits(DATA_WIDTH) X (2 ** ADDR_WIDTH) <> VAR.SHARED initFile "test"
val ram = Bits(DATA_WIDTH) X (2 ** ADDR_WIDTH) <> VAR.SHARED

val a, b = new EDDomain:
val clk = Bit <> IN
Expand All @@ -739,7 +739,7 @@ class PrintCodeStringSpec extends StageSpec:
| val DATA_WIDTH: Int <> CONST = 4,
| val ADDR_WIDTH: Int <> CONST = 4
|) extends EDDesign:
| val ram = Bits(DATA_WIDTH) X (2 ** ADDR_WIDTH) <> VAR.SHARED initFile "test"
| val ram = Bits(DATA_WIDTH) X (2 ** ADDR_WIDTH) <> VAR.SHARED
| val a = new EDDomain:
| val clk = Bit <> IN
| val data = Bits(DATA_WIDTH) <> IN
Expand Down
57 changes: 5 additions & 52 deletions core/src/main/scala/dfhdl/core/DFBits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,62 +104,15 @@ object DFBits:

object StrInterp:
private[DFBits] val widthExp = "([0-9]+)'(.*)".r
def fromBinString(
bin: String
): Either[String, (BitVector, BitVector)] = boundary {
val (valueBits, bubbleBits) =
bin.foldLeft((BitVector.empty, BitVector.empty)) {
case (t, '_' | ' ') => t // ignoring underscore or space
case ((v, b), c) =>
c match // bin mode
case '?' => (v :+ false, b :+ true)
case '0' => (v :+ false, b :+ false)
case '1' => (v :+ true, b :+ false)
case x =>
break(Left(s"Found invalid binary character: $x"))
}
Right((valueBits, bubbleBits))
}
private[DFBits] val isHex = "[0-9a-fA-F]".r
def fromHexString(
hex: String
): Either[String, (BitVector, BitVector)] = boundary {
val (valueBits, bubbleBits, binMode) =
hex.foldLeft((BitVector.empty, BitVector.empty, false)) {
case (t, '_' | ' ') => t // ignoring underscore or space
case ((v, b, false), c) =>
c match // hex mode
case '{' => (v, b, true)
case '?' => (v ++ BitVector.low(4), b ++ BitVector.high(4), false)
case isHex() =>
(
v ++ BitVector.fromHex(c.toString).get,
b ++ BitVector.low(4),
false
)
case x =>
break(Left(s"Found invalid hex character: $x"))
case ((v, b, true), c) =>
c match // bin mode
case '}' => (v, b, false)
case '?' => (v :+ false, b :+ true, true)
case '0' => (v :+ false, b :+ false, true)
case '1' => (v :+ true, b :+ false, true)
case x =>
break(Left(s"Found invalid binary character in binary mode: $x"))
}
if (binMode) Left(s"Missing closing braces of binary mode")
else Right((valueBits, bubbleBits))
}

extension (fullTerm: String)
private[DFBits] def interpolate[W <: IntP](
op: String,
explicitWidthOption: Option[IntP]
)(using DFC): DFConstOf[DFBits[W]] =
val fromString = op match
case "b" => fromBinString(fullTerm)
case "h" => fromHexString(fullTerm)
case "b" => ir.DFBits.dataFromBinString(fullTerm)
case "h" => ir.DFBits.dataFromHexString(fullTerm)
var (valueBits, bubbleBits) = fromString.toOption.get
explicitWidthOption.foreach(ew =>
val updatedWidth = IntParam.forced(ew).toScalaInt
Expand All @@ -182,8 +135,8 @@ object DFBits:
case Literal(StringConstant(t)) =>
val opStr = opExpr.value.get
val res = opStr match
case "b" => fromBinString(t)
case "h" => fromHexString(t)
case "b" => ir.DFBits.dataFromBinString(t)
case "h" => ir.DFBits.dataFromHexString(t)
res match
case Right((valueBits, bubbleBits)) =>
explicitWidthTpeOption match
Expand Down Expand Up @@ -213,7 +166,7 @@ object DFBits:

// Unclear why, but the compiler crashes if we do not separate these definitions from StrInterp
object StrInterpOps:
import StrInterp.{fromBinString, fromHexString, interpolate, isHex, widthExp}
import StrInterp.{interpolate, isHex, widthExp}
opaque type BinStrCtx <: StringContext = StringContext
object BinStrCtx:
extension (inline sc: BinStrCtx)
Expand Down
Loading

0 comments on commit 81a09db

Please sign in to comment.