What is System Verilog?

SystemVerilog is a successor/extension to Verilog that fixes a lot of common issues with syntax in the original language.

Four-Valued Logic

Following the logic of a high-impedance floating wire, we need to have 4 possible values. Sometimes, we cannot tell when a device is floating in value. It could possibly have issues with pulling up or pulling down, which could leave an intermediary voltage value. As a result, we need to have this in our simulations.

In SystemVerilog, there are 4 values that can occur:

  • 0: false
  • 1: true
  • z: floating
  • x: invalid (don’t know, don’t care)

Truth Table of Four-Valued Logic

AND01zx
000xx
101xx
zxxxx
xxxxx

Syntax

Will pick up on the syntax directly from where we left off on Verilog.

Inverters

module inv(input logic [3:0] a, output logic [3:0] y);
	assign y = ~a; // flips individual bits of the BUS a
endmodule

Logic Gates

module gates(input logic[3:0] a, b, output logic [3:0] y1, y2, y3, y4, y5);
	assign y1 = a & b; // a AND b
	assign y2 = a | b; // a OR b
	assign y3 = a ^ b; // a XOR b
	assign y4 = ~(a & b); // NOT (a AND b)
	assign y5 ~(a | b); // NOT (a OR b)
endmodule

Multi-Bit AND

Instead of manually writing out all bits of BUS a, you can just apply the scalar AND to all of the bits of the input until we have a single result.

module gates(input logic [7:0] a, output logic y);
	assign y = &a;
endmodule

2:1 Multiplexer

Just a C-like ternary operator.

module mux2(input logic[3:0] d0, d1, input logic s, output logic [3:0] y);
	assign y = s ? d1 : d0;
endmodule

4:1 Multiplexer (Behavioral)

We now need to add bits to s to choose. This is behavioral (not structural) because we define what the behavior is, not how to implement it.

module mux2(input logic[3:0] d0, d1, d2, d3, input logic[1:0] s, output logic [3:0] y);
	assign y = s[1] ? (s[0] ? d3 : d2) : (s[0] ? d1 : d0);
endmodule

4:1 Multiplexer (Structural)

Need to use 2:1 multiplexer as a base for making this.

module mux2(input logic[3:0] d0, d1, d2, d3, input logic[1:0] s, output logic [3:0] y);
	logic [3:0] low, high;
	mux2 lowmux(d0, d1, s[0], low);
	mux2 highmux(d1, d3, s[0], high);
	mux2 finalmux(low, high, s[1], y);
endmodule

Full Adder

module fulladder(input logic a, b, cin, output logic s, cout);
	logic p, g;
	assign p = a ^ b;
	assign s =  ^ cin;
	assign cout = g | (p & cin);
endmodule

Tristate Buffer

module tristate (input logic [3:0] a, input logic en, output logic [3:0] y);
	assign y = en ? A : 4'bz;
endmodule

Logic Gates with Delays

`timescale 1ns/1ps
module example(input logic a, b, c, output logic y);
	logic ab, bb, cb, n1, n2, n3;
	assign #1 {ab, bb, cb} = ~{a`, b, c};
endmodule

Register

module flop(input logic clk, input logic [3:0] d, output logic [3:0] q);
	always_ff @(posedge clk)
		q <= d;
endmodule

There is also a negedge word for the clock clk.

Initial and Always

Initial: only do this once at the start Always: do this every time

  • Can be in form always @(condition list) Three types of always:
  • always_ff (flip flop)
  • always_latch (level-sensitive latch)
  • always_comb (combinational)

Resettable Register (Asynchronous)

module flopr_async(input logic clk, reset, input logic [3:0] d, output logic [3:0] q);
	always_ff @(posedge clk, posedge reset)
	if (reset) q <= 'b0;
	else q <= d;
endmodule

Resettable Register (Synchronous)

module flopr_sync(input logic clk, reset, input logic [3:0] d, output logic [3:0] q);
	always_ff @(posedge clk)
	if (reset) q <= 'b0;
	else q <= d;
