Properties are arbitrary data associated with a task or network that are written in a simple syntax (related to Javascript/JSON).
Definition of properties
Syntax
Properties can describe three types of elements:
- A primitive can be a string (surrounded by single or double quotes), a number (float or integer, supporting the full Cx syntax with prefixes 0b and 0x, and "_" digit separator), a boolean (true or false), or the null literal.
- An array is surrounded by square brackets [ and ], and contains elements separated with commas.
- An object is surrounded with curly brackets { and }, and contains key-value pairs separated with commas, where the key is an identifier, and the value is an element.
That's all there is to it.
For example, this is an object with two properties "clocks" and "reset", associated respectively with an array and an object:
{
clocks: ["din", "dout"],
reset: {type: "synchronous", active: "high"}
}
Declaration
Properties are defined in a task or network with the properties keyword followed by an object. Properties can also be given to an instance as its first argument, for example to associate clocks and reset signals.
Clocks declaration
To specify the clocks of a task or network, use the clocks key. The value is an array of clock names:
properties {
clocks: ["din", "dout"]
}
The value of clocks must be an array, and the clock names must be valid identifiers. To declare a single clock, you can use the clock property (without 's') as follows: clock: "clk"
is a synonym for clocks: ["clk"]
. To indicate the task/network has no clocks, you can either write clock: null
or equivalently clocks: []
.
A preferable way of declaring an entity with no clocks is to use the type property to indicate that the task/network is combinational.
Implicit value
In the absence of the clocks property, the following clock is defined: clocks: ["clock"]
. This defines a single clock named "clock".
Clocks association
In most cases, when a network instantiates an entity (task or network), clocks are associated with clocks declared by the entity implicitly. Here is the list of cases where no explicit clocks association is required:
- the entity declares no clocks (it is combinational). The number of clocks declared by the network does not matter.
- the entity and the network declares the same number of clocks. Clocks are associated together in the following manner: the first clock of the network is associated with the first clock of the entity, and so on.
- the network has only one clock, and the entity declares multiple clocks. The network's clock is associated multiple times, once with each of the entity's clocks.
Explicit association
In short, explicit association is necessary each time a network declares more clocks than a synchronous entity it instantiates. This is done with a clocks
property specified at the instantiation site, with a value that can be:
an object whose keys are the entity's clock names (in this case the clock names are those declared by the DualPortRAM entity):
ram = new DualPortRAM({ clocks: {rdclock: "clk_recv", wr_clock: "clk_send"}, / other properties _/ depth: 8, width: 12 });
or an array of clock names:
ram = new DualPortRAM({ clocks: ["clkrecv", "clk_send"], / other properties _/ depth: 8, width: 12 });
Clock association with inner tasks
This corner case arises when you use an inner task within a network that declares multiple clocks. Whether this is a good idea or not is debatable, that said how do you associate clocks in this case? Unlike instances of classical entities, an instance of an inner task accepts no arguments and no properties. The trick in this case is that the task's properties are used both for declaration and instantiation, as follows:
network N {
properties {
clocks: ["clk_a", "clk_b"]
}
inner_a = new task {
properties { clock: "clk_a" }
void loop() {
print("inner a running");
}
};
inner_b = new task {
properties { clock: "clk_b" }
void loop() {
print("inner b running");
}
};
}
In this case, note that the entities that will be generated with use as clock names clk_a and clk_b respectively.
Reset
To specify the reset properties of a task or network, use the reset key. The value is either null (to specify that the task/network has no reset), or an object with properties type, active, and name:
properties {
reset: {type: "synchronous", active: "high", name: "reset_p"}
}
Valid values for type are "asynchronous" and "synchronous". active can be either "high" or "low". name is the reset signal's name.
Implicit values
In the absence of a reset key, the following reset is defined:
properties {
reset: {type: "asynchronous", active: "low", name: "reset_n"}
}
In the absence of the name key, if the reset is active low, the name is "reset_n", else if it is active high, the name is simply "reset".
Use with instances
When passing properties to an instance, the reset key can be associated with the name of a signal that should be used as a reset for the instance (in this example, the "ready" signal will be used as SimpleTask's reset):
new SimpleTask({reset: "ready"})
Type
To define a task or network as "combinational", use the type key with the value "combinational":
properties {
type: "combinational"
}
The "combinational" value is equivalent to the following properties (in other words no clock and no reset): {clocks: [], reset: null}
Test
The test property allows you to test a task or a network by specifying stimulus/expected values for ports cycle by cycle. The value of test is an object that associates each port name with an array that contains one value per cycle. For example, to test a Run-Length Encoding task whose ports are in sync u8 data, out sync value, sync u15 count;
, you can write the following properties:
properties {
test: {
data: [ 6, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2 ],
value: [ null, 6, null, 5, null, null, 4, null, null, null, 3 ],
count: [ null, 1, null, 2, null, null, 3, null, null, null, 4 ]
}
}
Values must be of the same type as the port, for example you can use true
and false
for bool
ports. When no value is expected on a port in a cycle, use null
.
This test suite is interpreted as follows:
- on cycle 0, write 6 to port
data
, and expect no data on portsvalue
andcount
. It is an error if async
port is written to when it is not supposed to. - on cycle 1, write 5 to port
data
, and expect 6 to be written tovalue
and 1 to be written tocount
.
We use a classic "black box" approach where a testbench instantiates the task, henceforth known as a Design Under Test (DUT). In this approach, the testbench checks values outside of the DUT - after the DUT has written them. This occurs one cycle later (as part of good coding style, all output ports are registered in the RTL code generated from Cx), as can be seen on the waveform:
Test values are specified cycle by cycle to help verify synchronization issues. After all a sync
port can wait for data, so technically you could allow the same thing for tests, and never specify null
values. As a matter of fact, that is what we used to do, and the problem was that a test would not test the synchronization behavior. We realized when we switched to truly cycle-accurate tests that some automated unit tests had synchronization issues that had been left undetected before.
The stimulus/expected values are in the format designed and used by Adacsys.
Implementation
The implementation key allows the designer to tell the code generator what to produce from a Cx task or network. This property is meant for entities declared in Cx and implemented in Verilog or VHDL (or any other language or format, really). The value is an object that can define a type, file and dependencies:
properties {
implementation: {
type: "external",
file: "../../top.v",
dependencies: ["../../entity_a.v", "../../entity_b.v"]
}
}
The type property supports two values: "builtin" (reserved for entities defined by the compiler) and "external" (for entities that you define in Verilog or VHDL). The file that implements the task declaring these properties is specified with the file property.
In a future version, "implementation" could be extended to embed code in Cx, for example:
properties {
implementation: {
vhdl: {s0_s1: "a <= a xor b;"},
verilog: {s0_s1: "a = a ^ b;"}
}
}
Copyright 2014-2020 Synflow SAS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.