In this blog, I try to break down these programming constructs into basic combinational circuits using Yosys, an open-source tool for synthesis.

In my last blog I mentioned that HDLs usually follow a concurrent hardware model and that is true but you can achieve procedural execution in Verilog using something called as the "*always"* block.

```
always @([senstivity_list])
begin
// procedural statements
end
```

The procedural statements inside the *always* block get executed when one of the signals in the senstivity list changes value. Here's an example of a block that generates a pulse width modulated signal.

```
module pwm_gen(
input [7:0]Dutycycle,
input clk,
output reg [7:0] counter,
output reg pwmout
);
always @(posedge clk) begin
if(counter < 99)
counter <= counter + 1;
else
counter <= 0;
end
always @(posedge clk) begin
if(counter < Dutycycle)
pwmout <= 1'b1;
else if (counter == 99)
pwmout <= 1'b0;
else
pwmout <= 1'b0;
end
endmodule
```

You can see that the senstivity list has the positive edge of the clock signal so the always block executes everytime there is a positive edge on the clock and we see the counter signal increase.

Once you have a verilog file, you can use Yosys to visualize the design. Yosys is an open source logic synthesis tool starting from RTL netlist and finally to gates in the target technology.

While constructs such as if, switch in a language like C are executed sequentially, these constructs in verilog are realised using combinational circuits which have no sequential control and are therefore realised using ** routing networks**. All the expressions in a routing network are evaluated concurrently and the desired result is routed to the output. We'll use yosys to understand the routing structure of common programming constructs.

```
# installing yosys
sudo apt-get install yosys
# yosys interactive mode
yosys
# read input file
readverilog filename.v
# convert to DFFs and muxes
proc
# perform some optimization
opt
# visualize the design
show
```

You can also create a script for Yosys by pasting all the commands in a .ys file.

```
yosys synth.ys
```

Result of yosys script for the pwm_gen module gives something like this.

More information about Yosys can be found here.

The previous example of a PWM generator uses an if statement in the *always* block. What does that translate to on the hardware? Let's find out using Yosys. To that end let us take the simple example of a n-to-2^n decoder with n = 2.

Truth table of 2-to-4 decoder with enable is as follows :

en | a[1] | a[0] | y[3:0] |

0 | - | - | 0000 |

1 | 0 | 0 | 0001 |

1 | 0 | 1 | 0010 |

1 | 1 | 0 | 0100 |

1 | 1 | 1 | 1000 |

```
module decoder (
input wire [1:0] a,
output reg [3:0] y,
input wire en
);
always @ * begin
if (en == 1'b0)
y = 4'b0000;
else if (a == 2'b00)
y = 4'b0001;
else if (a == 2'b01)
y = 4'b0010;
else if (a == 2'b10)
y = 4'b0100;
else
y = 4'b1000;
end
endmodule
```

The if-else statement implies a priority routing network in verilog. It is implemented by a sequence of 2-to-1 multiplexers as showing in the figure below.

The four 2-to-1 multiplexors form the priority routing network and other components represent the boolean/arithmetic expressions apart from the if-else ladder. The number of muxes increases propotionally to the number of if statements and a large number of if-else statements can cause a significant propogation delay.

```
module decode_case (
input wire [1:0] a,
output reg [3:0] y,
input wire en
);
always @ * begin
case ({en, a})
3'b000, 3'b001, 3'b010, 3'b011: y = 4'b0000;
3'b100: y = 4'b0001;
3'b101: y = 4'b0010;
3'b110: y = 4'b0100;
3'b111: y = 4'b1000;
endcase
end
endmodule
```

The case statement implies a multiplexing network and you can see how implementation of the same decoder results in two different circuits based on the programming construct used.

The pmux in the figure below represents a priority mux which can be thought of as a series of cascaded multiplexors used in the if statement or as a single n-to-1 multiplexer. Each value of the case expression can be mapped to one input of an n-to-1 multiplexer. Here, the number of inputs in the multiplexer would increase with the number of case expressions.

As usual the code and other files are in **this github repo.**

We checked out two routing structures in this blog and both implementations have tradeoffs. Priority routing would be benficial when there is some kind of weightage to the cases. Multiplexing networks would shine where the implementation is more truth-table oriented.

Let me know what you guys thought of this one. Feedback is always appreciated.

]]>Syntactically Verilog is similar to C/C++ but semantically the language is based on concurrent hardware operation which is very different from sequential execution of C/C++. Just like in any other language, there are many different ways of achieving a result but the focus of this blog series is to quickly get to a point where we are able to design hardware. So i'll try to come up with a verilog skeleton that we can use to generate hardware that is synthesizable in a better-safe-than-sorry approach.

