Channels
A channel provides communication and synchronization between a pair of processes. It abstracts the bundled interface wires used in traditional HDLs. A channel has the following features:
Endpoints: Each channel has two endpoints, corresponding to the two ends of the communication. Conceptually, a channel resembles a pipe that transports values between its two endpoints.
Messages : A channel defines a set of messages that can be sent and received in specified directions. Each message carries a value of a given data type.
Timing Contract: Each message has a timing contract specifying how long its value remains valid after communication completes. This duration is the message’s lifetime.
Synchronization: A message transfer completes only when both endpoints are ready. Sending and receiving therefore occur at the same logical time, making all messages in Anvil synchronous. The completion of the send/receive operation defines the time of synchronization.
Messages use two-way handshake synchronization by default. Each endpoint of a message can also specify a different synchronization mode. These modes let the compiler omit unnecessary handshakes when synchronization is not required or can be determined statically.
Channel Classes
A channel class serves as a template for creating channels. It specifies:
the set of messages,
their directions,
their data types,
their timing contracts, and
their synchronization modes.
Channel classes play a role similar to interface definitions in SystemVerilog. SystemVerilog interfaces specify the data types and directions of communication; Anvil channel classes also define the timing contract. A channel class may have type and integer parameters.
channel-class-definition ::= "chan" identifier [ params ] "{"
message-definition
( "," message-definition )*
"}"
message-definition ::= message-direction identifier ":"
"(" data-type-expression "@" lifetime-pattern ")"
[ "@" sync-mode "-" "@" sync-mode ]
message-direction ::= "left" | "right"
lifetime-pattern ::= "#" digit+ | identifier
sync-mode ::= "dyn"
| "#" digit+ [ "~" digit+ ]
| "#" identifier [ "+" digit+ ]
In @ sync-mode - @ sync-mode, the first mode describes the message’s left endpoint and the second describes its right endpoint.
For example, consider the following channel class definition for a simple request-response channel:
chan simple_ch<T : type, W : int> {
left req : (T@ req),
right res : (logic[W]@#1) @dyn - @#1
}
The class has a data type parameter, T, and an integer parameter, W. It declares two messages:
reqis received on the left endpoint with value typeTand lifetimereq.resis received on the right endpoint with value typelogic[W]and lifetime#1.
The synchronization contract for res specifies:
The left side (sender here) may initiate communication at any time (
dyn).The right side (receiver here) must be ready exactly one cycle later (
@#1).
For a detailed explanation of lifetime patterns and synchronization modes, see the tutorial.
Channel Creation
Channels are instantiated from channel classes to obtain a pair of endpoints.
channel-creation ::= "chan" identifier "--" identifier ":"
identifier [ param-vals ] ";"
For example:
chan ep_le -- ep_ri : simple_ch<logic[8], 1>;
This creates a channel of type simple_ch<logic[8], 1> and binds its endpoints to
ep_le (left) and ep_ri (right).
Array of Channels
An array declaration creates multiple instances of a channel type at once. Their endpoints have correspondingly indexed identifiers.
channel-array-creation ::= "chan" identifier "--" identifier ":"
identifier [ param-vals ]
"[" digit+ "]" ";"
For example, this declaration creates four independent channels of type simple_ch<logic[8], 1>:
chan ep_le -- ep_ri : simple_ch<logic[8], 1>[4];
Their endpoints are bound as follows:
The left endpoints of the 4 channels become
ep_le[0],ep_le[1],ep_le[2], andep_le[3].The right endpoints become
ep_ri[0],ep_ri[1],ep_ri[2], andep_ri[3].
Each index refers to one concrete channel instance in the array.