endmodule

Resettable Enabled Register (Synchronous)

module flopr_sync(input logic clk, reset, en, input logic [3:0] d, output logic [3:0] q);
	always_ff @(posedge clk)
	if (reset) q <= 'b0;
	else if (en) q <= d;
endmodule

Synchronizer

module sync(input logic clk, d, output logic q);
	logic n1;
	
	always_ff @(posedge clk)
	begin
		n1 <= d;
		q <= n1;
	end
endmodule

Level Sensitive D Latch

module latch(input logic clk, d, output logic q);
	always_latch @(clk,d)
	if (clk) q <= d
endmodule

Inverter Using always

module inv(input logic [3:0] a, output logic [3:0] y);
	always_comb @(*) // anything happens
		y = ~a;
endmodule

Assignment Statements

If doing an assignment outside of an always block:

  • continuous assignment using the assign keyword
  • evaluated concurrently

Inside an always block:

  • block assignment using = (use this for combinational logic)
  • non-blocking assignment using <= (use for sequential logic)

Full Adder using always

module fulladder(input logic a, b, cin, output logic s, cout);
	logic p, g;
	
	always_comb
		begin
			p = a ^ b;
			g  = a & b;
			s = p ^ cin;
			cout = g | (p & cin);
		end
endmodule

Seven-Segment Display Decoder

module sevenseg(input logic[3:0] data, output logic [6:0] segments);
	always_comb
		case(data)
			0: segments = 7'b111_1110;
			1: segments = 7'b011_0000;
			...
			9: segments = ...;
			default: segments = ...;
		endcase
endmodule

Priority Circuit

With Don’t Cares

module priority_casez(input logic[3:0] a, output logic[3:0] y);
	always_comb
		casez(a) 
			4'b1???: y = 4'b1000; // don't care about the rest of this #
		endcase
endmodule

Multiplier

Unsigned

Signed

Parameterized N: Decoder

module decoder
	#(parameter N=3)
	(input logic[N=1:0] a, output logic[2**N-1:0] y);
	
	always_comb
		begin
			y = 0;
			y[a] = 1;
		end
endmodule

Generate Statement

Be careful when using this.

module andN
	#(parameter width=8)
	(input logic[width-1:0] a, output logic y);
	genvar i;
	logic[width-1] x;
	
	generate
		assign x[0] = a[0];
		for(i = 1; i < width; i = i + 1) begin: forloop
			 assign x[i] = a[i] & x[i-1];
		end
	endgenerate
	
	assign y = x[width-1];
endmodule

When we need to define structure in variable sized modules/subsystems, we have to use generate to define the structure in a very metaprogramming-esque way.

Making a Test Bench

Basic Test Bench

module testbench2();
	logic a, b, c, y;
	// instantiate device under test
	sillyfunction dut(a, b, c, y);
	initial begin
		a = 0; b= 0; c = 0; #10;
		assert(y === 1) else $error ("000 failed.");
		c = 1;
		assert (y === 0) else $error ("001 failed.");
		...
	end
endmodule

File Driven Test Bench

It can be better to create a system with a behavioral implementation.

module testbench3();
	logic a, b, c, y;
	logic[31:0] vectornum, errors;
	logic[3:0] testv[10000:0];
	// instantiate device under test
	sillyfunction dut(a, b, c, y);
	always begin
		clk = 1; $5; clk = 0; #5;
	end
	
	initial begin
		$readmemb("example.tv", testv); // read binray file into memory
		vectornum = 0; errors = 0;
		reset = 1; #27; reset = 0;
	end
	
	always @(posedge clk) begin
		#1; {a, b, c, exp} = testv[vectornum];
	end
 
	always @(negedge clk) begin
		if(~reset) begin
			if(y !== exp) begin
				$display("Error: inputs = %b ", {a, b, c});
				$display("Output = %b (expected %b)", y, exp);
				errors++;
			end
			vectornum++; ...
		end
endmodule