【SpinalHDL快速入门】三、Scala 快速入门

news/2024/12/22 21:50:14/

SpinalHDL本质上来讲是Scala语言的一个库,所以需要先学习Scala,才能在此基础上学习SpinalHDL。

文章目录

    • Scala 基础
      • Scala 数据类型(5种:Boolean、Int、Float、Double、String)
      • Scala Variables
      • Scala Functions
        • Return
        • Return类型推断
        • 花括号
        • 返回值为空的函数
        • 参数默认值
        • Apply 函数
      • Object(静态类)
      • 入口函数main
      • class
        • 继承(Inheritance):override
        • Case class
        • 模板 / 类型参数化(Templates / Type parameterization)
    • Scala 编码规范
      • 使用class还是case class
      • [case] class:所有类名应以大写字母开头
      • companion object(伴生对象):伴生对象应该以大写字母开头
      • function:函数名始终以小写字母开头
      • instances(实例):类的实例应该始终以小写字母开头
      • if / when
      • switch
      • Parameters
    • Scala 交互
      • 介绍
      • SpinalHDL在API背后的工作原理
      • 一切皆为引用
      • 硬件类型
        • RGB example
      • 生成的 RTL 中的信号名称
      • Scala 用于elaboration,SpinalHDL 用于硬件描述(如:可以使用Boolean,而无法使用Bool)
      • Scala elaboration能力(if,for,函数式编程)

变量和函数应该定义在object、class或function中,不能在 Scala 文件的根目录下定义。

Scala 基础

Scala 数据类型(5种:Boolean、Int、Float、Double、String)

在这里插入图片描述

Scala Variables

在Scala中,您可以使用var关键字定义变量:

var number : Int = 0
number = 6
number += 4
println(number) // 10

Scala 能够自动推断类型。如果变量在声明时被赋值,您无需指定其类型:

var number = 0 //The type of 'number' is inferred as an Int during compilation

然而,在Scala中使用var并不是很常见。相反,通常会使用由val定义的常量值

val two = 2
val three = 3
val six = two * three

Scala Functions

例如,如果您想定义一个函数,该函数返回true,如果其两个参数的和大于零,则可以按照以下方式进行:

def sumBiggerThanZero(a: Float, b: Float): Boolean = {return (a + b) > 0
}

然后,要调用这个函数,你可以写:

sumBiggerThanZero(2.3f, 5.4f)

你也可以通过名称指定参数,如果有很多参数的话这是非常有用的:

sumBiggerThanZero(a = 2.3f,b = 5.4f
)

Return

return关键字并不是必须的。如果没有它,Scala会将函数中最后一个语句作为返回值

def sumBiggerThanZero(a: Float, b: Float): Boolean = {(a + b) > 0
}

Return类型推断

Scala 能够自动推断返回类型,您不需要指定它:

def sumBiggerThanZero(a: Float, b: Float) = {(a + b) > 0
}

花括号

如果你的函数只包含一条语句,Scala 函数不需要花括号

def sumBiggerThanZero(a: Float, b: Float) = (a + b) > 0

返回值为空的函数

如果你想让一个函数返回空值,那么它的返回类型应该设置为 Unit。这相当于 C/C++ 中的 void 类型。

def printer(): Unit = {println("1234")println("5678")
}

参数默认值

您可以为函数的每个参数指定默认值:

def sumBiggerThanZero(a: Float, b: Float = 0.0f) = {(a + b) > 0
}

Apply 函数

名为apply的函数很特殊,因为你可以在不必输入它们的名称的情况下调用它们

class Array() {def apply(index: Int): Int = index + 3
}
val array = new Array()
val value = array(4) //array(4) 被解释为 array.apply(4),将返回 7。

Object(静态类)

在Scala中,没有静态关键字。取而代之的是object。在对象定义内部定义的所有内容都是静态的。

以下示例定义了一个名为pow2的静态函数,它以浮点值作为参数,并返回一个浮点值。

object MathUtils {def pow2(value: Float): Float = value * value
}//可以使用如下方式调用它:
MathUtils.pow2(42.0f)

入口函数main

Scala程序的入口点(即主函数)应该定义在一个 object 内,作为名为main的函数

object MyTopLevelMain{def main(args: Array[String]) {println("Hello world")}
}

class

类语法与Java非常相似。假设您想定义一个颜色类,该类以三个浮点值(r、g、b)作为构造参数

