Chisel 教程翻译 之 第四章:组件 of《Digital Design with Chisel》

6 篇文章 1 订阅
6 篇文章 0 订阅

4 Components 35
4.1 Components in Chisel are Modules . . . . . . . . . . . . . . . . . . . . 35
4.2 An Arithmetic Logic Unit . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3 Bulk Connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.4 Lightweight Components with Functions . . . . . . . . . . . . . . . . . 41


4 Components
4 组件
A larger digital design is structured into a set of components, often in a hierarchical way. 
一个大型的数字逻辑设计都是由一堆组件构成的,它们通常是分层组织的。

Each component has an interface with input and output wires, usually called ports.
每一个组件都有一个接口,包括输入和输出wire,通常叫做端口。

These are similar to input and output pins on an integrated circuit (IC). 
它们有点像是集成电路上的输入输出管脚。

Components are connected by wiring up the inputs and outputs.
组件时通过导出wire类型的输入和输出互相连接的。

 Components may contain subcomponents to build the hierarchy. 
 组件中可能包含子组件,构成一个层次结构。 
 
The outermost component, which is connected to physical pins on a chip, is called the top-level component.
最外层的组件的输入和输出是连接到 芯片的外围物理管脚的,这个组件我们称之为顶层组件。

Figure 4.1 shows an example design. 
Figure 4.1 展示了示例设计

Component C has three input ports and two output ports. 
组件C有3个输入和两个输出。

The component itself is assembled out of two subcomponents: B and C, which are connected to the inputs and outputs of C. 
组件C自己是由两个子组件B和C组成,这两个组件的端口连接在C的端口上。

One output of A is connected to an input of B. 
A的一个输出连接到了B的一个输入上。

Component D is at the same hierarchy level as component C and connected to it.
组件D同C再同一个层级上,而且跟C相连接。

In this chapter, we will explain how components are described in Chisel and provide several examples of standard components. 
本章我们将介绍如何用Chisel描述一个组件,并且会提供一些标准组件的示例。

Those standard components serve two purposes: (1) they provide examples of Chisel code and (2) they provide a library of components ready to be reused in your design.
展示这些标准组件的目的有两个:1,可以提供Chisel描述硬件的示例;2,它们会作为一个组件库,便于你在设计中的复用。

4.1 Components in Chisel are Modules
4.1 Chisel中的组件叫做Module(就像SC和Veri不是module似的)

Hardware components are called modules in Chisel. 
Chisel中把硬件组件叫做Module。

Each module extends the class Module and contains a field io for the interface. 
每一个模块都要继承Module类,而且还要包含一个io字段作为接口。

The interface is defined by a Bundle that is wrapped into a call to IO(). 
这个接口是用Bundle类定义的,而且整个定义过程封装在了IO()函数中。

The Bundle contains fields to represent input and output ports of the module. 
这个Bundle包含了好几个字段来表示这个模块的输入和输出端口。

The direction is given by wrapping a field into either a call to Input() or Output(). 
端口的方向再定义的时候通过调用Input()或Output()函数来指定。

The direction is from the view of the component itself.
这个方向是输入还是输出,是站在这个组件的自身的角度来定义的。

The following code shows the definition of the two example components A and B from Figure 4.1:
下面的代码展示了模块A和模块B的端口的定义示例:

class CompA extends Module {
    val io = IO(new Bundle {
    val a = Input(UInt(8.W))
    val b = Input(UInt(8.W))
    val x = Output(UInt(8.W))
    val y = Output(UInt(8.W))
    })
// function of A
}
class CompB extends Module {
    val io = IO(new Bundle {
    val in1 = Input(UInt(8.W))
    val in2 = Input(UInt(8.W))
    val out = Output(UInt(8.W))
    })
// function of B
}

Component A has two inputs, named a and b, and two outputs, named x and y. 
组件A有两个输入,分别叫做a和b,还有两个输出,叫做x和y。


For the ports of component B we chose the names in1, in2, and out. 
对于组件B的端口,分别命名为in1,in2和out。

All ports use an unsigned integer (UInt) with a bit width of 8. 
所有的端口都用一个8比特的无符号整型来表示(UInt)。