I'm a big fan of open-source tools and running stuff locally on my machine rather than on servers. You could get by using an online verilog compiler but i'll be using Icarus Verilog, GTKWave and CocoTB to simulate, view waveforms and write testbenches respectively. To set this environment up, you can refer to one of my previous blogs.

```
module modulename
// Port Declration
(
input wire in0, in1,
output wire out0
);
// Internal Signal Declaration
wire sig0, sig1;
// Block logic
assign out0 = sig0 & sig1;
assign sig0 = in0 & in1;
assign sig1 = ~in0 & ~in1;
endmodule
```

The above is a sample verilog code, we'll try to understand the basic syntax of the language used in this example and i'll leave it upto the reader to figure out the syntax in future examples as we move forward.

The port declaration specifies the modes, data-types and names of the module's I/O. Modes are typically input, output or inout, data-type is usually wire but other data-types like reg, realtime, integer exist but not all of them can be physically synthesized.

This section specifies the internal signals and parameters and can be thought of as the internal wires connecting various circuit parts in a module.

This is the program body of a verilog module and can be thought of as a combination of circuit parts that execute concurrently. There are several ways to describe a circuit part. We have used continuous assignment in the example above but there are other ways like the "Always Block" that are used in more complicated circuits.

Once a module is developed, it can be simulated to verify functional correctness. Usually in companies there's a whole another team to run thorough checks using simulation where they use things like UVM, etc. But once you write code it's still worthwhile to do some basic checks on your own. Simulation is usually performed with the same HDL framework but we're seeing more options availble like cocoTb that allows you to write your test bench in python rather than verilog which gives you access to more advanced computation libraries like numpy, pytorch and scikit-learn.

```
`timescale 1ns/10ps
module modulename_testbench;
reg test_in0, test_in1;
wire test_out;
modulename uut(.in0(test_in0), .in1(test_in1), .out0(test_out));
initial begin
$dumpfile("sample.vcd");
$dumpvars(0, modulename_testbench);
// Test Vector
test_in0 = 1'b0;
test_in1 = 1'b0;
// Wait time
# 100;
test_in0 = 1'b1;
test_in1 = 1'b0;
# 100;
test_in0 = 1'b0;
test_in1 = 1'b1;
# 100;
test_in0 = 1'b1;
test_in1 = 1'b1;
# 100;
$finish();
end
endmodule
```

If you're a college student you probably have access to some kind of tools like Cadence Xcelium or Vivado but if you don't have access to those tools and still would like to simulate your circuit, Icarus Verilog is pretty good. You won't be able to simulate a full fledged AMS design but purely digital designs should not be an issue.

```
iverilog -o samp modulename.v modulename_testbench.v
vvp sample
gtkwave sample.vcd
```

To simulate with cocotb we do not need to write a testbench in Verilog. But if you remember we were using the testbench to also generate the VCD file to view the waveform.

```
module modulename
// Port Declration
(
input wire in0, in1,
output wire out0
);
// Internal Signal Declaration
wire sig0, sig1;
// Block logic
assign out0 = sig0 & sig1;
assign sig0 = in0 & in1;
assign sig1 = ~in0 & ~in1;
initial begin
$dumpfile("waves.vcd");
$dumpvars;
end
endmodule
```

The python test bench looks something like this.

```
import cocotb
from cocotb.triggers import Timer, RisingEdge
@cocotb.test()
async def test_modulename(dut):
a = (0,1,0,1)
b = (0,0,1,1)
y = (0,1,1,0)
for i in range(4):
dut.in0.value = a[i]
dut.in1.value = b[i]
await Timer(10, 'ns')
# assert dut.out0.value == y[i], f"Error at iteration {i}"
```

The assert statement can be used to make the simulation "stop on fail" where it matches the "out0" signal to an element in array y.

Cocotb makefile and other additional files are in this github repo.

This was the first blog and I hope to write more as I learn more about digital design, there's alot of interesting topics that I would like to discuss like BIST, DfT in due time.

Let me know what you guys thought about this one. Feedback is always appreciated.

]]>Similarly in the world of digital circuits, a NOT gate is the most basic circuit out there.

But with just one input and one output, it does not give a whole lot of room to play around and I personally am a big fan of XOR gate.

A | B | Y |

0 | 0 | 0 |

0 | 1 | 1 |

1 | 0 | 1 |

1 | 1 | 0 |

Now that we have chosen a circuit, or DUT - Device Under Test (Industry jargon 101). It's time we write the code for the same using Verilog.

```
module xor_gate(input wire a,
input wire b,
output wire y
);
assign y = a^b;
endmodule
```

```
import cocotb
from cocotb.triggers import Timer, RisingEdge
@cocotb.test()
async def test_xor(dut):
a = (0,0,1,1)
b = (0,1,0,1)
y = (0,1,1,0)
for i in range(4):
dut.a.value = a[i]
dut.b.value = b[i]
await Timer(1, 'ns')
assert dut.y.value == y[i], f"Error at iteration {i}"
```

In the python file we have basically defined the inputs that we will give to the DUT and the outputs that we expect.

```
# Makefile
TOPLEVEL_LANG = verilog
VERILOG_SOURCES = $(shell pwd)/../HDL/xor_gate.v
TOPLEVEL = xor_gate
MODULE = test_xor
include $(shell cocotb-config --makefiles)/Makefile.sim
```

And Voila!

We did get a pass, but this is boring. I want to look at waveforms! To that end we will create a wrapper that dumps the waveform and calls our module. I could not get these two work in two separate files so I ended up pasting them in the same file.

```
module xor_gate(input wire a,
input wire b,
output wire y
);
assign y = a^b;
endmodule
module xor_wrapper(input wire a,
input wire b,
output wire y
);
xor_gate xor_1 (.a(a), .b(b), .y(y));
initial begin
$dumpfile("waves.vcd");
$dumpvars;
end
endmodule
```

This dumps out a waves.vcd file that you can view with GTKWave.

```
gtkwave waves.vcd
```

Lo and behold! Waveforms!

]]>As I delved deeper into the world of digital, learning a Hardware Description Language became imperative, I had never bothered to learn more that basic VHDL during my undergrad and i found System Verilog counter intuitive. I had a good hold over python and while I am all for learning new things, I wanted to get some results quickly to motivate me to immerse myself in this pursuit.

The best thing for a beginner would probably be to go to a website like EDAplayground. But I am a huge fan of running things on my local machine so that is what we will do here.

The first couple of things that I needed to install were Python and Icarus Verilog. Apart from this I also setup a virtual environment.

```
sudo apt-get install make python3 python3-pip
sudo apt install iverilog
python3 -m venv myenv
```

Then I simply activated the environment and started installing cocotb and it's dependancies.

```
source myenv/bin/activate
pip3 install pytest cocotb cocotb-bus cocotb-coverage
```

The next step is to try the sample code from cocotb GitHUb repo to test a D Flip Flop. We create two files, dff.sv and test_dff.py

```
// dff.sv
`timescale 1us/1ns
module dff (
output logic q,
input logic clk, d
);
always @(posedge clk) begin
q <= d;
end
endmodule
```