class Color(r: Float, g: Float, b: Float) {def getGrayLevel(): Float = r * 0.3f + g * 0.4f + b * 0.4f
}

接下来,实例化前面示例中的类并使用它的getGrayLevel函数:

val blue = new Color(0, 0, 1)
val grayLevelOfBlue = blue.getGrayLevel()

注意,如果你想从外部访问类的构造参数,这个构造参数应该被定义为val:

class Color(val r: Float, val g: Float, val b: Float) { ... }
...
val blue = new Color(0, 0, 1)
val redLevelOfBlue = blue.r

继承(Inheritance):override

举个例子,假设你想定义两个类,矩形和正方形,它们都继承自Shape类:(注意使用其中的override

class Shape {def getArea(): Float
}
class Square(sideLength: Float) extends Shape {override def getArea() = sideLength * sideLength
}
class Rectangle(width: Float, height: Float) extends Shape {override def getArea() = width * height
}

Case class

case class是声明类的另一种方式。

case class Rectangle(width: Float, height: Float) extends Shape {override def getArea() = width * height
}

case class和普通calss之间有一些区别:

  • case class不需要使用 new 关键字进行实例化
  • 构造参数可以从外部访问;您无需将它们定义为 val

在 SpinalHDL 中,这解释了编码规范的原因:通常建议使用case class而不是普通class,以减少输入量并增加连贯性

模板 / 类型参数化(Templates / Type parameterization)

假设你想设计一个给定数据类型的队列类,那么你需要为该类提供一个类型参数:

class Queue[T](){def push(that: T) : Unit = ...def pop(): T = ...
}

如果您想将 T 类型限制为给定类型(例如 Shape)的子类,可以使用 <: Shape 语法:

class Shape() {def getArea(): Float
}class Rectangle() extends Shape { ... }class Queue[T <: Shape]() {def push(that: T): Unit = ...def pop(): T = ...
}

函数也可以这样实现:

def doSomething[T <: Shape](shape: T): Something = { shape.getArea() }

Scala 编码规范

使用class还是case class

当您定义Bundle或Component时,最好将其声明为case class。原因如下:

  • 避免了使用new关键字
  • case类提供了一个clone函数。这在SpinalHDL中非常有用,例如,在定义新的Reg或某种新的Stream时需要克隆Bundle。
  • 构造参数可以直接从外部看到

[case] class:所有类名应以大写字母开头

class Fifo extends Component {}
class Counter extends Area {}
case class Color extends Bundle {}

companion object(伴生对象):伴生对象应该以大写字母开头

object Fifo {def apply(that: Stream[Bits]): Stream[Bits] = {...}
}
object MajorityVote {def apply(that: Bits): UInt = {...}
}

这个规则的一个例外是当伴生对象被用作函数(仅适用于内部),并且这些apply函数不会生成硬件时

object log2 {def apply(value: Int): Int = {...}
}

function:函数名始终以小写字母开头

def sinTable = (0 until sampleCount).map(sampleIndex => {val sinValue = Math.sin(2 * Math.PI * sampleIndex / sampleCount)S((sinValue * ((1 << resolutionWidth) / 2 - 1)).toInt, resolutionWidth bits)
})
val rom = Mem(SInt(resolutionWidth bits), initialContent = sinTable)

instances(实例):类的实例应该始终以小写字母开头

val fifo = new Fifo()
val buffer = Reg(Bits(8 bits))

if / when

Scala中的if和SpinalHDL中的when通常应该按照以下方式编写:

if(cond) {...
} else if(cond) {...
} else {...
}when(cond) {...
}.elseWhen(cond) {...
}.otherwise {...
}

例外情况包括:

  • 在 otherwise 前省略点是可以的。
  • 如果将 if/when 语句压缩到一行上可以使代码更易读,则可以这样做。

switch

SpinalHDL switch通常应按以下方式编写:

switch(value) {is(key) {}is(key) {}default {}
}

如果将is/default语句压缩成一行可以使代码更易读,则可以这样做。

Parameters

将 Component/Bundle 中的分组参数放在一个case class中通常是受欢迎的,因为:

  • 更容易携带/操作(carry/manipulate)以配置设计
  • 更好的可维护性
case class RgbConfig(rWidth: Int, gWidth: Int, bWidth: Int) {def getWidth = rWidth + gWidth + bWidth
}
case class Rgb(c: RgbConfig) extends Bundle {val r = UInt(c.rWidth bits)val g = UInt(c.gWidth bits)val b = UInt(c.bWidth bits)
}

但这并不适用于所有情况。例如:在FIFO中,将dataType参数与fifo的depth参数分组是没有意义的,因为通常dataType是与设计相关的内容,而depth则是与设计配置相关的内容。

class Fifo[T <: Data](dataType: T, depth: Int) extends Component {}

Scala 交互

介绍

SpinalHDL实际上不是一种语言:它是一个普通的Scala库。这乍一看可能很奇怪,但它是一个非常强大的组合。
您可以使用整个Scala世界来帮助您通过SpinalHDL库描述硬件,但为了做到这一点,重要的是要理解SpinalHDL如何与Scala交互。

SpinalHDL在API背后的工作原理

当您执行SpinalHDL硬件描述时,每次使用SpinalHDL functions、operators或classes时,它都会构建一个表示设计电路的内存图形(in-memory graph)。

然后,在**完成实例化顶层组件类的工作(即elaboration)**之后,SpinalHDL将对构建的图进行一些处理,并且如果一切正常,则将该图刷新到VHDL或Verilog文件中。

一切皆为引用

例如,如果您定义了一个以Bits类型参数为输入的Scala函数,在调用它时,该参数将作为引用传递。因此,如果在函数内部分配该参数,则对底层Bits对象产生的影响与在函数外部进行分配相同。

硬件类型

SpinalHDL中的硬件数据类型是两个部分的组合:

  • 给定Scala类型的实例
  • 该实例的配置

例如,Bits(8位)是Scala类型Bits和其8位配置(作为构造参数)的组合。

RGB example

让我们以 Rgb bundle 类为例:

case class Rgb(rWidth: Int, gWidth: Int, bWidth: Int) extends Bundle {val r = UInt(rWidth bits)val g = UInt(gWidth bits)val b = UInt(bWidth bits)
}

硬件数据类型在这里是Scala Rgb类及其rWidth、gWidth和bWidth参数化的组合。

这是一个用法示例:【cloneOf 有点意思!!!】

// 定义一个 RGB 信号
val myRgbSignal = Rgb(5, 6, 5)// 定义与前面相同数据类型的另一个 RGB 信号
val myRgbCloned = cloneOf(myRgbSignal)

你还可以使用函数来定义各种类型工厂(typedef):

// 定义一个类型工厂函数
def myRgbTypeDef = Rgb(5, 6, 5)// 使用该类型工厂创建一个 RGB 信号。
val myRgbFromTypeDef = myRgbTypeDef

生成的 RTL 中的信号名称

为了在生成的 RTL 中命名信号,SpinalHDL 使用 Java 反射来遍历整个组件层次结构,收集存储在class属性中的所有引用,并使用它们的属性名称进行命名。

这就是为什么在function内定义的每个信号的名称都会丢失:

def myFunction(arg: UInt) {val temp = arg + 1 // 在生成的 RTL 中,您将无法检索到“temp”信号return temp
}val value = myFunction(U"000001") + 42

如果您想保留生成的 RTL 中内部变量的名称,一种解决方案是使用 Area

def myFunction(arg: UInt) new Area {val temp = arg + 1 // 您将无法在生成的 RTL 中检索到临时信号。
}val myFunctionCall = myFunction(U"000001") // 将生成名为`myFunctionCall_temp`的`temp`
val value = myFunctionCall.temp + 42

Scala 用于elaboration,SpinalHDL 用于硬件描述(如:可以使用Boolean,而无法使用Bool)

例如,如果您编写一个Scala for循环来生成一些硬件,它将在VHDL / Verilog中生成展开的结果。

另外,如果您想要一个常量,您不应该使用SpinalHDL硬件字面值而是Scala的。例如:

// 这是错误的,因为您不能将硬件Bool用作构造参数(它会导致层次结构违规)。
class SubComponent(activeHigh: Bool) extends Component {// ...
}// 没错,你可以使用Scala世界中的所有内容来参数化你的硬件。
class SubComponent(activeHigh: Boolean) extends Component {// ...
}

Scala elaboration能力(if,for,函数式编程)

Scala的所有语法都可以用于详细说明硬件设计,例如,Scala if语句可用于启用或禁用硬件生成:

val counter = Reg(UInt(8 bits))
counter := counter + 1
if(generateAClearWhenHit42) { // elaboration test,就像在VHDL中生成if语句一样when(counter === 42) { // Hardware testcounter := 0}
}

Scala的for循环也是如此:

val value = Reg(Bits(8 bits))
when(something) {// 使用Scala for循环设置值的所有位(在硬件elaboration期间评估)for(idx <- 0 to 7) {value(idx) := True}
}

此外,函数式编程技术可以与许多SpinalHDL类型一起使用:

val values = Vec(Bits(8 bits), 4)
val valuesAre42 = values.map(_ === 42)
val valuesAreAll42 = valuesAre42.reduce(_ && _)
val valuesAreEqualToTheirIndex = values.zipWithIndex.map{ case (value, i) => value === i }

http://www.ppmy.cn/news/240540.html

相关文章

台式计算机的CPU上安装有小风扇,台式电脑cpu风扇安装教程

&#xff3b;教程介绍&#xff3d; 一般我们电脑主机箱里面&#xff0c;电脑的CPU和电源都会有风扇&#xff0c;电脑只要开机工作&#xff0c;风扇也就需要转动来降温。 CPU风扇属于易损件&#xff0c;使用的时间一久&#xff0c;很容易就会损坏&#xff0c;这样电脑CPU温度过高…

台式计算机中如何安装CPU,台式机cpu怎么换?台式机cpu更换方法

台式电脑在 现代 社会中已经成为了很重要的办公娱乐工具了&#xff0c;而电脑的主要组成部件之一就是cpu&#xff0c;也就是中央处理器&#xff0c;是电脑处理数据和运行程序的核心部件。虽然cpu不容易坏&#xff0c;但是cpu的更新换代是非常快的。当我们的cpu已经不能满足我们…

台式计算机购买cpu,如何选购台式机电脑CPU?看完你就会明白

CPU又称“中央处理器”是电脑的核心部件&#xff0c;也是我们选购时第一要看的部件&#xff0c;今天我们来说说如何选择台式机的CPU。 首先CPU分为两大类&#xff0c;一类INTEL(英特尔)另一类AMD&#xff0c;英特尔以稳定著称&#xff0c;AMD以性价比高被人们熟知。英特尔长期占…

台式计算机的cpu是指,台式电脑CPU是A10指的是什么?

A10是AMD APU中的高端型号&#xff0c;从第二代APU开始才有的&#xff0c;第一代是A4、A6、A8&#xff0c;当年AMD用这三个型号来对应intel的二代酷睿i3、i5、i7&#xff0c;虽然数字上面AMD占优&#xff0c;而且在核显方面的性能AMD的A4就能比intel的i7还牛&#xff0c;但是鉴…

计算机主机的cpu,台式机cpu能换吗?台式电脑换cpu的详细步骤

近期好多网友询问说台式电脑的CPU能换吗&#xff1f;确实有一部分是可以更换。CPU是一台计算机的运算核心和控制核心&#xff0c;占据着非常重要的位置。那么台式电脑换cpu怎么换&#xff1f;对于很多朋友来说&#xff0c;拆主板上的CPU并非易事&#xff0c;大家只要参考下文步…

台式机是不是最早出现的个人计算机,目前最早进的台式电脑的CPU是哪款?

您好&#xff01;感谢您选择惠普产品。 您这款机器测试过下面规格的CPU&#xff0c;如果您因为特殊原因需要升级CPU&#xff0c;您可以尝试联系当地的金牌服务中心看看他们是否可以协助您升级&#xff1a; 2nd generation Intel Core i7 processors (Sandy Bridge, Socket rPGA…

台式电脑组装机相关知识之处理器(CPU)篇

&#xff08;最近更新时间2018/3月&#xff09; 电脑cpu&#xff1a; 举个例子&#xff0c;如果同是Intel Coffee Lake架构的处理器产品&#xff0c;那么其架构都是一样的&#xff0c;在这种情况下&#xff0c;影响处理器性能较大的就是核心数量和运行频率&#xff0c;这…

引用类型数组、继承的意义

Java引用类型数组和继承的意义 在Java中,引用类型数组和继承是两个重要的概念。引用类型数组允许我们在一个数组中存储对象的引用,而继承则提供了代码复用和多态性的特性。在本教程中,我们将探讨Java引用类型数组的使用和继承的意义,并提供相应的示例代码。 步骤1:创建引…