与VHDL对比
简介
本页将讨论 VHDL 和 SpinalHDL 之间的主要区别。但不会深入解释。
过程(Process)
通常编写 RTL 时需要用到过程,但是它们的语义使用起来可能很笨拙。由于它们在 VHDL 中的工作方式,可能会迫使您拆分代码并重复编写。
要生成以下 RTL:
您必须编写以下 VHDL:
signal mySignal : std_logic;
signal myRegister : std_logic_vector(3 downto 0);
signal myRegisterWithReset : std_logic_vector(3 downto 0);
begin
process(cond)
begin
mySignal <= '0';
if cond = '1' then
mySignal <= '1';
end if;
end process;
process(clk)
begin
if rising_edge(clk) then
if cond = '1' then
myRegister <= myRegister + 1;
end if;
end if;
end process;
process(clk,reset)
begin
if reset = '1' then
myRegisterWithReset <= (others => '0');
elsif rising_edge(clk) then
if cond = '1' then
myRegisterWithReset <= myRegisterWithReset + 1;
end if;
end if;
end process;
在 SpinalHDL 中,它是:
val mySignal = Bool()
val myRegister = Reg(UInt(4 bits))
val myRegisterWithReset = Reg(UInt(4 bits)) init(0)
mySignal := False
when(cond) {
mySignal := True
myRegister := myRegister + 1
myRegisterWithReset := myRegisterWithReset + 1
}
隐式与显式定义对比
在 VHDL 中,当声明一个信号时,您无需指定它是组合信号还是寄存器。给它赋值的位置和方式决定了它是组合电路还是寄存器。
在 SpinalHDL 中,这些事情是明确的。寄存器直接在其声明中就定义为寄存器。
时钟域
在 VHDL 中,每次想要定义一堆寄存器时,都需要将时钟和复位信号传递给它们。此外,您必须在各处硬编码如何使用这些时钟和复位信号(包括它们的属性时钟沿、复位极性、复位性质(异步、同步))。
在 SpinalHDL 中,您可以定义 ClockDomain
,然后定义使用它的硬件区域。
例如:
val coreClockDomain = ClockDomain(
clock = io.coreClk,
reset = io.coreReset,
config = ClockDomainConfig(
clockEdge = RISING,
resetKind = ASYNC,
resetActiveLevel = HIGH
)
)
val coreArea = new ClockingArea(coreClockDomain) {
val myCoreClockedRegister = Reg(UInt(4 bits))
// ...
// coreClockDomain will also be applied to all sub components instantiated in the Area
// ...
}
组件的内部组织方式
在 VHDL 中,有一个 block
功能,允许您在组件内定义逻辑子区域。然而,几乎没有人使用这一功能,因为大多数人不了解它们,也因为这些区域内定义的所有信号都无法从外部读取、使用。
在 SpinalHDL 中,你有一个 Area
功能,可以更好地实现这个概念:
val timeout = new Area {
val counter = Reg(UInt(8 bits)) init(0)
val overflow = False
when(counter =/= 100) {
counter := counter + 1
} otherwise {
overflow := True
}
}
val core = new Area {
when(timeout.overflow) {
timeout.counter := 0
}
}
在 Area
内部定义的变量和信号可以在组件的其他地方访问,包括其他 Area
区域内。
安全性
在 VHDL 中,就像在 SpinalHDL 中一样,很容易编写出组合逻辑环,或者因为忘记给路径中的信号驱动而得到一个锁存器(latch)。
然后,为了检测这些问题,您可以使用一些 lint
工具来分析您的 VHDL,但这些工具不是免费的。在 SpinalHDL 中, lint
过程集成在编译器内部,并且在一切正常之前它不会生成 RTL 代码。此外,它还会检查跨时钟域信号。
功能与流程
函数和过程在 VHDL 中不经常使用,可能是因为它们的功能非常有限:
您只能定义一块组合逻辑硬件,或者只能定义一块寄存器(如果您在时钟进程内调用函数/过程)。
您无法在其中定义流程。
您无法在其中实例化组件。
在这里面,您可以读/写的范围是有限的。
在 SpinalHDL 中,所有这些限制都被消除了。
在单个函数中混合使用组合逻辑和寄存器的示例:
def simpleAluPipeline(op: Bits, a: UInt, b: UInt): UInt = {
val result = UInt(8 bits)
switch(op) {
is(0){ result := a + b }
is(1){ result := a - b }
is(2){ result := a * b }
}
return RegNext(result)
}
Stream线束内的队列函数示例(带握手)。该函数实例化一个 FIFO 组件:
class Stream[T <: Data](dataType: T) extends Bundle with IMasterSlave with DataCarrier[T] {
val valid = Bool()
val ready = Bool()
val payload = cloneOf(dataType)
def queue(size: Int): Stream[T] = {
val fifo = new StreamFifo(dataType, size)
fifo.io.push <> this
fifo.io.pop
}
}
为在外部定义的信号赋值的示例函数:
val counter = Reg(UInt(8 bits)) init(0)
counter := counter + 1
def clear() : Unit = {
counter := 0
}
when(counter > 42) {
clear()
}
总线和接口
当谈到总线和接口时,VHDL 非常无聊。您有两个选择:
随时随地逐线定义总线和接口:
PADDR : in unsigned(addressWidth-1 downto 0);
PSEL : in std_logic
PENABLE : in std_logic;
PREADY : out std_logic;
PWRITE : in std_logic;
PWDATA : in std_logic_vector(dataWidth-1 downto 0);
PRDATA : out std_logic_vector(dataWidth-1 downto 0);
使用记录但无法参数化(静态固定在包中),并且您必须为每个信号定义方向:
P_m : in APB_M;
P_s : out APB_S;
SpinalHDL 对参数化总线和接口的声明提供非常强大的支持:
val P = slave(Apb3(addressWidth, dataWidth))
您还可以使用面向对象编程来定义专门的配置对象:
val coreConfig = CoreConfig(
pcWidth = 32,
addrWidth = 32,
startAddress = 0x00000000,
regFileReadyKind = sync,
branchPrediction = dynamic,
bypassExecute0 = true,
bypassExecute1 = true,
bypassWriteBack = true,
bypassWriteBackBuffer = true,
collapseBubble = false,
fastFetchCmdPcCalculation = true,
dynamicBranchPredictorCacheSizeLog2 = 7
)
// The CPU has a system of plugins which allows adding new features into the core.
// Those extensions are not directly implemented in the core, but are kind of an additive logic patch defined in a separate area.
coreConfig.add(new MulExtension)
coreConfig.add(new DivExtension)
coreConfig.add(new BarrelShifterFullExtension)
val iCacheConfig = InstructionCacheConfig(
cacheSize = 4096,
bytePerLine = 32,
wayCount = 1, // Can only be one for the moment
wrappedMemAccess = true,
addressWidth = 32,
cpuDataWidth = 32,
memDataWidth = 32
)
new RiscvCoreAxi4(
coreConfig = coreConfig,
iCacheConfig = iCacheConfig,
dCacheConfig = null,
debug = debug,
interruptCount = interruptCount
)
信号声明
VHDL 强制您在架构描述的顶部定义所有信号,这很烦人。
..
.. (many signal declarations)
..
signal a : std_logic;
..
.. (many signal declarations)
..
begin
..
.. (many logic definitions)
..
a <= x & y
..
.. (many logic definitions)
..
SpinalHDL 在信号声明方面非常灵活。
val a = Bool()
a := x & y
它还允许您在一行中定义和赋值信号。
val a = x & y
组件实例化
VHDL 对此非常冗长,因为您必须重新定义子组件实体的所有信号,然后在实例化组件时将它们一一绑定。
divider_cmd_valid : in std_logic;
divider_cmd_ready : out std_logic;
divider_cmd_numerator : in unsigned(31 downto 0);
divider_cmd_denominator : in unsigned(31 downto 0);
divider_rsp_valid : out std_logic;
divider_rsp_ready : in std_logic;
divider_rsp_quotient : out unsigned(31 downto 0);
divider_rsp_remainder : out unsigned(31 downto 0);
divider : entity work.UnsignedDivider
port map (
clk => clk,
reset => reset,
cmd_valid => divider_cmd_valid,
cmd_ready => divider_cmd_ready,
cmd_numerator => divider_cmd_numerator,
cmd_denominator => divider_cmd_denominator,
rsp_valid => divider_rsp_valid,
rsp_ready => divider_rsp_ready,
rsp_quotient => divider_rsp_quotient,
rsp_remainder => divider_rsp_remainder
);
SpinalHDL 在这方面做出了很大提升,并允许您以面向对象的方式访问子组件的 IO。
val divider = new UnsignedDivider()
// And then if you want to access IO signals of that divider:
divider.io.cmd.valid := True
divider.io.cmd.numerator := 42
类型转换
VHDL 中有两种烦人的转换方法:
boolean <> std_logic (例如:使用
mySignal <= myValue < 10
等条件赋值信号是不合法的)unsigned <> integer(例如:访问数组)
SpinalHDL 通过统一化对象来转换这些类型。
boolean/std_logic:
val value = UInt(8 bits)
val valueBiggerThanTwo = Bool()
valueBiggerThanTwo := value > 2 // value > 2 return a Bool
unsigned/integer:
val array = Vec(UInt(4 bits),8)
val sel = UInt(3 bits)
val arraySel = array(sel) // Arrays are indexed directly by using UInt
调整位宽
VHDL 对位宽限制严格可能是一件好事。
my8BitsSignal <= resize(my4BitsSignal, 8);
在 SpinalHDL 中,您有两种方法可以实现相同的目的:
// The traditional way
my8BitsSignal := my4BitsSignal.resize(8)
// The smart way
my8BitsSignal := my4BitsSignal.resized
参数化
SpinalHDL 完全支持在其编译器中自然的集成泛型,并且它不依赖于 VHDL 泛型。
这是参数化数据结构的示例:
val colorStream = Stream(Color(5, 6, 5)))
val colorFifo = StreamFifo(Color(5, 6, 5), depth = 128)
colorFifo.io.push <> colorStream
以下是参数化组件的示例:
class Arbiter[T <: Data](payloadType: T, portCount: Int) extends Component {
val io = new Bundle {
val sources = Vec(slave(Stream(payloadType)), portCount)
val sink = master(Stream(payloadType))
}
// ...
}
元硬件描述
VHDL 具有某种封闭的语法。您无法在其上添加抽象层。
而SpinalHDL, 由于它构建在 Scala 之上,所以非常灵活,并且允许您非常轻松地定义新的抽象层。
这种灵活性在后面的库中表现突出: FSM 库、 BusSlaveFactory 库以及 JTAG 库。