```
# test_dff.py
import random
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge
from cocotb.types import LogicArray
@cocotb.test()
async def dff_simple_test(dut):
"""Test that d propagates to q"""
# Assert initial output is unknown
assert LogicArray(dut.q.value) == LogicArray("X")
# Set initial input value to prevent it from floating
dut.d.value = 0
clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk
# Start the clock. Start it low to avoid issues on the first RisingEdge
cocotb.start_soon(clock.start(start_high=False))
# Synchronize with the clock. This will regisiter the initial `d` value
await RisingEdge(dut.clk)
expected_val = 0 # Matches initial input value
for i in range(10):
val = random.randint(0, 1)
dut.d.value = val # Assign the random value val to the input port d
await RisingEdge(dut.clk)
assert dut.q.value == expected_val, f"output q was incorrect on the {i}th cycle"
expected_val = val # Save random value for next RisingEdge
# Check the final input on the next clock
await RisingEdge(dut.clk)
assert dut.q.value == expected_val, "output q was incorrect on the last cycle"
```

We also need a makefile to set the simulator for the HDL file.

```
# Makefile
TOPLEVEL_LANG = verilog
VERILOG_SOURCES = $(shell pwd)/dff.sv
TOPLEVEL = dff
MODULE = test_dff
include $(shell cocotb-config --makefiles)/Makefile.sim
```

The setup is now complete, we execute the simulation with Icarus Verilog

```
make SIM=icarus
```

If all is as expected, this is what you ought to see after execution.

I'm going to try to understand more about cocotb and verification and the next blog would probably have more details.

]]>I recommend that the reader be familiar with extremely basic aspects of signal processing, like the delta function and the Fourier transform among other things for this blog to truly make sense.

To somewhat lessen the scope of what can happen inside that black box, we introduce two major constraints,

- Linearity - "The sum of outputs is equal to the output of sums"
- Time Invariance - This essentially means that the performance of our black box does not depend explicitly on time.

Once these two conditions are satisfied, we can conveniently refer to our black box as an LTI system, LTI being Linear, Time-Invariant.

