# 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.

## Input Buses

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

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

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

Consider the following model of an 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.

## Output Buses

The challenges that must be overcome to implement electrical digital output buses in Verilog-A are:

An integer must be decomposed into its individual bits.

The abrupt and discontinuous transitions in the value of the output bits must be smoothed with well controlled.

This process is demonstrated with the following model of an 6-bit ADC:

```
module adc (out, in, clk);
output [5:0] out; input in, clk;
electrical [5:0] out; electrical in, clk;
parameter real vh = 1;
parameter real vth = vh/2;
parameter real tt = 100n from (0:inf);
integer result;
genvar i;
analog begin
@(cross(V(clk) - vth, +1)) begin
result = 64*(V(in)+1)/2;
if (result > 63) result = 63;
else if (result < 0) result = 0;
end
for (i=0; i<6; i=i+1)
V(out[i]) <+ transition(result & (1<<i) ? vh : 0, 0, tt);
end
endmodule
```

The first part of this model is an event block that samples the voltage of the input pin at rising edges of the clock. The actual analog-to-digital conversion is performed by offsetting and scaling the input voltage and then casting the value to an integer. This line converts input values from the range −1 V ⋯ 1 V to integers in the range 0 ⋯ 64. Finally, the integer is then clipped so that its value always falls within the range 0 to 63, the normal operating range of the ADC. You can eliminate the +1 to get a signed output, but you must also change the clipping range.

The second part of the model is a genvar loop that splits *result* into its
individual bits and drives them onto the output bus. This is performed with the
help of the *left-shift* operator, the *bitwise and* operator, and the *inline
conditional* operator. To see how this works, expand the genvar loop:

```
V(out[0]) <+ transition(result & (1<<0) ? vh : 0, 0, tt);
V(out[1]) <+ transition(result & (1<<1) ? vh : 0, 0, tt);
V(out[2]) <+ transition(result & (1<<2) ? vh : 0, 0, tt);
V(out[3]) <+ transition(result & (1<<3) ? vh : 0, 0, tt);
V(out[4]) <+ transition(result & (1<<4) ? vh : 0, 0, tt);
V(out[5]) <+ transition(result & (1<<5) ? vh : 0, 0, tt);
```

Now, evaluate the left shift operator:

```
V(out[0]) <+ transition(result & ('b000001) ? vh : 0, 0, tt);
V(out[1]) <+ transition(result & ('b000010) ? vh : 0, 0, tt);
V(out[2]) <+ transition(result & ('b000100) ? vh : 0, 0, tt);
V(out[3]) <+ transition(result & ('b001000) ? vh : 0, 0, tt);
V(out[4]) <+ transition(result & ('b010000) ? vh : 0, 0, tt);
V(out[5]) <+ transition(result & ('b100000) ? vh : 0, 0, tt);
```

Assume that the value of *result* is 58, which in binary is ‘b111010. Consider
bit 0. ‘b111010 & ‘b000001 is ‘b000000, which is a logical false and so the
inline conditional operator evaluates to its third argument, 0. Now consider
bit 1. ‘b111010 & ‘b000010 is ‘b000010, which is a logical true and so the
inline conditional operator evaluates to its second argument, *vh*. You can
follow this logic to its conclusion to see that the values of the output bus
will be {*vh*, *vh*, *vh*, 0, *vh*, 0}.

Finally, the *transition* function smooths and controls each output transition.