Modeling Digital Buses in Verilog-AΒΆ

It is generally preferred to use Verilog-AMS when simulating mixed-signal systems. However, sometimes that is not possible. This tutorial demonstrates a reasonably efficient and robust way to convert the value of an electrical bus into an integer. Before starting this tutorial, you should read Functional Modeling.

There are a two challenges that must be overcome to implement electrical digital buses in Verilog-A:

  1. The threshold crossings must be resolved to avoid undesirable delays in recognizing the value of the bus.

  2. The individual bit values must be combined into a single integer.

Consider the following model of a 8-bit DAC:

module dac (out_p, out_n, in, enable, vdda, gnda);
output out_p, out_n; electrical out_p, out_n;
input [7:0] in; electrical [7:0] in;
input enable; electrical enable;
input vdda; electrical vdda;
input gnda; electrical gnda;
integer code;
real value;
genvar i;

analog begin
    // convert the input to a signed integer
    code = 0;
    for (i = 0; i < 8; i = i + 1) begin
        @(cross(V(in[i]) - V(vdda)/2))
            ;
        if (V(in[i]) > V(vdda)/2)
            code = code + (1 << i);
    if (code >= 128)
        code = code - 256;

    // implement enable
    @(cross(V(enable) - V(vdda)/2))
        ;
    if (V(enable) < V(vdda)/2)
        value = 0;
    else
        value = code/256.0;

    // drive the differential output
    V(out_p) <+ V(vdda)/2 + transition(value/2, 0, 10n);
    V(out_n) <+ V(vdda)/2 - transition(value/2, 0, 10n);
end
endmodule

This model starts by applying a threshold to the individual lines in an electrical array and combining the resulting Booleans into an integer. The electrical array is in and the integer is code. The if statement computes the sign of the integer. It is only needed if in represents a signed number. Specifically, it should be deleted in in is an unsigned number.

The next block of code implements the enable feature and scales the output to the desired range.

The final block of code drives the differential outputs with the computed result. Notice that transition functions are used to smooth the otherwise discontinuous output signal.

The conversion from electrical signal to Booleans includes use of cross functions to assure that the threshold crossings are properly resolved.

The for loop in the first block is genvar loop. Such loops are fully expanded before the simulation begins. That loop expands as follows:

// convert the input to a signed integer
code = 0;
@(cross(V(in[0]) - V(vdda)/2))
    ;
if (V(in[0]) > V(vdda)/2)
    code = code + (1 << 0);
@(cross(V(in[1]) - V(vdda)/2))
    ;
if (V(in[1]) > V(vdda)/2)
    code = code + (1 << 1);

...

@(cross(V(in[7]) - V(vdda)/2))
    ;
if (V(in[7]) > V(vdda)/2)
    code = code + (1 << 7);

This DAC updates immediately after the input changes. It is possible to modify this model so it updates on a clock edge:

module dac (out_p, out_n, in, clock, enable, vdda, gnda);
output out_p, out_n; electrical out_p, out_n;
input [7:0] in; electrical [7:0] in;
input clock; electrical clock;
input enable; electrical enable;
input vdda; electrical vdda;
input gnda; electrical gnda;
integer code, en;
real value;
genvar i;

analog begin
    // convert the input to a signed integer on positive clock edge
    @(cross(V(clock) - V(vdda)/2), +1) begin
        code = 0;
        for (i = 0; i < 8; i = i + 1) begin
            @(cross(V(in[i]) - V(vdda)/2));
            if (V(in[i]) > V(vdda)/2)
                code = code + (1 << i);
        if (code >= 128)
            code = code - 256;
        value = code/256.0;
    end

    // reset output value when disabled
    @(cross(V(enable) - V(vdda)/2))
        ;
    if (V(enable) < V(vdda)/2)
        value = 0;

    // drive the differential output
    V(out_p) <+ V(vdda)/2 + transition(value/2, 0, 10n);
    V(out_n) <+ V(vdda)/2 - transition(value/2, 0, 10n);
end
endmodule

In this model the input in is sampled on the rising edge of the clock and it is the sampled value that drives the output.