The ALCHA project, including the language grammar and, by extension, this
wiki, is under development. This wiki serves as a documentation of the
project goals and aspirations, which are inherently unstable and subject to
change without notice.
The ALCHA language has been designed to have simulation and verification features that are as close to the SystemVerilog industry standard as possible. Minor modifications were applied in order to reduce verbosity and fit in with the rest of the ALCHA language.
It is possible, in ALCHA, to include the unit test in the same source file as the module it is meant to test. After synthesis, the resulting net-list is simulated and verified by using the simulation and verification constructs present in the source code.
In order to perform a unit test, the module under test must have its inputs stimulated by test signals. The code below shows the basics of an ALCHA stimulus
construct, which is comparable to the body of a Verilog initial
block. The delay operators (#
, ##
and @
) mean the same thing in both languages. There is no equivalent to a Verilog always
block, because the same thing can achieved by using a loop.
group<waveform = "green">{
net(8) A = 0, B = 0, C = 0;
}
// Simple stimulus
stimulus(1e-9){ // Use nanoseconds as the basic time-step
A = 5; // At the start of simulation, make A = 5
#5 B = 7; // After 5 time-steps, make B = 7
@(posedge Clk); // Wait until the positive edge of the Clk signal
loop #7 C++; // Forever afterwards, wait 7 time-steps and increment C
}
// Clock-based stimulus
stimulus(1e-9, Clk){ // Use nanoseconds as the basic time-step,
// and Clk as the default clock
#7 A = 5; // Wait 7 ns, then make A = 5
##3 B = 7; // After 3 rising-edges of Clk, make B = 7
loop ##1 C++; // Forever afterwards, wait 1 clock edge and increment C
}
This construct is run, by the simulator, after the rest of the code has been synthesised to a circuit net-list. If the output of the compiler is an FPGA project, rather than a simulation, the compiler produces SystemVerilog test-bench files based on the simulation constructs.
The waveform
attribute informs the simulator that the signal should be included in the log and displayed on the waveform viewer. Signals without this attribute are not logged, which saves time and memory. The value can be used to format the visual representation. In this case, the waveform should be displayed in green.
The parameters of the stimulus
construct takes the place of the Verilog timescale
directive and default clocking
declaration. The latter is optional, in which case the most recent @(posedge ...)
statement is used to determine the current clock used by the ##
operator.
Stimulus constructs can be defined anywhere, including class and function bodies. The stimulus construct is ignored by the synthesis tool, but implemented as a concurrent process by the simulation tool. Every instance of the function (or class) then has its own instance of that stimulus process.
It is often useful to split stimulus execution into multiple threads without using a completely separate stimulus
block. Verilog implements this concept by means of the fork
-join
construct. The code below shows the ALCHA syntax of the same structure. The Verilog disable
statement can be implemented by means of a break
statement in ALCHA.
stimulus(1e-9){
// fork ... join_all
{ /* The first fork */ } &&
{ /* The second fork */ } ;
// fork ... join_any
{ /* The first fork */ } ||
{ /* The second fork */ } ;
// fork ... join_none
{ /* The first fork */ } ||
{ /* The second fork */ } || {};
/* The rest of the stimulus body */
// Wait for Condition, or a delay, and stop the other thread when done
{wait(Condition); break;} || {#100 break;} ;
}
Scripting statements within simulation constructs are run during simulation time, not compile-time. This makes it possible to write advanced test-benches, including file I/O, casting net
types to num
types, making use of transcendental mathematical functions, etc.
File I/O can be used to implement data injection and monitoring. The test vectors can be generated by external tools, read by the ALCHA simulator and injected into the module under test. The results can then be logged to file and compared with expected results.
Model-based verification is a design methodology where the user would first write an emulation of the intended module functionality. The user would then develop the synthesisable equivalent of the emulation (or model). During verification, the same stimulus is injected into both synthesisable and emulation modules, and the results compared.
Emulation also has another use. It is often, if not always, faster to emulate a module than simulating the full synthesised net-list. The emulate
construct can be used for this purpose, as well as model-based verification.
It has the same syntax and semantics as the stimulus
construct, but allows the user to overwrite synthesised nets with simulated signals. The stimulus
construct can only assign to nets that have constant-expressions assigned to them (the constant-expression is taken as the value at the start of simulation). The emulate
construct can assign to any net, thereby overwriting the RTL behaviour.
class FFT{
net(31, -1) Input [256];
net(31, -1) Output_I[256];
net(31, -1) Output_Q[256];
net Go, Busy;
!! RTL implementation of the FFT...
// This is run during simulation, instead of the RTL above
emulate(1e-9, Clk){
loop{
##1;
if(Go){
Busy = 1;
num X = fft(Input) / 256;
##8; // The latency of the FFT engine
Output_I = real(X);
Output_Q = imag(X);
while(Go) ##1;
Busy = 0;
}
}
}
}
The user can make use of conditional compilation to switch between the emulated and simulated models.
The same construct can be used to emulated imported HDL. The ALCHA simulator does not support the simulation of external VHDL or Verilog code, so the user must include an emulator in those modules that must be included in the simulation. The code below shows an example of how to emulate an Altera ROM block.
hdl() altsyncram(
num widthad_a = 12,
num width_a = 16,
num numwords_a = 2**widthad_a,
char init_file[],
!! Other "altsyncram" parameter declarations go here
){
net clock0;
net(widthad_a) address_a;
net(width_a ) q_a;
!! Other "altsyncram" port declarations go here
}
class CPU_ROM(net(16) Opcodes[4096]){
!! Use scripting to write Opcodes to "CPU_ROM.mif"
// Instantiate the ROM block
private altsyncram ROM = altsyncram(init_file = "CPU_ROM.mif");
// Emulate the ROM block during simulation
ROM.{ // For convenience, push the ROM namespace onto the stack
emulate(1e-9, clock0){
loop ##1 q_a = Opcodes[address_a];
}
}
// Declare local signals (for the convenience of users of this class)
net Clk;
net(12) Address;
net(16) Data;
// Connect the ports
ROM.clock0 = Clk;
ROM.address_a = Address;
Data = ROM.q_a;
}
One of the more powerful methods of verification, provided by SystemVerilog, is assertion-based verification. The verification engineer would write a list of rules, based on the system specification. The verification tool then checks that those rules hold true for every step of the simulation.
The simplest way to write an assertion is to include it in a stimulus
construct. It checks the state of the system at that point in the simulation, by means of a semicolon-separated list of boolean expressions. The code below provides an example.
net(8) Adder(net(8) A, net(8) B, net C){
:(C, Adder) = A + B;
}
net c;
net(8) a, b, y = Adder(a, b, c);
stimulus(1e-9){
#1 a = 0x00; b = 0x00; assert{y == 0x00; c == 0; };
#1 a = 0xFF; b = 0x01; assert{y == 0x00; c == 1; };
#1 a = 0x01; b = 0xFF; assert{y == 0x00; c == 1; };
#1 a = 0xFF; b = 0xFF; assert{y == 0xFE; c == 1; };
#1 a = 0x7F; b = 0x80; assert{y == 0xFF; c == 0; };
}
Another option is to write the assertions outside the stimulus construct. Assertion statements of this form would typically take the form shown below. The |->
operator is used to specify rules of the form: ``when the left-hand-side is true, then the right-hand-side must also hold true''.
assert{
a == 0x00 && b == 0x00 |-> y == 0x00 && c == 0;
a == 0xFF && b == 0x01 |-> y == 0x00 && c == 1;
a == 0x01 && b == 0xFF |-> y == 0x00 && c == 1;
a == 0xFF && b == 0xFF |-> y == 0xFE && c == 1;
a == 0x7F && b == 0x80 |-> y == 0xFF && c == 0;
}
SystemVerilog sequences can be compared to regular expressions. The verification tool matches the given sequence expression against the simulated waveforms. A hand-shake transaction might be specified as shown in below. As with SystemVerilog, the signals are sampled one simulation time-step before the clock edge being tested, thereby ensuring that the system state is consistent with a hardware implementation.
// A simple sequence:
sequence Handshake{
Go ##[1..5] Busy ##[1.. 10] !Go ##[1..100] !Busy;
}
// A sequence-based assert:
assert(1e-9, Clk){
if(~Reset){
Go |-> Handshake;
rose(Go ) |-> !Busy;
fell(Busy) |-> !Go;
(Go & !Busy) |=> stable(Data);
}
}
In this case, the sequence inherits the time-step and clock signal from the assertions that calls it. It is also possible to specify a time-step and, optionally, a clock signal in the sequence declaration, which will override the assertion time-step and clock signals.
Sequences are useful in other aspects as well. The entire sequence is essentially a boolean expression, albeit over multiple clock-cycles. This means that it can be used within a stimulus
construct wait statement. The wait statement will then wait until the sequence has occurred, instead of a simple condition.
SystemVerilog sequences can be combined by using the and
, or
and intersect
keywords. The ALCHA equivalents are the &&
, ||
and &&&
operators, respectively. These operators are generally used, in other languages, to distinguish between bit-wise and logical binary operations. This is not required in ALCHA, because it does not support logical operations directly: the user is expected to or-reduce the operands and then use the binary operator instead.
In order to make the sequence expression syntax more expressive, SystemVerilog provides the user with helper functions, as well as repetition operators. These include $rose(...)
, $fell(...)
, $stable(...)
, $past(...)
, [* ... ]
, [-> ... ]
and [= ... ]
. ALCHA supports the same functions and operators, but without the $
prefix to the functions.
Formal verification is related to assertion-based verification. Assertions make sure that the design adhere to the specified rules for every iteration of the simulation. Formal verification runs the process in reverse. It starts with the rules specified by the assertion statements, and attempts to find a case that violates those rules, without a time-based simulation. It can be implemented as a feature of the simulation tool.
Hardware-in-the-loop verification entails loading the code onto a physical FPGA and verifying functionality by means of data-injection. The concept is depicted below.
The logic analyser can be a laboratory instrument, embedded logic analyser within the FPGA firmware, or a combination of the two. Within the ALCHA language, the user can add the logic_analyser
attribute to any signal, which will cause the compiler to automatically generate an embedded logic analyser project file (ChipScope in Xilinx Vivado or SignalTap in Altera Quartus) that includes that signal in the sample set.
Code and functional coverage reports can be used to determine dead code and insufficient test cases, among other uses.
For code coverage, the simulation process logs a counter on every statement in the source code, and reports the number of times each statement was executed during simulation. This also includes branch coverage, that tests that every possible branch of if
and case
statements have been tested. ALCHA enables this by means of an attribute, as shown below.
group<code_coverage = true>{
!! All the code that should be tested for coverage goes here
}
stimulus(1e-9, Clk){
!! Describe the stimulus here
}
Expression coverage tests that all possible combinations and permutations of an expression has been tested. This is generally reported as a percentage, because it is often impractical to test every possibility. Any expression that carry the expression_coverage
attribute is included in the expression coverage report.
For functional coverage, the process logs a counter for every functionality that the test engineer specified. Functions include typical valid use cases, edge cases and boundary conditions, protocols, etc.
As an example, consider the case of a simplified floating-point operation. Each input operand, as well as the output, can fall into one of six categories: NAN, -∞, <0, 0, >0, ∞. This means that there are 63 (216) possible combinations of functionality. The code below shows the ALCHA syntax for specifying functional coverage.
// Instances of the Float class
Float() A;
Float() B;
Float() Y;
// Instance of the FPU_Div class
FPU_Div(Clk, A, B, Y) MyDiv;
// Define coverage bins
coverbins FloatingPointFunctions(x){
NAN = x.exponent == 0xFF & x.fraction > 0;
NegInfty = x.sign == 1 & x.exponent == 0xFF & x.fraction == 0;
Negative = x.sign == 1 & x.exponent > 0 & x.exponent < 0xFF;
Zero = x.exponent == 0 & x.fraction == 0;
Positive = x.sign == 0 & x.exponent > 0 & x.exponent < 0xFF;
PosInfty = x.sign == 0 & x.exponent == 0xFF & x.fraction == 0;
}
// Define a coverage group
covergroup(Clk) MyDivCoverage{
FloatingPointFunctions(A);
FloatingPointFunctions(B);
FloatingPointFunctions(Y);
}
The coverbins
construct defines a list of bins, defined by boolean expressions, that applies to the input parameters. There is only one parameter (x
) in the example, but there can in fact be any number of parameters. The simulator issues an error when the current system state falls into more than one bin, or no bins.
The boolean expression used to define bins also supports sequences, which can be used to test protocol coverage.
The covergroup
construct is then used to group multiple coverbins
constructs into a functional coverage description. In the example, it defines the 216 bins that cover all combinations of input and output categories.
There are various simulation methodologies and industry standards. One of the more popular ones is UVM (Universal Verification Methodology). It is an Accellera standard, implemented by means of a SystemVerilog library of classes. It would be possible to implement an ALCHA based UVM library that is equivalent to the current SystemVerilog version.