As this example code is about connecting components and building a hierarchy, we do not show any implementation within the components. 
因为这些怠慢是关于组件之间的连接和层级关系的,所以我们没有给出组件内部的任何功能实现。

The implementation of the component is written at the place where the comments states “function of X”. 
组件的功能实现应该写在注释语句“function of X”处。

As we have no function associated with those example components, we used generic port names. 
因为在这些示例组件中,我们没有给出与之关联的功能部分,所以给端口取得名字也比较无聊。

For a real design use descriptive port names, such as data, valid, or ready.
在真正的设计中,应该给出具有描述性的端口名称,例如data,valid,或者ready。

Component C has three input and two output ports. 
组件C有3个输入和两个输出。

It is built out of components A and B. 
它有A和B组件构成。
We show how A and B are connected to the ports of C and also the connection between an output port of A and an input port of B:
我们展示了A和B的端口是如何连接到C上的,而且A还有一输出端口连接到了B的输入端口。

class CompC extends Module {
    val io = IO(new Bundle {
        val in_a = Input(UInt(8.W))
        val in_b = Input(UInt(8.W))
        val in_c = Input(UInt(8.W))
        val out_x = Output(UInt(8.W))
        val out_y = Output(UInt(8.W))
    })
    // create components A and B
    val compA = Module(new CompA())
    val compB = Module(new CompB())
    // connect A
    compA.io.a := io.in_a
    compA.io.b := io.in_b
    io.out_x := compA.io.x
    // connect B
    compB.io.in1 := compA.io.y
    compB.io.in2 := io.in_c
    io.out_y := compB.io.out
}//信号接受端口在左侧,送来信号端口在右侧。

Components are created with new, e.g., new CompA(), and need to be wrapped into a call to Module(). 
组件是使用new操作符创建的,比如:new ComA(),而且需要用一个Module()函数包起来。

The reference to that module is stored in a local variable, in this example val compA = Module(new CompA()).
对module对象的引用存储在一个本地变量中,例如在本例中是这样表达的: val compA = Module(new CompA()).

With this reference, we can access the IO ports by dereferencing the io field of the module and the individual fields of the IO Bundle.
使用这个引用,我们可以通过对模块的字段和IO Bundle的字段来解引用,然后就可以访问它们。

The simplest component in our design has just an input port, named in, and an output port named out.
在下面这个最简单的引用中,只有一个输入端口和一个输出端口,分别命名为in和out。

class CompD extends Module {
    val io = IO(new Bundle {
        val in = Input(UInt(8.W))
        val out = Output(UInt(8.W))
    })
    // function of D
}

The final missing piece of our example design is the top-level component, which itself is assembled out of components C and D:
在刚才的那个示例设计中还有一个组件没讲到,也就是顶层组件,它本身是由C和D两个组件构成的。

class TopLevel extends Module {
    val io = IO(new Bundle {
        val in_a = Input(UInt(8.W))
        val in_b = Input(UInt(8.W))
        val in_c = Input(UInt(8.W))
        val out_m = Output(UInt(8.W))
        val out_n = Output(UInt(8.W))
    })
    // create C and D
    val c = Module(new CompC())
    val d = Module(new CompD())
    // connect C
    c.io.in_a := io.in_a
    c.io.in_b := io.in_b
    c.io.in_c := io.in_c
    io.out_m := c.io.out_x
    // connect D
    d.io.in := c.io.out_y
    io.out_n := d.io.out
}

Good component design is similar to the good design of functions or methods in software design. 
设计一个优秀的组件,跟软件中设计一个优秀的函数或方法是很相似的。

One of the main questions is how much functionality shall we put into a component and how large should a component be. 
最主要的问题是我们应该在组件中放置多少功能,而且一个组件应该多么大才合宜。

The two extremes are tiny components, such an adder, and huge components, such as a full microprocessor, Beginners in hardware design often start with tiny components. 
两个比较极端的情况是,最小的组件和最大的组件,分别是一个加法器和一个功能齐全的微处理器,
硬件设计的新鸟一般是从小小组件的设计开始找感觉的。

The problem is that digital design books use tiny components to show the principles. 
问题在于,数字逻辑设计的书都是用小组件的设计过程来展现设计原则。