$$

Impulse \hspace{0.25cm} response \hspace{0.25cm} of \hspace{0.25cm} a \hspace{0.25cm} filter \hspace{0.25cm} : \hspace{0.5cm} h[n]
$$

I often heard my professors say, "A fundamental result is that the impulse response characterizes a filter". Turns out that the impulse response of a filter is nothing but the output of the filter when the input is a delta function.

But why is the impulse response so important - once you have the impulse response of a filter, you simply convolve the impulse response with the input of your filter and you get the output. Convolve once again is just a fancy name given to a particular summation because it is used so often in signal processing.

Convolution is represented by the asterisk(*) operator which is why most signal processing texts simply do not use it to represent multiplication.

$$ y[n] = x[n]*h[n] $$

Here, y[n] is the output of the filter, and x[n] is the input to the filter.

If you're familiar with a little bit of signal processing, you probably know about the time and frequency domains. To classify filters, we first take a peek at the impulse response of a filter from the time domain :

- FIR/ Finite Impulse Response: the impulse response is finite in nature
- IIR/ Infinite Impulse Response: duh!

Since we covered time, it only makes sense to see what the frequency domain has to offer: Frequency response of a filter is determined by the Discrete-Time Fourier Transform (DTFT) of its Impulse Response, as with any complex quantity once we obtain the frequency response we divide it into a magnitude and a phase term. The magnitude of the frequency response determines if the filter is going to be low-pass, high-pass, or band-pass. A low-pass filter allows low frequencies to pass and similarly for high-pass and band-pass.

*A neat observation is that if the phase term of the frequency response is proportional to the frequency or in other words is a "pure complex exponential", the filter simply adds a delay to our signal.*

Ideally, you would want your filter to have zero phase, so as to not add any delay to the output of the filter. You would also want to infinitely attenuate all frequencies that are outside the passband so that none of them show up in the output. But when we take a look at the impulse response of an ideal filter, the impulse response is not absolutely summable and thus isn't BIBO stable. BIBO stands for Bounded Input Bounded Output, to know more about the stability of a filter, check out this link.

The impulse response of an ideal filter extends infinitely in either direction on the X-axis, logic dictates that since the infinite extension of the impulse response is the root cause of our inability to design an ideal digital filter, we truncate it.

This is where *windows* come in.
A window can be thought of as a sequence that we multiply our impulse response with to truncate it

For simplicity, we can assume our window to be a rectangular window, which is defined as :
$$
\begin{align}
w[n] & = 1 \hspace{0.5cm} for \hspace{0.5cm}|n|<=N \\
& = 0 \hspace{0.5cm} otherwise \\
\end{align}

$$
Our new impulse response becomes
$$
\hat{h} [n] = h[n]w[n]
$$

*Note: there are many other window functions out there namely, Triangular, Hamming, Hanning, and so on.*

For designing filters, we are interested in the H[e^j], the Fourier transform of the impulse response h[n]. So we use something known as the Z transform. We already know $$ y[n] = x[n]*h[n] $$ We take the z transform on both sides. A little mathematical background about the Z transform can be found here.

We get
$$
\begin{align}
Z(y[n]) = Z(h[n]*x[n])\\
Y(z) = H(z)X(z)\\
H(z) = Y(z)/X(z)
\end{align}

$$

H(z) is known as the transfer function and when we input $$ z = e^{j} $$ in the transfer function. We get the frequency response of the filter.

We can consider H(z) to be a ratio of two polynomials which explains why we cannot have sharp transition bands as it would introduce discontinuity and polynomials are continuous by definition.

Every time we would go about designing a filter we would essentially have 2 constraints

- Frequency Response - Which frequencies do we want/ don't want.
- Phase - No phase, linear phase, etc

And practically we also have a third constrain which is the computation power available to us.

The image shows the ideal frequency response of a low-pass filter.

Since our transfer function is a polynomial we can't have the immediate transition from passband to stopband.

A practical low pass filter has a frequency response that looks like this

Both images have been taken from this book.

The ripples in the passband and the width of the transition band can both be minimized as we increase filter order. But increase in filter order can prove to be computationally expensive, which is why we have the third constraint - computational power.

Remember when I said that the H(z) is equal to a polynomial; designing a filter involves calculating the various coefficients of the said polynomial, but thankfully due to various numerical packages available in MATLAB and Python. We never really need to do that. In fact, for the most part, you'll find yourself using a trial and error approach - you'll use some premade filter and alter the parameters till it satisfies your requirements.

That's it for now folks. Do let me know if I've published some wrong information, or you have some suggestions.

]]>