But the sizes of the examples (in those books, and also in this book) are small to fit into a page  and to not distract by too many details.
但是,没关系,这些示例正因为挺小的,所以可以用一张纸就能够画下来,而且不会被太多细节分散初学者的注意力。

The interface to a component is a little bit verbose (with types, names, directions, IO construction). 
组件的接口部分是比较绕的,它有类型、名字、传输方向和IO构造。

As a rule of thumb, I would propose that the core of the component, the function, should be at least as long as the interface of the component.
根据经验,我建议组件和函数的内核部分应该至少比组件的接口定义语句要多。


For tiny components, such as a counter, Chisel provides a more lightweight way to describe them as functions that return hardware.
对于小组件,比如一个计数器,Chisel提供了更多轻量级的方式取来描述它的电路,也就是函数,它可以返回一个硬件电路。

Figure 4.2: An arithmetic logic unit, or ALU for short.
Figure 4.2:一个算数逻辑单元,或者简称为ALU

4.2 An Arithmetic Logic Unit
4.2 一个算数逻辑单元

One of the central components for circuits that compute, e.g., a microprocessor, is an arithmetic-logic unit, or ALU for short. 
一个计算电路中的中心组件,比如一个微处理器中的中心组件,是一个算术逻辑单元,简写为ALU。

Figure 4.2 shows the symbol of an ALU.
Figure 4.2 展示了一个ALU的符号。


The ALU has two data inputs, labeled A and B in the figure, one function input fn, and an output, labeled Y. 
ALU有两个输入,在图中标记为A和B,一个操作函数输入,以及一个输出,标记为Y。

The ALU operates on A and B and provides the result at the output. 
这个ALU对A和B进行操作,并且提供一个结果作为输出。

The input fn selects the operation on A and B. 
而输入的函数fn,负责决定对A和B所实施的操作。

The operations are usually some arithmetic, such as addition and subtraction, and some logical functions such as and, or, xor. That’s why it is called ALU.
这些可选的操作通常是加减法,以及一些逻辑功能,例如与、或、非、异或等。这就是为什么它被成为ALU。

The function input fn selects the operation. The ALU is usually a combinational circuit without any state elements. 
输入的函数fn的值负责选择具体操作。通常一个ALU是一个纯组合逻辑电路,其中并没有记录任何的状态。

An ALU might also have additional outputs to signal properties of the result, such as zero or the sign.
一个ALU通常还有一些额外的输出来指示运算结果的特点,比如是否为0或者其正负号。

The following code shows an ALU with 16-bit inputs and outputs that supports: addition, subtraction, or, and and operation, selected by a 2-bit fn signal.
下面的代码展示了一个16比特输入和输出的ALU,它支持加减或与等操作,是用2比特的fn信号来选择控制的。

class Alu extends Module {
    val io = IO(new Bundle {
        val a = Input(UInt(16.W))
        val b = Input(UInt(16.W))
        val fn = Input(UInt(2.W))
        val y = Output(UInt(16.W))
    })
    // some default value is needed
    io.y := 0.U
    // The ALU selection
    switch(io.fn) {
        is(0.U) { io.y := io.a + io.b }
        is(1.U) { io.y := io.a - io.b }
        is(2.U) { io.y := io.a | io.b }
        is(3.U) { io.y := io.a & io.b }
    }
}

In this example, we use a new Chisel construct, the switch/is construct to describe the table that selects the output of our ALU. 
在这个示例中,我们使用了一个新的Chisel构造器来表述所选择的ALU操作的输出,也就是用switch/is 构造器。

To use this utility function, we need to import another Chisel package:
使用这些使用功能,我们需要导入另一个Chisel包:

import chisel3.util._


4.3 Bulk Connections
4.3 模块连接简写符
For connecting components with multiple IO ports, Chisel provides the bulk connection operator <>. This operator connects parts of bundles in both directions. 
为了连接拥有多个端口的组件,chisel提供了bulk连接符<>.这个操作符会连接两个方向的部分端口。

Chisel uses the names of the leaf fields for the connection. 
Chisel使用模块的字段名字来做对应连接。

If a name is missing, it is not connected.
如果两个组件间的名字不存在的端口,那么就不做连接。

As an example, let us assume we build a pipelined processor. 
作为示例,让我们假设组建一个流水线处理器。

The fetch stage has a following interface:
在取指阶段拥有下面这些接口:

class Fetch extends Module {
    val io = IO(new Bundle {
        val instr = Output(UInt(32.W))
        val pc = Output(UInt(32.W))
    })
    // ... Implementation od fetch
}

The next stage is the decode stage.
然后是译码阶段。

class Decode extends Module {
    val io = IO(new Bundle {
        val instr = Input(UInt(32.W))
        val pc = Input(UInt(32.W))
        val aluOp = Output(UInt(5.W))
        val regA = Output(UInt(32.W))
        val regB = Output(UInt(32.W))
    })
    // ... Implementation of decode
}

The final stage of our simple processor is the execute stage.
最后是我们这个简单处理器的执行阶段呢。

class Execute extends Module {
    val io = IO(new Bundle {
        val aluOp = Input(UInt(5.W))
        val regA = Input(UInt(32.W))
        val regB = Input(UInt(32.W))
        val result = Output(UInt(32.W))
    })
    // ... Implementation of execute
}

To connect all three stages we need just two <> operators. We can also connect the port of a submodule with the parent module.
连接三个阶段的数据通信,我们只需要两个<>操作符。
我们同时也可以使用<>操作符,把子模块的端口连接到父模块上。


    val fetch = Module(new Fetch())
    val decode = Module(new Decode())
    val execute = Module(new Execute)
    fetch.io <> decode.io
    decode.io <> execute.io
    io <> execute.io

4.4 Lightweight Components with Functions
4.4 用Function描述轻量组件

Modules are the general way to structure your hardware description. 
Module是组织你的硬件描述的常用方式。

However, there is some boilerplate code when declaring a module and when instantiating and connecting it. 
然而,声明一个模块并且实例化以及连接它时,是有一些样板代码的。

A lightweight way to structure your hardware is to use functions. 
函数是一种轻量级的组织你的硬件的方式。

Scala functions can take Chisel (and Scala) parameters and return generated hardware. 
Scala函数可以利用Chisel函数生成硬件电路。

As a simple example, we generate an adder:
作为简单的示例,我们用函数生成一个加法器:

def adder (x: UInt , y: UInt) = {
x + y
}


We can then create two adders by simply calling the function adder.
然后我们可以通过调用这个函数来生成两个加法器,如下:

    val x = adder(a, b)
    // another adder
    val y = adder(c, d)

    
    
Note that this is a hardware generator. 
注意,这是个硬件生成器。

You are not executing any add operation during elaboration, but create two adders (hardware instances). 
在函数执行阶段你并没有让程序做加法运算,而是生成了两个加法器硬件实例。

The adder is an artificial example to keep it simple. 
为了让示例看上去比较简单,实际上加法器是一个比较矫揉造作的示例。

Chisel has already an adder generation function, like +(that: UInt).
Chisel中已经有一个加法器生成函数,形式位 +(that: UInt)

Functions, as lightweight hardware generators, can also contain state (including a register). 
函数,作为轻量级硬件生成器,生成的电路也可以包含状态,也就是内含寄存器。

Following example returns a one clock cycle delay element (a register). 
下面的示例包括一个但时钟周期延迟元素,也就是一个寄存器。

If a function has just a single statement, we can write it in one line and omit the curly braces ().
如果函数只有一个状态,我们可以把它写成一行,并且省略花括号。

def delay(x: UInt) = RegNext(x)

By calling the function with the function itself as parameter, this generated a two clock cycle delay.
通过把函数自身作为参数来调用,将会产生两个时钟周期的延迟。

val delOut = delay(delay(delIn))

Again, this is a too short example to be useful, as RegNext() already is that function creating the register for the delay.
还是一样,这个例子太短了,并没有用处,因为RegNext()已经是一个产生单周期延迟寄存器的硬件函数了。

Functions can be declared as part of a Module. 
函数可以作为模块的一部分来声明。

However, functions that shall be used in different modules are better placed into a Scala object that collects utility functions.
如果一个函数会在不同的多个模块中使用,最好把它放在一个收集实用函数的Scala对象中。


 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值