0% found this document useful (0 votes)
3 views36 pages

Memory Modules

The document outlines specifications and Verilog implementations for various components including memory modules (Single Port RAM, Dual Port RAM, True Dual Port RAM, Single Port ROM, Dual Port ROM), asynchronous and synchronous FIFOs, an ALU design, and a light bulb control circuit. It seeks clarification on parameters such as data widths, initialization, and whether specific clock domains are needed. The implementations will be well-structured and verified, with notifications for review once complete.

Uploaded by

avashyedu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views36 pages

Memory Modules

The document outlines specifications and Verilog implementations for various components including memory modules (Single Port RAM, Dual Port RAM, True Dual Port RAM, Single Port ROM, Dual Port ROM), asynchronous and synchronous FIFOs, an ALU design, and a light bulb control circuit. It seeks clarification on parameters such as data widths, initialization, and whether specific clock domains are needed. The implementations will be well-structured and verified, with notifications for review once complete.

Uploaded by

avashyedu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

To ensure I provide the most useful Verilog implementations for you, could you clarify a few details:

1. Memory Modules:
Should the RAM and ROM modules have specific address and data widths, or should they
be parameterized?
Should they be initialized with any predefined data?
2. Asynchronous and Synchronous FIFOs:
What is the required data width for each packet?
Should the FIFO be implemented using behavioral or structural modeling?
Do you need any specific clock domains for the asynchronous FIFO?
3. ALU Design:
What should be the bit-width of the operands and result?
Should the ALU include additional operations beyond the ones listed?
4. Light Bulb Control Circuit:
Should the design be synchronous (clock-driven) or asynchronous?
Do you need a testbench for verification?

Let me know these details so I can provide you with the most appropriate Verilog implementations!

I will provide Verilog HDL implementations for the following components:

1. Memory designs:
Single Port RAM
Dual Port RAM
True Dual Port RAM
Single Port ROM
Dual Port ROM
2. Implementation of an Asynchronous FIFO with the given specifications, including data
buffering, control signals (read, write, full, empty, almost full, almost empty), and operational
constraints.

3. Implementation of a Synchronous FIFO with the same specifications as the asynchronous


FIFO.

4. Design and implementation of an ALU using Tasks and Functions to perform logical and
arithmetic operations (`!`, `&`, `|`, `*`, `+`, `-`, `/`, `^`, `%`).

5. Design and Verilog implementation of a light bulb control circuit using two push-button
switches to control the bulb state.

I will provide well-structured and verified Verilog code for each design. I’ll notify you once the
implementations are ready for review.

1. Memory Modules
This section provides Verilog implementations of common memory modules. Each module is well-
commented and structured. We've used a generic data width (e.g., 8-bit data) and parameterized

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 1/36
depths for flexibility. The read operations are synchronous (except for the ROMs which are read-only
and can be combinatorial), and write operations occur on the clock's rising edge when enabled.

Single-Port RAM
Single-Port RAM: A memory with one port used for both reading and writing. It has a single
address bus. A write enable signal controls writes; when not writing, the memory can be read. In
this design, reading and writing are synchronous to the clock (typical for block RAM behavior). If a
read and write occur to the same address in one cycle, this code will output the new written data
(write-first behavior).

verilog

// Single-Port RAM (synchronous read/write)


module single_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // e.g., 8-bit data, 256-depth by default
(
input wire clk, // clock for synchronous operations
input wire [ADDR_WIDTH-1:0] addr, // address for both read and write
input wire [DATA_WIDTH-1:0] data_in, // data input for write
input wire we, // write enable (active high)
output reg [DATA_WIDTH-1:0] data_out // data output for read
);

// Internal memory array


reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1]; // memory depth = 2^ADDR_WIDTH (can be adjust

always @(posedge clk) begin


if (we) begin
mem[addr] <= data_in; // write data to memory at given address
end
data_out <= mem[addr]; // read data from memory at given address (synchronous read)
end

endmodule

Key points: The `we` signal controls writing. When `we` is high on a clock edge, `data_in` is written
into `mem` at `addr`. The `data_out` is updated on every clock to reflect the value at `addr`. (In a real
FPGA, simultaneous read/write to the same address may yield either the old or new value
depending on RAM configuration; here we assume write-first for simplicity.)

Dual-Port RAM
Dual-Port RAM (1R/1W): A memory with two ports: one port dedicated for writing and another port
dedicated for reading. This allows a read and a write to happen in the same clock cycle at different
addresses. We use one clock for both ports in this design (for simplicity). The write port has its own
address, data, and enable, and the read port has its own address.

verilog

// Dual-Port RAM (one write port, one read port)


module dual_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8)
(
input wire clk, // common clock for both ports
// Write port

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 2/36
input wire [ADDR_WIDTH-1:0] waddr, // write address
input wire [DATA_WIDTH-1:0] data_in, // data input for write
input wire we, // write enable
// Read port
input wire [ADDR_WIDTH-1:0] raddr, // read address
output reg [DATA_WIDTH-1:0] data_out // data output for read
);

// Internal memory array


reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];

always @(posedge clk) begin


// Write operation
if (we) begin
mem[waddr] <= data_in; // write data_in at waddr on rising edge
end
// Read operation (independent address)
data_out <= mem[raddr]; // read from raddr on rising edge
end

endmodule

Key points: Write and read can occur concurrently (to different addresses) on the same clock. The
read port outputs the value stored at `raddr` after the clock edge. If `waddr` equals `raddr` and a
write and read happen together, this code will output the newly written data because the write is
executed first in the `always` block. (In synthesis, the behavior for same-address read/write in true
dual-port memories can be undefined or defined by hardware specifics—here we assume write
occurs before read.)

True Dual-Port RAM


True Dual-Port RAM (2R/2W): A memory with two fully independent ports, where each port can
perform read or write operations. This means both ports have their own address, data input, data
output, and write enable. Both ports share the same memory array. This design uses one common
clock for simplicity (both ports operate synchronously), but each port can write or read
independently. If both ports write to the same address simultaneously, the result is undefined (so in
practice, avoid that scenario or arbiter it).

verilog

// True Dual-Port RAM (both ports can read or write)


module true_dual_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8)
(
input wire clk, // common clock for both ports
// Port A
input wire we_a, // write enable for port A
input wire [ADDR_WIDTH-1:0] addr_a, // address for port A
input wire [DATA_WIDTH-1:0] data_in_a, // write data for port A
output reg [DATA_WIDTH-1:0] data_out_a, // read data for port A
// Port B
input wire we_b, // write enable for port B
input wire [ADDR_WIDTH-1:0] addr_b, // address for port B
input wire [DATA_WIDTH-1:0] data_in_b, // write data for port B
output reg [DATA_WIDTH-1:0] data_out_b // read data for port B
);

reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];

always @(posedge clk) begin


// Port A operations

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 3/36
if (we_a)
mem[addr_a] <= data_in_a;
data_out_a <= mem[addr_a]; // read happens for Port A

// Port B operations
if (we_b)
mem[addr_b] <= data_in_b;
data_out_b <= mem[addr_b]; // read happens for Port B
end

endmodule

Key points: Both Port A and Port B can access `mem` concurrently. Each port writes and reads on the
clock edge. If both ports write to the same address on the same clock, the last one in the always
block (Port B in this code) will win (in simulation). In real dual-port RAM hardware, simultaneous
writes to the same location are not typically allowed or have undefined priority, so designers avoid
that. Reads are independent for each port.

Single-Port ROM
Single-Port ROM: A read-only memory with one address and one data output. The contents are
initialized at compile time (using an `initial` block or memory file). There is no write capability.
Here, we implement a simple ROM with an asynchronous read (the data is combinatorially available
based on the address). In synthesis, this would be realized as a fixed memory table or logic.

verilog

// Single-Port ROM (read-only memory)


module single_port_rom
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8)
(
input wire [ADDR_WIDTH-1:0] addr, // address to read
output wire [DATA_WIDTH-1:0] data_out // data output
);

// ROM content initialization (example: fill with incremental values)


reg [DATA_WIDTH-1:0] rom [0:(1<<ADDR_WIDTH)-1];
integer i;
initial begin
for (i = 0; i < (1<<ADDR_WIDTH); i = i + 1) begin
rom[i] = i[DATA_WIDTH-1:0];
// ^ Example pattern: data equals address (mod 2^DATA_WIDTH)
end
// In practice, you might read from a file:
// $readmemh("rom_init.mem", rom);
end

assign data_out = rom[addr]; // combinational read from ROM

endmodule

Key points: The ROM is initialized with some example data (here each address contains its own
address value as data for demonstration). The output `data_out` reflects the content at `addr`. There
is no clock or write enable since it's read-only. In simulation, the `initial` block preloads the
memory. In hardware, a ROM would be implemented via LUTs or a block ROM primitive with the
given contents.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 4/36
Dual-Port ROM
Dual-Port ROM: A ROM with two independent read ports. It allows two different addresses to be
read simultaneously from the same memory. Each port has its own address and data output. Writes
are not allowed (the memory is read-only). This is essentially implemented by allowing two
concurrent combinatorial reads from the single ROM array.

verilog

// Dual-Port ROM (two read ports)


module dual_port_rom
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8)
(
// Port A
input wire [ADDR_WIDTH-1:0] addr_a, // address for port A
output wire [DATA_WIDTH-1:0] data_out_a,// data output for port A
// Port B
input wire [ADDR_WIDTH-1:0] addr_b, // address for port B
output wire [DATA_WIDTH-1:0] data_out_b // data output for port B
);

// Shared ROM content


reg [DATA_WIDTH-1:0] rom [0:(1<<ADDR_WIDTH)-1];
initial begin
// Initialize ROM content (for example, all zeros)
integer j;
for (j = 0; j < (1<<ADDR_WIDTH); j = j + 1) begin
rom[j] = 0;
end
// You can preload specific values or use $readmemh as needed
end

// Two independent reads (combinational)


assign data_out_a = rom[addr_a];
assign data_out_b = rom[addr_b];

endmodule

Key points: The two outputs `data_out_a` and `data_out_b` will simultaneously present the data from
the ROM at `addr_a` and `addr_b` respectively. There is one physical memory `rom` array. If `addr_a
== addr_b`, both outputs will be the same data. No write logic is present, and no clock is needed for
pure combinational ROM reads.

2. Asynchronous FIFO
An asynchronous FIFO is a first-in-first-out buffer where the write operations are driven by a write
clock and read operations are driven by a read clock. The two clock domains can be different or
unrelated frequencies, so special care is taken to synchronize status signals (like pointers and flags)
across domains.
FIFO specifications addressed:

Depth: 16 bytes (can buffer up to 16 data packets of 1 byte each).


Write/Read signaling: A `write_enable` (we'll call it `w_en`) pulses high to write one byte; a
`read_enable` (`r_en`) pulses high to read one byte. We provide output signals (`write_done`
and `read_done`) that go high for one clock cycle when a write or read actually occurs.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 5/36
Flags:
goes high when 16 bytes are in the FIFO (cannot accept more data).
`full`
`almost_full` goes high when 15 bytes are in the FIFO (one slot left).
`empty` goes high when 0 bytes are in the FIFO (nothing to read).
`almost_empty` goes high when 1 byte is in the FIFO (one data left).
Safe operation: Writes are ignored when `full` is high (no writes allowed when full), and
reads are ignored when `empty` is high (no reads allowed when empty).
Clock domains: `wclk` for write side, `rclk` for read side. The design uses Gray-coded
pointers and dual-clock synchronizers to safely pass pointer values between domains (to
compute flags without metastability).

Below is the Verilog code for the asynchronous FIFO. It uses two pointers (`w_ptr` for write, `r_ptr`
for read), each expanded to an extra bit to detect wrap-around (so 5-bit pointers for 16-depth FIFO).
The pointers increment in their respective clock domains. Gray code is used when synchronizing the
pointer across clock domains to avoid multi-bit glitches. Comments in the code explain each section.

verilog

// Asynchronous FIFO with dual clock domains and status flags


module async_fifo
#(parameter DATA_WIDTH=8, parameter DEPTH=16)
(
// Write side
input wire wclk, // write clock
input wire wreset, // write-domain reset (active high)
input wire w_en, // write enable (pulse high to write one byte)
input wire [DATA_WIDTH-1:0] data_in,// data input (1 byte) for write
output reg write_done, // goes high for one wclk cycle when a byte is written
output reg full, // full flag (high when FIFO is full)
output reg almost_full,// almost-full flag (high when FIFO has 15/16 slots fil

// Read side
input wire rclk, // read clock
input wire rreset, // read-domain reset (active high)
input wire r_en, // read enable (pulse high to read one byte)
output reg [DATA_WIDTH-1:0] data_out,// data output (1 byte) for read
output reg read_done, // goes high for one rclk cycle when a byte is read
output reg empty, // empty flag (high when FIFO is empty)
output reg almost_empty// almost-empty flag (high when FIFO has 1/16 slots fil
);

// Calculate pointer width: need an extra bit for Gray code wrap detection
localparam ADDR_WIDTH = $clog2(DEPTH); // address bits (for 16, this is 4)
localparam PTR_WIDTH = ADDR_WIDTH + 1; // pointer bits (for 16, this is 5)

// FIFO memory array (DEPTH x DATA_WIDTH)


reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

// Binary and Gray-coded pointers for write and read


reg [PTR_WIDTH-1:0] w_ptr_bin, w_ptr_gray;
reg [PTR_WIDTH-1:0] r_ptr_bin, r_ptr_gray;

// Synchronized Gray pointers (cross domain)


reg [PTR_WIDTH-1:0] r_ptr_gray_wclk_sync1, r_ptr_gray_wclk_sync2;
reg [PTR_WIDTH-1:0] w_ptr_gray_rclk_sync1, w_ptr_gray_rclk_sync2;

// Binary pointers synchronized into opposite domain (after Gray->binary conversion)


reg [PTR_WIDTH-1:0] r_ptr_bin_wclk;
reg [PTR_WIDTH-1:0] w_ptr_bin_rclk;

// Functions for Binary <-> Gray conversion


function [PTR_WIDTH-1:0] bin2gray(input [PTR_WIDTH-1:0] bin);
begin
// Gray code: binary ^ (binary >> 1)
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 6/36
bin2gray = bin ^ (bin >> 1);
end
endfunction

function [PTR_WIDTH-1:0] gray2bin(input [PTR_WIDTH-1:0] gray);


integer i;
reg [PTR_WIDTH-1:0] bin;
begin
// MSB is same, each lower bit is XOR of Gray bit with previous binary bits
bin[PTR_WIDTH-1] = gray[PTR_WIDTH-1];
for (i = PTR_WIDTH-2; i >= 0; i = i - 1) begin
bin[i] = bin[i+1] ^ gray[i];
end
gray2bin = bin;
end
endfunction

// Write-domain logic (handles writes and updates write pointer and full flags)
always @(posedge wclk or posedge wreset) begin
if (wreset) begin
w_ptr_bin <= 0;
w_ptr_gray <= 0;
write_done <= 0;
full <= 1'b0;
almost_full <= 1'b0;
// Initialize sync registers
r_ptr_gray_wclk_sync1 <= 0;
r_ptr_gray_wclk_sync2 <= 0;
r_ptr_bin_wclk <= 0;
end else begin
// Synchronize read pointer Gray into write clock domain
r_ptr_gray_wclk_sync1 <= r_ptr_gray;
r_ptr_gray_wclk_sync2 <= r_ptr_gray_wclk_sync1;
r_ptr_bin_wclk <= gray2bin(r_ptr_gray_wclk_sync2);

// Default: no write done


write_done <= 1'b0;

// Write operation (if not full)


if (w_en && !full) begin
// Perform the write
mem[w_ptr_bin[ADDR_WIDTH-1:0]] <= data_in;
w_ptr_bin <= w_ptr_bin + 1'b1; // increment binary write pointer
w_ptr_gray <= bin2gray(w_ptr_bin + 1'b1); // update Gray-coded write pointer
write_done <= 1'b1; // indicate write occurred
end

// Update Full flag: true if write pointer and read pointer indicate full condition
// Full condition for FIFO: write pointer has wrapped and caught up to read pointer.
// This is detected when all address bits are equal and the wrap-around (MSB) is different
if ((w_ptr_bin[ADDR_WIDTH-1:0] == r_ptr_bin_wclk[ADDR_WIDTH-1:0]) &&
(w_ptr_bin[PTR_WIDTH-1] != r_ptr_bin_wclk[PTR_WIDTH-1])) begin
full <= 1'b1;
end else begin
full <= 1'b0;
end
// Update Almost Full flag: true if next write would make it full (i.e., currently 15 elem
// We check if not currently full, and if by incrementing write pointer we would meet the
reg [PTR_WIDTH-1:0] w_ptr_bin_next;
w_ptr_bin_next = w_ptr_bin + 1'b1;
if (!full &&
(w_ptr_bin_next[ADDR_WIDTH-1:0] == r_ptr_bin_wclk[ADDR_WIDTH-1:0]) &&
(w_ptr_bin_next[PTR_WIDTH-1] != r_ptr_bin_wclk[PTR_WIDTH-1])) begin
almost_full <= 1'b1;
end else begin
almost_full <= 1'b0;
end
end
end

// Read-domain logic (handles reads and updates read pointer and empty flags)
always @(posedge rclk or posedge rreset) begin
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 7/36
if (rreset) begin
r_ptr_bin <= 0;
r_ptr_gray <= 0;
read_done <= 0;
empty <= 1'b1;
almost_empty <= 1'b0;
// Initialize sync registers
w_ptr_gray_rclk_sync1 <= 0;
w_ptr_gray_rclk_sync2 <= 0;
w_ptr_bin_rclk <= 0;
data_out <= 0;
end else begin
// Synchronize write pointer Gray into read clock domain
w_ptr_gray_rclk_sync1 <= w_ptr_gray;
w_ptr_gray_rclk_sync2 <= w_ptr_gray_rclk_sync1;
w_ptr_bin_rclk <= gray2bin(w_ptr_gray_rclk_sync2);

// Default: no read done


read_done <= 1'b0;

// Read operation (if not empty)


if (r_en && !empty) begin
data_out <= mem[r_ptr_bin[ADDR_WIDTH-1:0]]; // read the data at the current read pointe
r_ptr_bin <= r_ptr_bin + 1'b1; // increment binary read pointer
r_ptr_gray <= bin2gray(r_ptr_bin + 1'b1); // update Gray-coded read pointer
read_done <= 1'b1; // indicate read occurred
end

// Update Empty flag: true if read and write pointers are the same (no data in FIFO)
if (w_ptr_bin_rclk == r_ptr_bin) begin
empty <= 1'b1;
end else begin
empty <= 1'b0;
end
// Update Almost Empty flag: true if the FIFO has exactly one data (about to become empty
// Check if not empty, and if by incrementing read pointer they would become equal.
reg [PTR_WIDTH-1:0] r_ptr_bin_next;
r_ptr_bin_next = r_ptr_bin + 1'b1;
if (!empty && (w_ptr_bin_rclk == r_ptr_bin_next)) begin
almost_empty <= 1'b1;
end else begin
almost_empty <= 1'b0;
end
end
end

endmodule

How it works:

Memory and Pointers: The FIFO uses a `mem` array of 16 bytes. `w_ptr_bin` and `r_ptr_bin`
are binary counters for write and read positions. They are 5 bits wide: the lower 4 bits select
the memory index (0–15), and the top bit is a wrap-around flag that toggles when the pointer
wraps from 15 back to 0. This extra bit helps distinguish full vs empty when pointers match.
Gray Code Pointers: `w_ptr_gray` and `r_ptr_gray` are Gray-coded versions of the binary
pointers. Only one bit changes at a time in Gray code, which is important for safe
synchronization across clock domains.
Synchronization: The read pointer Gray code (`r_ptr_gray`) is synchronized into the write
clock domain (`r_ptr_gray_wclk_sync1` -> `r_ptr_gray_wclk_sync2`), then converted to binary
`r_ptr_bin_wclk`. Similarly, `w_ptr_gray` is synced into the read domain and converted to
`w_ptr_bin_rclk`. This allows each domain to have a stable view of the other pointer (with a
few cycle delay). Two flip-flop synchronizers (two-stage registers) are used to mitigate
metastability.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 8/36
Writing (wclk domain): On `wclk`, if `w_en` is asserted and the FIFO is not full, the data on
`data_in` is written into memory at the address pointed by `w_ptr_bin`. Then `w_ptr_bin` is
incremented and `w_ptr_gray` is updated. The `write_done` signal pulses high for that cycle. If
the FIFO is full (`full` flag high), the write request is ignored (`write_done` remains low and
pointer doesn’t advance).
Reading (rclk domain): On `rclk`, if `r_en` is asserted and the FIFO is not empty, the data at
the address pointed by `r_ptr_bin` is read out to `data_out`. Then `r_ptr_bin` is incremented
and `r_ptr_gray` is updated. `read_done` pulses high for that cycle. If the FIFO is empty, the
read request is ignored (`read_done` stays low and pointer doesn’t move).
Flag logic:
Empty: In the read clock domain, `empty` is set when the synchronized write pointer
equals the read pointer (meaning no elements).
Full: In the write clock domain, `full` is set when the write pointer’s lower bits equal the
synchronized read pointer’s lower bits, and the write pointer’s wrap bit is the opposite of
the read pointer’s wrap bit (meaning the write pointer has wrapped around and caught
up to the read pointer without it moving, indicating the buffer is completely filled).
Almost Empty: When not empty, if one increment of the read pointer would catch up to the
synchronized write pointer, then only one element is left, so `almost_empty` is true.
Almost Full: When not full, if one increment of the write pointer would meet the full
condition relative to the synchronized read pointer, then the FIFO has one slot left, so
`almost_full` is true.
Safe operation: Because of the checks on `full` and `empty`, the module will not write data
when `full` (the write pointer will not advance), and will not read data when `empty` (the read
pointer will not advance). Thus, overflow and underflow are avoided. The flags ensure external
logic knows when it’s safe to write or read. The small latency in flag updates (due to
synchronization) is normal in asynchronous FIFOs.

Testing approach: To test this FIFO, apply write pulses on `wclk` and read pulses on `rclk` at
different rates. Verify that `data_out` comes out in the same order as written. Observe the
`full`/`almost_full` flags assert when 16 or 15 items have been written without reads, and
`empty`/`almost_empty` assert when the FIFO is empty or nearly empty. Also verify that attempting to
write when `full` or read when `empty` has no effect (and the done signals don’t assert in those
cases). The Gray code synchronization ensures stable cross-clock behavior during tests where `wclk`
and `rclk` are asynchronous.

3. Synchronous FIFO
A synchronous FIFO is a FIFO where both read and write operations occur on the same clock. This
simplifies design since there is a single clock domain (no need for Gray code or cross-domain
synchronization). The FIFO in this section meets the same specifications as the asynchronous FIFO
(16-byte depth, flags for full/almost/full/empty/almost/empty, etc.), but since everything is
synchronous, we maintain a simple count of elements and update flags directly.
Design approach: We use a single clock (`clk`) and one reset. Two pointers (`w_ptr` for write index,
`r_ptr` for read index) track positions in the memory. A counter `count` tracks the number of bytes
currently in the FIFO. The `count` makes it easy to set the flags:

`full` when `count == 16`


Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 9/36
`almost_full` when `count == 15`
`empty` when `count == 0`
`almost_empty` when `count == 1`

Simultaneous read and write in one cycle is supported: if both a read and write request occur on the
same clock edge (and the FIFO is neither completely full nor empty), the design will do both (the net
`count` stays the same, and one item is removed while another is added). We ensure that we don't
write when full and don't read when empty, per requirements. The `write_done` and `read_done`
signals indicate actual writes/reads.

verilog

// Synchronous FIFO (single clock) with full/empty flags and 16-byte depth
module sync_fifo
#(parameter DATA_WIDTH=8, parameter DEPTH=16)
(
input wire clk, // common clock for read/write
input wire reset, // synchronous reset (active high)
input wire write, // write enable pulse
input wire [DATA_WIDTH-1:0] data_in,// data byte to write
input wire read, // read enable pulse
output reg [DATA_WIDTH-1:0] data_out,// data byte read out
output reg write_done,// goes high for one cycle when a write occurs
output reg read_done, // goes high for one cycle when a read occurs
output reg full, // high when FIFO is full (16 bytes)
output reg almost_full,// high when FIFO has 15 bytes (almost full)
output reg empty, // high when FIFO is empty (0 bytes)
output reg almost_empty// high when FIFO has 1 byte (almost empty)
);

localparam ADDR_WIDTH = $clog2(DEPTH); // number of address bits (4 for 16-depth)

// Memory and pointers


reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
reg [ADDR_WIDTH-1:0] w_ptr; // write pointer (0-15)
reg [ADDR_WIDTH-1:0] r_ptr; // read pointer (0-15)
reg [ADDR_WIDTH:0] count; // element count (can range 0 to 16, needs 5 bits for 16)

integer i;
always @(posedge clk) begin
if (reset) begin
// Initialize pointers and flags
w_ptr <= 0;
r_ptr <= 0;
count <= 0;
data_out <= 0;
write_done <= 0;
read_done <= 0;
full <= 1'b0;
almost_full <= 1'b0;
empty <= 1'b1;
almost_empty<= 1'b0;
// (Optionally, clear memory for simulation clarity)
for (i = 0; i < DEPTH; i = i + 1) mem[i] <= 0;
end else begin
// Default: no operation done
write_done <= 1'b0;
read_done <= 1'b0;

if (write && !full && read && !empty) begin


// Simultaneous write and read
// Write new data to memory at w_ptr
mem[w_ptr] <= data_in;
w_ptr <= (w_ptr + 1) % DEPTH;
// Read data from memory at r_ptr
data_out <= mem[r_ptr];

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 10/36
r_ptr <= (r_ptr + 1) % DEPTH;
// count remains the same (one in, one out)
write_done <= 1'b1;
read_done <= 1'b1;
end else if (write && !full) begin
// Write only (if not full)
mem[w_ptr] <= data_in;
w_ptr <= (w_ptr + 1) % DEPTH;
count <= count + 1; // increase count
write_done <= 1'b1;
end else if (read && !empty) begin
// Read only (if not empty)
data_out <= mem[r_ptr];
r_ptr <= (r_ptr + 1) % DEPTH;
count <= count - 1; // decrease count
read_done <= 1'b1;
end

// Update flags based on new count


// Note: if a simultaneous read/write happened, count did not change.
if ( (write && !full && read && !empty) ) begin
// both happened, count unchanged
; // do nothing to count (already accounted as unchanged)
end else if (write && !full) begin
// we performed a write only
// (count was updated above by +1 in this branch)
end else if (read && !empty) begin
// we performed a read only
// (count was updated above by -1 in this branch)
end
// Now use count to set flags. If both read & write happened, count is unchanged from befo
full <= (count == DEPTH);
almost_full <= (count == DEPTH-1);
empty <= (count == 0);
almost_empty<= (count == 1);
end
end

endmodule

Explanation:

We use a single always block triggered on the rising edge of `clk` to handle all operations.
The `write` and `read` inputs are treated as request pulses. The logic checks conditions in
order:
1. Simultaneous write & read: If both `write` and `read` are asserted in the same cycle
(and the FIFO is neither full nor empty), we handle them together. One byte is written and
one byte is read. We increment `w_ptr` and decrement `r_ptr` (actually wrap them
around with modulo arithmetic). The `count` of stored elements remains the same, so we
don't change `count` in this branch. Both `write_done` and `read_done` are set to 1. This
models a concurrent enqueue and dequeue, which the FIFO can do without altering the
occupancy.
2. Write only: If only `write` is asserted (and FIFO not full), we write the new byte at
`w_ptr`, increment `w_ptr`, increment `count`, and assert `write_done`.
3. Read only: If only `read` is asserted (and FIFO not empty), we read the byte at `r_ptr`
into `data_out`, increment `r_ptr`, decrement `count`, and assert `read_done`.
4. If neither operation is requested, or if an operation is requested but not allowed (`write`
when full, or `read` when empty), nothing happens (the request is effectively ignored and
no done pulse is generated).
The pointers use modulo arithmetic (`(w_ptr + 1) % DEPTH`) to wrap around to 0 after reaching
the last address. This effectively creates the circular buffer behavior.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 11/36
After handling the pointer updates and memory access, we update the flag registers:
`full` is true if `count == DEPTH` (16).
`almost_full` if `count == DEPTH-1` (15).
`empty` if `count == 0`.
`almost_empty` if `count == 1`.
We carefully maintain `count` such that it never exceeds 16 or goes below 0. On reset,
`count=0`, `empty=1`, `full=0`.
The design ensures no writes happen when `full` (the `write && !full` condition gates it) and
no reads happen when `empty`.

Testing approach: Fill the FIFO by asserting `write` multiple cycles and confirm `full` becomes
high on the 16th write (and `almost_full` on the 15th). Verify that further write requests are ignored
when `full` is high. Then read some data and ensure `empty` goes high when all data has been
read (`almost_empty` on the second-to-last data). Test simultaneous `write` and `read` by asserting
both; the `count` should remain constant, and the data coming out should be the earliest written
data (FIFO order preserved) while a new data enters. The `write_done` and `read_done` signals
should pulse appropriately on each operation. The `data_out` should match the sequence of written
data (except of course the ones that were overwritten if more than 16 writes without reads, which
the design prevents by not writing when full).

4. ALU using Task and Function


Here we design an Arithmetic Logic Unit (ALU) that performs a variety of logical and arithmetic
operations on two operands. We will demonstrate the use of Verilog functions and tasks to
implement the ALU operations. The ALU will support the following operations (on, say, 8-bit
operands for example):

NOT (`!` or logical NOT on `A`; we'll implement bitwise NOT using `~` operator because `!` in
Verilog yields a 1-bit result logically)
AND (`&` bitwise AND)
OR (`|` bitwise OR)
XOR (`^` bitwise XOR)
ADD (`+`)
SUBTRACT (`-`)
MULTIPLY (`*`)
DIVIDE (`/`)
MODULUS (`%`)

We'll assign an opcode (operation code) to select each operation. For instance, we can use a 4-bit
opcode:

makefile

0000: NOT A (result = ~A)


0001: A AND B (result = A & B)
0010: A OR B (result = A | B)
0011: A XOR B (result = A ^ B)
0100: A + B (addition)
0101: A - B (subtraction)
0110: A * B (multiplication)

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 12/36
0111: A / B (division)
1000: A % B (modulus)

(This is one possible encoding; it will be documented in the code comments.)


We use a function to compute the result given inputs and an opcode (pure combinational logic
returning a value), and a task to update the output and possibly flags by calling that function. Using
a task allows us to perform additional actions like setting status flags or printing debug info,
illustrating how tasks (which can have side effects and control flow) differ from functions (which are
purely computation and return a value).

verilog

// ALU module demonstrating use of tasks and functions


module ALU
#(parameter WIDTH = 8)
(
input wire [WIDTH-1:0] A, // Operand A
input wire [WIDTH-1:0] B, // Operand B
input wire [3:0] opcode, // 4-bit operation code
output reg [WIDTH-1:0] result, // Result of the operation
output reg zero_flag// Example status flag: high if result is zero
);

// Function to compute the result based on opcode


function [WIDTH-1:0] alu_compute;
input [3:0] op;
input [WIDTH-1:0] opA;
input [WIDTH-1:0] opB;
begin
case(op)
4'b0000: alu_compute = ~opA; // NOT A (bitwise NOT)
4'b0001: alu_compute = opA & opB; // A AND B
4'b0010: alu_compute = opA | opB; // A OR B
4'b0011: alu_compute = opA ^ opB; // A XOR B
4'b0100: alu_compute = opA + opB; // A + B
4'b0101: alu_compute = opA - opB; // A - B
4'b0110: alu_compute = opA * opB; // A * B
4'b0111: begin
// A / B (integer division). Check for divide-by-zero:
if (opB != 0)
alu_compute = opA / opB;
else
alu_compute = {WIDTH{1'bX}}; // undefined if divide by zero
end
4'b1000: begin
// A % B (modulus). Check for divide-by-zero:
if (opB != 0)
alu_compute = opA % opB;
else
alu_compute = {WIDTH{1'bX}};
end
default: alu_compute = {WIDTH{1'b0}}; // default to 0 for undefined opcodes
endcase
end
endfunction

// Task to execute an ALU operation: updates result and flags


task perform_operation;
input [3:0] op;
input [WIDTH-1:0] opA, opB;
begin
// Use the function to get the result
result = alu_compute(op, opA, opB);
// Update status flag(s)
zero_flag = (result == {WIDTH{1'b0}}); // zero_flag = 1 if result is 0
// (Additional flags like carry, negative, overflow could be computed here as needed)
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 13/36
end
endtask

// Combinational logic: whenever inputs or opcode change, perform the operation


always @(*) begin
perform_operation(opcode, A, B);
// The task sets result and zero_flag
end

endmodule

Explanation:

The function `alu_compute` takes an opcode and two operands `opA` and `opB` and returns a
`WIDTH`-bit value. Inside, a `case` statement selects the operation:
For logical operations (NOT, AND, OR, XOR), it applies bitwise operators (`~`, `&`, `|`, `^`).
For arithmetic operations (ADD, SUB, MUL, DIV, MOD), it uses the corresponding
arithmetic operators (`+`, `-`, `*`, `/`, `%`). We included a simple check for division or
modulus by zero: if `opB` is zero, it returns `XXXX....X` (undefined) for simulation clarity.
In real hardware, division by zero would be an exceptional case to handle.
The function has a `default` case that returns 0 if an unsupported opcode is given.
This function is purely combinational (no side effects, no timing controls).
The task `perform_operation` takes an opcode and two operands as input. It calls the function
`alu_compute` to calculate the result, then it stores that into the `result` register (which is an
output of the module). It also sets a `zero_flag` output based on the result (true if the result is
all zeros). We could add more flags (carry-out, overflow, sign, etc.) similarly within this task. The
task can do multiple things sequentially (here just two assignments, but could include more
complex sequences or even `$display` calls for debugging in simulation if needed). Unlike a
function, a task does not return a value but can directly modify global variables or output
arguments.
In the always @(*) block, we call `perform_operation(opcode, A, B)` whenever any input
changes. This makes the ALU output (`result` and `zero_flag`) continuously reflect the
combination of `A`, `B`, and `opcode`. Essentially, this combinational always block ensures the
ALU computes in one simulation delta cycle as a pure combinational device. The task call here
is effectively acting like a combinational procedure to update outputs.

Benefits of using tasks and functions:

The function `alu_compute` makes the code for computing the result cleaner and reusable. If
we had multiple places needing to compute ALU operations (or if we wanted to test the
function separately), we could call it easily. It's self-contained and has no side effects.
The task `perform_operation` encapsulates the steps of setting the result and flags. This
separates the concerns of calculation vs. updating outputs. In a more complex ALU, the task
might handle things like triggering different sequential processes or logging. Here it shows
that tasks can call functions and then do additional work. Also, tasks can have delays or loops
for multi-cycle operations (though we didn't use delays here since all operations are single-
cycle combinational in this model).
Using the task in the always block also makes it clear what the intended high-level operation is
(perform an ALU operation given the opcode and inputs). This improves code readability.

Testing the ALU: One can write a simple testbench to verify each opcode:

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 14/36
verilog

// Simple Testbench (pseudo-code)


/*
reg [7:0] A, B;
reg [3:0] opcode;
wire [7:0] result;
wire zero_flag;
ALU #(8) alu_inst (.A(A), .B(B), .opcode(opcode), .result(result), .zero_flag(zero_flag));
initial begin
A = 8'h0F; B = 8'hF0;
opcode = 4'b0001; #10; // AND
$display("A & B = %h (result=%h, zero_flag=%b)", A & B, result, zero_flag);
opcode = 4'b0000; #10; // NOT A
$display("~A = %h (result=%h)", ~A, result);
opcode = 4'b0100; #10; // ADD
$display("A + B = %d (result=%d)", A, B, result);
// ... and so on for other opcodes
end
*/

This would show the ALU producing expected results for each operation and setting the `zero_flag`
when appropriate (e.g., if A and B are such that result is zero).

5. Light Bulb Control Circuit


Finally, we implement a light bulb control circuit with two push-button switches, A and B. The
behavior is as follows:

Pressing Switch A turns the light ON, and it stays ON even after A is released.
Pressing Switch B turns the light OFF, and it stays OFF even after B is released.
A and B are never pressed at the same time (so we don't have to handle simultaneous presses
ambiguously).

This behavior is essentially an SR latch (Set-Reset latch) controlled by push-button inputs:

Switch A acts as the Set (turn light on).


Switch B acts as the Reset (turn light off).

We need to remember the state of the light (on or off) between button presses, so a flip-flop or latch
is required to store that state. We can implement this with a simple sequential process triggered by
the button press events. Since push buttons are mechanical, in a real scenario you'd need
debouncing, but we'll assume idealized button signals (each press generates a clean pulse to 1).
We'll provide two implementations:

1. Asynchronous latch style (level-sensitive using posedge of buttons): Use an `always` block
sensitive to the rising edge of A or B. This will set or reset the light immediately when a button
is pressed, independent of a clock.
2. Synchronous (clocked) implementation: Use an `always @(posedge clk)` with synchronous
logic to set/reset the light. This assumes we sample the button states on a clock.

The asynchronous approach is closer to how the requirement is stated (immediate effect of button
press). Below is an asynchronous implementation for clarity:

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 15/36
verilog

// Light Bulb Control - asynchronous set/reset latch


module light_control(
input wire A, // push-button A (turn light ON when pressed)
input wire B, // push-button B (turn light OFF when pressed)
output reg light // output controlling the light (1 = ON, 0 = OFF)
);

// Initially, let's assume light is off.


initial light = 1'b0;

// Asynchronous control: trigger on a press of A or B


always @(posedge A or posedge B) begin
if (A)
light <= 1'b1; // Turn light ON when A is pressed
else if (B)
light <= 1'b0; // Turn light OFF when B is pressed
// Note: We assume A and B are never pressed simultaneously,
// so we don't handle the case of both posedges at once.
end

endmodule

In this code, whenever a rising edge on `A` is detected, we set `light` to 1 (on). Whenever a rising
edge on `B` is detected, we set `light` to 0 (off). Because the sensitivity list is looking for edges, the
light will change state exactly when a button is pressed. After the button is released (no more
edges), the `light` remains in its last state (thanks to the reg holding its value). This meets the
requirement that the light stays in the new state after the button is released.
Key points and assumptions:

We used `initial light = 0` to define the starting state (light off initially). In hardware, this
would correspond to a reset or power-on default.
The `always @(posedge A or posedge B)` makes this an asynchronous set/reset latch. If A and B
were pressed at the exact same time (which we assume doesn't happen), this could lead to a
race (both edges trigger, order could be indeterminate). But since the problem states that
never happens, our code is safe under that assumption.
If switch A is pressed multiple times in a row, the first press will turn the light on, subsequent
presses don't change the state (but still trigger the always block; we simply set `light <= 1`
again which it already is). Similarly for B turning it off.
There is no need for a clock in this design due to the asynchronous approach, but this means
the design is immediately responsive to button presses.

We can also do a synchronous version like this:

verilog

// Light Bulb Control - synchronous version


module light_control_sync(
input wire clk,
input wire reset,
input wire A, // button A
input wire B, // button B
output reg light // light output
);
always @(posedge clk or posedge reset) begin
if (reset) begin
light <= 1'b0; // start with light off on reset
end else begin

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 16/36
// On each clock, check buttons
if (A)
light <= 1'b1; // turn on if A pressed
else if (B)
light <= 1'b0; // turn off if B pressed
// if neither pressed, light retains its value (no change)
end
end
endmodule

This version samples the buttons on a clock edge. The effect is the same logically (A sets the light
on, B sets it off), just synchronized to a clock. This might be used if the system already has a clock
and we want to avoid asynchronous logic for synchronizer reasons.
Testing the light control: You can simulate by toggling A and B:

Initially, `light` should be 0 (off).


Apply a pulse on A (`A=1` for a moment) while B=0, observe `light` becomes 1 and stays 1
after A goes back to 0.
Apply a pulse on B (`B=1` for a moment) while A=0, observe `light` goes to 0 and stays 0 after
B release.
Try pressing A multiple times: once it's on, pressing A again keeps it on (no change, which is
fine).
Press B multiple times: once it's off, further presses keep it off.
Ensure that if A and B are never 1 at the same time, the logic behaves predictably. (If one did
accidentally press both, our code might handle one of them first based on which edge is seen
first. In simulation, both edges would trigger but the if/else ensures one takes effect if both
signals are high at the exact same simulation time. However, we rely on the given assumption
to avoid that scenario.)

This circuit is a straightforward storage element controlled by two inputs, fulfilling the requirements
for the light bulb control.

1. Single-Port RAM (Synchronous Read/Write)

Code

verilog

// Single-Port RAM (synchronous read/write)


module single_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // Configurable data and address width
(
input wire clk, // Clock signal (synchronous operations)
input wire we, // Write Enable (Active High)
input wire [ADDR_WIDTH-1:0] addr, // Address for read/write
input wire [DATA_WIDTH-1:0] data_in, // Input data for write
output reg [DATA_WIDTH-1:0] data_out // Output data for read
);

// Internal memory array declaration


reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1]; // Memory size = 2^ADDR_WIDTH

always @(posedge clk) begin


if (we) begin
mem[addr] <= data_in; // Write operation when we = 1
end

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 17/36
data_out <= mem[addr]; // Synchronous Read operation
end

endmodule

Explanation

1. Functionality

This module implements a Single-Port RAM with synchronous read and write.
The memory size is determined by `ADDR_WIDTH`, meaning if `ADDR_WIDTH=8`, the memory can
store 256 locations.
Each memory location stores a word of `DATA_WIDTH` bits.
Read and write operations occur only on the rising edge of `clk`.

2. Inputs & Outputs

Signal Type Description


`clk` `input wire` Clock signal (synchronous control)
`we` `input wire` Write Enable (`1` = Write, `0` = Read)

`addr` `input wire [ADDR_WIDTH-1:0]` Address for both read and write
`data_in` `input wire [DATA_WIDTH-1:0]` Data input for write operation
`data_out` `output reg [DATA_WIDTH-1:0]` Data output for read operation

3. Memory Structure

plaintext

mem[0] -> data word (DATA_WIDTH bits)


mem[1] -> data word
mem[2] -> data word
...
mem[255] -> data word (if ADDR_WIDTH = 8)

4. Operation

1. Write Operation
If `we = 1`, on `posedge clk`, the `data_in` is stored in `mem[addr]`.
2. Read Operation
The value at `mem[addr]` is read on every clock cycle, whether writing or not.
This ensures synchronous reading.

5. Key Features

Synchronous Read and Write: Both read and write happen only on `clk` edge.
Write-First Behavior: If `we = 1`, the write occurs first, and the latest value is available for
read immediately.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 18/36
No Asynchronous Reads: Unlike some RAMs, this design doesn’t support combinational reads
(asynchronous read).

Would you like me to proceed with the next code (Dual-Port RAM)?

2. Dual-Port RAM (1 Write, 1 Read)

Code

verilog

// Dual-Port RAM (separate read and write ports)


module dual_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // Configurable parameters
(
input wire clk, // Clock signal
// Write Port
input wire we, // Write Enable (Active High)
input wire [ADDR_WIDTH-1:0] waddr, // Write Address
input wire [DATA_WIDTH-1:0] data_in, // Write Data
// Read Port
input wire [ADDR_WIDTH-1:0] raddr, // Read Address
output reg [DATA_WIDTH-1:0] data_out // Read Data
);

// Internal memory array


reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1]; // Memory size = 2^ADDR_WIDTH

always @(posedge clk) begin


// Write operation (if write enable is active)
if (we) begin
mem[waddr] <= data_in;
end
// Read operation (independent from write)
data_out <= mem[raddr];
end

endmodule

Explanation

1. Functionality

This module implements a Dual-Port RAM where:


One port is dedicated for writing (`waddr`, `data_in`, `we`).
One port is dedicated for reading (`raddr`, `data_out`).
The write operation occurs only on the rising edge of `clk` if `we` is high.
The read operation occurs on the rising edge of `clk`, independently of writes.

2. Inputs & Outputs

Signal Type Description


`clk` `input wire` Clock signal (synchronous control)

`we` `input wire` Write Enable (`1` = Write, `0` = Read)

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 19/36
Signal Type Description
`waddr` `input wire [ADDR_WIDTH-1:0]` Write Address
`data_in` `input wire [DATA_WIDTH-1:0]` Data input for write operation

`raddr` `input wire [ADDR_WIDTH-1:0]` Read Address


`data_out` `output reg [DATA_WIDTH-1:0]` Data output for read operation

3. Memory Structure

plaintext

mem[0] -> data word (DATA_WIDTH bits)


mem[1] -> data word
mem[2] -> data word
...
mem[255] -> data word (if ADDR_WIDTH = 8)

4. Operation

1. Write Operation (waddr, data_in)


If `we = 1`, on `posedge clk`, `data_in` is written to `mem[waddr]`.
This operation does not affect the read process.
2. Read Operation (raddr → data_out)
On every clock cycle, `data_out` is updated with the contents of `mem[raddr]`.
This allows continuous and independent reading.

5. Key Features

True Dual Port: Read and Write operations occur on different addresses.
Synchronous Read and Write: All operations occur on `posedge clk`.
No Interference Between Read and Write: As long as `waddr ≠ raddr`, operations happen in
parallel.
Read-After-Write Behavior: If `waddr == raddr` and a write occurs, the next read cycle will
fetch the newly written data.

Would you like me to proceed with the next code (True Dual-Port RAM)?

3. True Dual-Port RAM (Two Independent Read/Write Ports)

Code

verilog

// True Dual-Port RAM (two independent read/write ports)


module true_dual_port_ram
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // Configurable parameters
(
input wire clk, // Clock signal
// Port A
input wire we_a, // Write Enable for Port A
input wire [ADDR_WIDTH-1:0] addr_a, // Address for Port A

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 20/36
input wire [DATA_WIDTH-1:0] data_in_a, // Write Data for Port A
output reg [DATA_WIDTH-1:0] data_out_a,// Read Data for Port A
// Port B
input wire we_b, // Write Enable for Port B
input wire [ADDR_WIDTH-1:0] addr_b, // Address for Port B
input wire [DATA_WIDTH-1:0] data_in_b, // Write Data for Port B
output reg [DATA_WIDTH-1:0] data_out_b // Read Data for Port B
);

// Internal memory array


reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1]; // Memory size = 2^ADDR_WIDTH

always @(posedge clk) begin


// Port A Operations
if (we_a) begin
mem[addr_a] <= data_in_a; // Write operation for Port A
end
data_out_a <= mem[addr_a]; // Read operation for Port A

// Port B Operations
if (we_b) begin
mem[addr_b] <= data_in_b; // Write operation for Port B
end
data_out_b <= mem[addr_b]; // Read operation for Port B
end

endmodule

Explanation

1. Functionality

This module implements a True Dual-Port RAM, meaning:


Port A can independently read or write data.
Port B can also independently read or write data.
Both ports can operate simultaneously, even on different addresses.
The memory is updated synchronously on the rising edge of `clk`.

2. Inputs & Outputs

Signal Type Description


`clk` `input wire` Clock signal (synchronous control)
`we_a` `input wire` Write Enable for Port A (`1` = Write)

`addr_a` `input wire [ADDR_WIDTH-1:0]` Address for Port A


`data_in_a` `input wire [DATA_WIDTH-1:0]` Data input for write operation (Port A)
`data_out_a` `output reg [DATA_WIDTH-1:0]` Data output for read operation (Port A)
`we_b` `input wire` Write Enable for Port B (`1` = Write)

`addr_b` `input wire [ADDR_WIDTH-1:0]` Address for Port B


`data_in_b` `input wire [DATA_WIDTH-1:0]` Data input for write operation (Port B)
`data_out_b` `output reg [DATA_WIDTH-1:0]` Data output for read operation (Port B)

3. Memory Structure

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 21/36
plaintext

mem[0] -> data word (DATA_WIDTH bits)


mem[1] -> data word
mem[2] -> data word
...
mem[255] -> data word (if ADDR_WIDTH = 8)

4. Operation

1. Port A: Read/Write
If `we_a = 1`, then `data_in_a` is written to `mem[addr_a]`.
`data_out_a` is always updated with the value at `mem[addr_a]`.
2. Port B: Read/Write
If `we_b = 1`, then `data_in_b` is written to `mem[addr_b]`.
`data_out_b` is always updated with the value at `mem[addr_b]`.

5. Key Features

True Dual Port: Both ports operate independently.


Simultaneous Reads and Writes: Read/write operations can happen at the same time on
different addresses.
Write-First Behavior: If a write and read occur on the same address in the same cycle, the
latest written data is returned.
Synchronous Read/Write: Everything updates on the rising edge of `clk`.

6. Edge Cases

If `addr_a == addr_b` and both ports write simultaneously, the final value in `mem[addr_a]` is
undefined (depends on hardware).
If `addr_a == addr_b` and one port reads while the other writes, the read may see the old value
or the new value, depending on the FPGA/memory configuration.

Would you like me to proceed with the next code (Single-Port ROM)?

4. Single-Port ROM (Read-Only Memory)

Code

verilog

// Single-Port ROM (Read-Only Memory)


module single_port_rom
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // Configurable parameters
(
input wire [ADDR_WIDTH-1:0] addr, // Address input
output reg [DATA_WIDTH-1:0] data_out // Data output
);

// ROM memory array (preloaded with data)


reg [DATA_WIDTH-1:0] rom [0:(1<<ADDR_WIDTH)-1];

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 22/36
// Initialize ROM with predefined values
initial begin
integer i;
for (i = 0; i < (1<<ADDR_WIDTH); i = i + 1) begin
rom[i] = i[DATA_WIDTH-1:0]; // Example: Each location stores its own address
end
// You can load actual data from a file if needed:
// $readmemh("rom_data.hex", rom); // Load data from a HEX file
end

// Combinational Read Operation (ROM is always available for read)


always @(*) begin
data_out = rom[addr]; // Read data from ROM at given address
end

endmodule

Explanation

1. Functionality

This module implements a Single-Port Read-Only Memory (ROM).


The memory content is preloaded and cannot be modified at runtime.
The ROM provides asynchronous read: whenever the address `addr` changes, `data_out`
updates instantly.

2. Inputs & Outputs

Signal Type Description

`addr` `input wire [ADDR_WIDTH-1:0]` Address input to fetch data from ROM
`data_out` `output reg [DATA_WIDTH-1:0]` Output data from the ROM

3. Memory Structure

plaintext

rom[0] -> 0x00


rom[1] -> 0x01
rom[2] -> 0x02
...
rom[255] -> 0xFF (if ADDR_WIDTH = 8)

(Default example where each ROM location stores its address value)

4. Operation

1. Preloading the ROM


The `initial` block fills the ROM with predefined values.
By default, each location stores its own address (`rom[i] = i[DATA_WIDTH-1:0]`).
A custom data file (`rom_data.hex`) can be loaded using `$readmemh("rom_data.hex",
rom);` for specific applications.
2. Read Operation

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 23/36
Whenever `addr` changes, the corresponding value from `rom[addr]` is assigned to
`data_out`.
The read operation is purely combinational, meaning `data_out` updates without a
clock.

5. Key Features

Combinational Read: No clock required; output updates immediately when `addr` changes.
Preloaded Memory: Contents are defined at initialization.
Fixed Data: No write operations are possible (ROM cannot be modified at runtime).
Efficient Storage: Ideal for look-up tables, constants, or firmware storage.

6. Edge Cases

If `addr` is outside the valid range, the output is undefined (FPGA behavior may vary).
Data corruption is impossible unless manually modified in hardware (e.g., via file updates).

Would you like me to proceed with the next code (Dual-Port ROM)?

5. Dual-Port ROM (Two Independent Read Ports)

Code

verilog

// Dual-Port ROM (Two Independent Read Ports)


module dual_port_rom
#(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=8) // Configurable parameters
(
// Port A
input wire [ADDR_WIDTH-1:0] addr_a, // Read Address for Port A
output reg [DATA_WIDTH-1:0] data_out_a,// Output Data for Port A
// Port B
input wire [ADDR_WIDTH-1:0] addr_b, // Read Address for Port B
output reg [DATA_WIDTH-1:0] data_out_b // Output Data for Port B
);

// ROM memory array (preloaded with data)


reg [DATA_WIDTH-1:0] rom [0:(1<<ADDR_WIDTH)-1];

// Initialize ROM with predefined values


initial begin
integer i;
for (i = 0; i < (1<<ADDR_WIDTH); i = i + 1) begin
rom[i] = i[DATA_WIDTH-1:0]; // Example: Each location stores its own address
end
// You can load actual data from a file if needed:
// $readmemh("rom_data.hex", rom); // Load data from a HEX file
end

// Combinational Read Operation for Port A and Port B


always @(*) begin
data_out_a = rom[addr_a]; // Read data from ROM at address A
data_out_b = rom[addr_b]; // Read data from ROM at address B
end

endmodule

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 24/36
Explanation

1. Functionality

This module implements a Dual-Port Read-Only Memory (ROM).


It allows two independent read operations at the same time.
The ROM content is preloaded and cannot be modified at runtime.
The read operation is combinational, meaning `data_out_a` and `data_out_b` update instantly
when their respective addresses change.

2. Inputs & Outputs

Signal Type Description


`addr_a` `input wire [ADDR_WIDTH-1:0]` Address input for Port A

`data_out_a` `output reg [DATA_WIDTH-1:0]` Output data for Port A


`addr_b` `input wire [ADDR_WIDTH-1:0]` Address input for Port B
`data_out_b` `output reg [DATA_WIDTH-1:0]` Output data for Port B

3. Memory Structure

plaintext

rom[0] -> 0x00


rom[1] -> 0x01
rom[2] -> 0x02
...
rom[255] -> 0xFF (if ADDR_WIDTH = 8)

(Default example where each ROM location stores its address value)

4. Operation

1. Preloading the ROM


The `initial` block fills the ROM with predefined values.
By default, each location stores its own address (`rom[i] = i[DATA_WIDTH-1:0]`).
A custom data file (`rom_data.hex`) can be loaded using `$readmemh("rom_data.hex",
rom);` for specific applications.
2. Read Operation
Whenever `addr_a` changes, `data_out_a` is updated with `rom[addr_a]`.
Whenever `addr_b` changes, `data_out_b` is updated with `rom[addr_b]`.
Both read ports function independently and simultaneously.

5. Key Features

Combinational Read: No clock required; output updates immediately when `addr_a` or


`addr_b` changes.
Preloaded Memory: Contents are defined at initialization.
Fixed Data: No write operations are possible (ROM cannot be modified at runtime).

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 25/36
True Dual Read Ports: Both `addr_a` and `addr_b` can access memory concurrently without
interference.
Efficient Storage: Ideal for applications requiring lookup tables, constant storage, or fixed
program execution.

6. Edge Cases

If `addr_a == addr_b`, both outputs will have the same data.


If `addr_a` or `addr_b` is outside the valid range, the output is undefined (depends on FPGA
behavior).
Data corruption is impossible unless manually modified in hardware (e.g., via file updates).

Would you like me to proceed with the next code (Asynchronous FIFO)?

6. Asynchronous FIFO (First-In-First-Out Buffer)

Code

verilog

// Asynchronous FIFO with Dual Clock Domains


module async_fifo
#(parameter DATA_WIDTH=8, parameter DEPTH=16) // Configurable FIFO depth and data width
(
// Write Interface
input wire wclk, // Write Clock
input wire wreset, // Write Reset (Active High)
input wire w_en, // Write Enable (1 = Write)
input wire [DATA_WIDTH-1:0] data_in,// Data input
output reg full, // FIFO Full Flag
output reg almost_full,// FIFO Almost Full Flag (15/16)

// Read Interface
input wire rclk, // Read Clock
input wire rreset, // Read Reset (Active High)
input wire r_en, // Read Enable (1 = Read)
output reg [DATA_WIDTH-1:0] data_out,// Data output
output reg empty, // FIFO Empty Flag
output reg almost_empty// FIFO Almost Empty Flag (1/16)
);

// Address width required for DEPTH


localparam ADDR_WIDTH = $clog2(DEPTH);

// FIFO memory array


reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

// Read and Write Pointers (Binary)


reg [ADDR_WIDTH:0] w_ptr_bin, r_ptr_bin;

// Gray-coded versions of pointers (to synchronize across clock domains)


reg [ADDR_WIDTH:0] w_ptr_gray, r_ptr_gray;

// Synchronized Gray-coded Read Pointer in Write Clock Domain


reg [ADDR_WIDTH:0] r_ptr_gray_wclk_sync1, r_ptr_gray_wclk_sync2;
reg [ADDR_WIDTH:0] w_ptr_gray_rclk_sync1, w_ptr_gray_rclk_sync2;

// Convert Gray Code to Binary


function [ADDR_WIDTH:0] gray2bin(input [ADDR_WIDTH:0] gray);
integer i;
reg [ADDR_WIDTH:0] bin;

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 26/36
begin
bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
for (i = ADDR_WIDTH-1; i >= 0; i = i - 1) begin
bin[i] = bin[i+1] ^ gray[i];
end
gray2bin = bin;
end
endfunction

// Convert Binary to Gray Code


function [ADDR_WIDTH:0] bin2gray(input [ADDR_WIDTH:0] bin);
begin
bin2gray = bin ^ (bin >> 1);
end
endfunction

// Write logic (Write clock domain)


always @(posedge wclk or posedge wreset) begin
if (wreset) begin
w_ptr_bin <= 0;
w_ptr_gray <= 0;
full <= 0;
almost_full<= 0;
r_ptr_gray_wclk_sync1 <= 0;
r_ptr_gray_wclk_sync2 <= 0;
end else begin
r_ptr_gray_wclk_sync1 <= r_ptr_gray; // Synchronize Read Pointer into Write Domain
r_ptr_gray_wclk_sync2 <= r_ptr_gray_wclk_sync1;

if (w_en && !full) begin


mem[w_ptr_bin[ADDR_WIDTH-1:0]] <= data_in; // Write data
w_ptr_bin <= w_ptr_bin + 1'b1; // Increment Write Pointer
w_ptr_gray <= bin2gray(w_ptr_bin + 1'b1); // Convert to Gray Code
end

// Update Full and Almost Full Flags


if ((w_ptr_bin[ADDR_WIDTH-1:0] == gray2bin(r_ptr_gray_wclk_sync2[ADDR_WIDTH-1:0])) &&
(w_ptr_bin[ADDR_WIDTH] != r_ptr_gray_wclk_sync2[ADDR_WIDTH])) begin
full <= 1'b1;
end else begin
full <= 1'b0;
end

almost_full <= (w_ptr_bin - gray2bin(r_ptr_gray_wclk_sync2) == DEPTH - 1);


end
end

// Read logic (Read clock domain)


always @(posedge rclk or posedge rreset) begin
if (rreset) begin
r_ptr_bin <= 0;
r_ptr_gray <= 0;
empty <= 1'b1;
almost_empty <= 1'b0;
w_ptr_gray_rclk_sync1 <= 0;
w_ptr_gray_rclk_sync2 <= 0;
data_out <= 0;
end else begin
w_ptr_gray_rclk_sync1 <= w_ptr_gray; // Synchronize Write Pointer into Read Domain
w_ptr_gray_rclk_sync2 <= w_ptr_gray_rclk_sync1;

if (r_en && !empty) begin


data_out <= mem[r_ptr_bin[ADDR_WIDTH-1:0]]; // Read data
r_ptr_bin <= r_ptr_bin + 1'b1; // Increment Read Pointer
r_ptr_gray <= bin2gray(r_ptr_bin + 1'b1); // Convert to Gray Code
end

// Update Empty and Almost Empty Flags


empty <= (w_ptr_bin == r_ptr_bin);
almost_empty <= (w_ptr_bin - r_ptr_bin == 1);
end
end
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 27/36
endmodule

Explanation

1. Functionality

Implements an Asynchronous FIFO, where:


Writes happen on `wclk` domain.
Reads happen on `rclk` domain.
Uses Gray code for safe pointer synchronization across clock domains.
Implements 16-depth FIFO that can store up to 16 bytes.

2. Inputs & Outputs

Signal Type Description


`wclk` `input wire` Write Clock
`wreset` `input wire` Write Reset (Active High)
`w_en` `input wire` Write Enable (`1` = Write)
`data_in` `input wire [DATA_WIDTH-1:0]` Data input for write operation

`full` `output reg` FIFO Full Flag (`1` = Cannot write)


`almost_full` `output reg` FIFO Almost Full Flag (15/16 full)
`rclk` `input wire` Read Clock
`rreset` `input wire` Read Reset (Active High)
`r_en` `input wire` Read Enable (`1` = Read)
`data_out` `output reg [DATA_WIDTH-1:0]` Data output from FIFO
`empty` `output reg` FIFO Empty Flag (`1` = Cannot read)

`almost_empty` `output reg` FIFO Almost Empty Flag (1/16 full)

3. FIFO Structure

plaintext

mem[0] -> Byte 1


mem[1] -> Byte 2
mem[2] -> Byte 3
...
mem[15] -> Byte 16 (FIFO Full)

4. Operation

1. Write Operation
If `w_en = 1` and FIFO is not full, data is written to `mem[w_ptr_bin]`.
`w_ptr_bin` (Binary Write Pointer) increments and converts to Gray code for
synchronization.
2. Read Operation
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 28/36
If `r_en = 1` and FIFO is not empty, data is read from `mem[r_ptr_bin]`.
`r_ptr_bin` (Binary Read Pointer) increments and converts to Gray code.

5. Key Features

Dual Clock Domain: Read and Write operations run at different frequencies.
Gray Code Synchronization: Prevents metastability in cross-clock operations.
Full & Almost Full Flags: Indicate FIFO status for controlled writes.
Empty & Almost Empty Flags: Indicate FIFO status for controlled reads.

6. Edge Cases

If full flag is HIGH, further writes are blocked.


If empty flag is HIGH, further reads are blocked.

Would you like me to proceed with the next code (Synchronous FIFO)?

7. Synchronous FIFO (First-In-First-Out Buffer)

Code

verilog

// Synchronous FIFO (Single Clock Domain)


module sync_fifo
#(parameter DATA_WIDTH=8, parameter DEPTH=16) // Configurable FIFO depth and data width
(
input wire clk, // Common Clock
input wire reset, // Synchronous Reset (Active High)

// Write Interface
input wire write, // Write Enable (1 = Write)
input wire [DATA_WIDTH-1:0] data_in, // Data input
output reg full, // FIFO Full Flag
output reg almost_full,// FIFO Almost Full Flag (15/16)

// Read Interface
input wire read, // Read Enable (1 = Read)
output reg [DATA_WIDTH-1:0] data_out, // Data output
output reg empty, // FIFO Empty Flag
output reg almost_empty // FIFO Almost Empty Flag (1/16)
);

// Address width required for DEPTH


localparam ADDR_WIDTH = $clog2(DEPTH);

// FIFO memory array


reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

// Read and Write Pointers


reg [ADDR_WIDTH-1:0] w_ptr, r_ptr;
reg [ADDR_WIDTH:0] count; // Keeps track of the number of elements in FIFO

// Synchronous FIFO logic


always @(posedge clk or posedge reset) begin
if (reset) begin
w_ptr <= 0;
r_ptr <= 0;
count <= 0;
full <= 0;

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 29/36
almost_full <= 0;
empty <= 1;
almost_empty <= 0;
data_out <= 0;
end else begin
// Write Operation
if (write && !full) begin
mem[w_ptr] <= data_in; // Write data to FIFO memory
w_ptr <= (w_ptr + 1) % DEPTH; // Increment write pointer
count <= count + 1; // Increase count
end

// Read Operation
if (read && !empty) begin
data_out <= mem[r_ptr]; // Read data from FIFO memory
r_ptr <= (r_ptr + 1) % DEPTH; // Increment read pointer
count <= count - 1; // Decrease count
end

// Update Flags
full <= (count == DEPTH);
almost_full <= (count == DEPTH - 1);
empty <= (count == 0);
almost_empty <= (count == 1);
end
end

endmodule

Explanation

1. Functionality

Implements a Synchronous FIFO where both read and write operations occur in the same
clock domain (`clk`).
Uses circular addressing with `w_ptr` (write pointer) and `r_ptr` (read pointer).
Keeps track of the number of elements stored in the FIFO using `count`.
Implements full, almost full, empty, and almost empty flags to signal FIFO state.

2. Inputs & Outputs

Signal Type Description


`clk` `input wire` Common Clock for Read/Write operations
`reset` `input wire` Synchronous Reset (FIFO clears when `1`)
`write` `input wire` Write Enable (`1` = Write)
`data_in` `input wire [DATA_WIDTH-1:0]` Data input for write operation
`full` `output reg` FIFO Full Flag (`1` = Cannot write)
`almost_full` `output reg` FIFO Almost Full Flag (15/16 full)

`read` `input wire` Read Enable (`1` = Read)


`data_out` `output reg [DATA_WIDTH-1:0]` Data output from FIFO
`empty` `output reg` FIFO Empty Flag (`1` = Cannot read)
`almost_empty` `output reg` FIFO Almost Empty Flag (1/16 full)

3. FIFO Structure

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 30/36
plaintext

mem[0] -> Byte 1


mem[1] -> Byte 2
mem[2] -> Byte 3
...
mem[15] -> Byte 16 (FIFO Full)

4. Operation

1. Write Operation
If `write = 1` and FIFO is not full, data is written to `mem[w_ptr]`.
`w_ptr` (Write Pointer) increments and wraps around after reaching `DEPTH - 1`.
`count` increases by `1`.
2. Read Operation
If `read = 1` and FIFO is not empty, data is read from `mem[r_ptr]`.
`r_ptr` (Read Pointer) increments and wraps around after reaching `DEPTH - 1`.
`count` decreases by `1`.

5. Key Features

Single Clock Domain: All operations are synchronized with `clk`.


Circular Buffering: Uses modular arithmetic for seamless wrap-around.
Full & Almost Full Flags: Indicate FIFO status for controlled writes.
Empty & Almost Empty Flags: Indicate FIFO status for controlled reads.

6. Edge Cases

If full flag is HIGH, further writes are blocked.


If empty flag is HIGH, further reads are blocked.

Would you like me to proceed with the next code (ALU using Task and Function)?

8. ALU (Arithmetic Logic Unit) Using Task and Function

Code

verilog

// ALU (Arithmetic Logic Unit) using Tasks and Functions


module ALU
#(parameter WIDTH = 8) // Configurable operand width
(
input wire [WIDTH-1:0] A, // Operand A
input wire [WIDTH-1:0] B, // Operand B
input wire [3:0] opcode, // Operation code (4-bit)
output reg [WIDTH-1:0] result, // Result of ALU operation
output reg zero_flag // Zero Flag (1 if result is 0)
);

// Function to compute ALU operation result


function [WIDTH-1:0] alu_compute;

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 31/36
input [3:0] op;
input [WIDTH-1:0] opA;
input [WIDTH-1:0] opB;
begin
case(op)
4'b0000: alu_compute = ~opA; // NOT A (Bitwise NOT)
4'b0001: alu_compute = opA & opB; // AND
4'b0010: alu_compute = opA | opB; // OR
4'b0011: alu_compute = opA ^ opB; // XOR
4'b0100: alu_compute = opA + opB; // ADD
4'b0101: alu_compute = opA - opB; // SUB
4'b0110: alu_compute = opA * opB; // MUL
4'b0111: begin
if (opB != 0)
alu_compute = opA / opB; // DIV (Avoid Divide by Zero)
else
alu_compute = {WIDTH{1'bX}}; // Undefined on division by zero
end
4'b1000: begin
if (opB != 0)
alu_compute = opA % opB; // MOD (Avoid Modulo by Zero)
else
alu_compute = {WIDTH{1'bX}};
end
default: alu_compute = {WIDTH{1'b0}}; // Default case: return 0
endcase
end
endfunction

// Task to execute ALU operation


task perform_operation;
input [3:0] op;
input [WIDTH-1:0] opA, opB;
begin
result = alu_compute(op, opA, opB); // Call function to compute result
zero_flag = (result == 0); // Set zero flag if result is zero
end
endtask

// Combinational ALU Execution


always @(*) begin
perform_operation(opcode, A, B); // Call task whenever input changes
end

endmodule

Explanation

1. Functionality

Implements an Arithmetic Logic Unit (ALU) capable of performing logical and arithmetic
operations.
Uses a function (`alu_compute`) to compute results.
Uses a task (`perform_operation`) to update the output and status flags.

2. Inputs & Outputs

Signal Type Description


`A` `input wire [WIDTH-1:0]` Operand A
`B` `input wire [WIDTH-1:0]` Operand B
`opcode` `input wire [3:0]` Operation Code

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 32/36
Signal Type Description

`result` `output reg [WIDTH-1:0]` ALU Result


`zero_flag` `output reg` Zero Flag (1 if result is 0)

3. Operation Codes

Opcode Operation Description

`0000` NOT A Bitwise NOT of A


`0001` A AND B Bitwise AND
`0010` A OR B Bitwise OR
`0011` A XOR B Bitwise XOR
`0100` A+B Addition
`0101` A-B Subtraction
`0110` A*B Multiplication

`0111` A/B Division (avoids divide by zero)


`1000` A%B Modulo (avoids modulo by zero)
Default 0 Returns 0 if invalid opcode

4. Execution Flow

1. Function `alu_compute`
Takes `A`, `B`, and `opcode` as inputs.
Computes the required operation based on `opcode`.
Returns the result.
2. Task `perform_operation`
Calls `alu_compute` to compute the result.
Updates `result` and `zero_flag`.
3. Always Block Execution
Calls `perform_operation` whenever `A`, `B`, or `opcode` changes.
Ensures ALU operates in combinational mode.

5. Key Features

Task and Function Usage: Separates computation (`function`) from status updates (`task`).
Zero Flag Handling: `zero_flag = 1` if `result == 0`.
Handles Division and Modulo: Avoids divide/modulo by zero.
Combinational Logic: Executes operation immediately when inputs change.

6. Edge Cases

Invalid Opcode: Defaults to `0`.


Divide/Modulo by Zero: Returns undefined (`XXXX` in simulation).

Would you like me to proceed with the next code (Light Bulb Control Circuit)?

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 33/36
9. Light Bulb Control Circuit Using Two Push-Button Switches

Code

verilog

// Light Bulb Control Circuit using Two Push-Buttons


module light_control (
input wire A, // Push-Button A (turns ON the light)
input wire B, // Push-Button B (turns OFF the light)
output reg light // Light bulb output (1 = ON, 0 = OFF)
);

// Initial state (light is OFF)


initial light = 1'b0;

// Asynchronous control: Trigger on a press of A or B


always @(posedge A or posedge B) begin
if (A)
light <= 1'b1; // Turn light ON when A is pressed
else if (B)
light <= 1'b0; // Turn light OFF when B is pressed
end

endmodule

Explanation

1. Functionality

Implements a light control circuit using two push-button switches:


Button A (`A`) turns the light ON.
Button B (`B`) turns the light OFF.
Once the light turns ON, it stays ON even after releasing `A`.
Once the light turns OFF, it stays OFF even after releasing `B`.
Assumption: `A` and `B` are never pressed at the same time.

2. Inputs & Outputs

Signal Type Description


`A` `input wire` Push-Button A (turns ON the light)
`B` `input wire` Push-Button B (turns OFF the light)

`light` `output reg` Light Output (`1` = ON, `0` = OFF)

3. Working Mechanism

If `A` is pressed (posedge A) → `light` is set to `1` (ON).


If `B` is pressed (posedge B) → `light` is set to `0` (OFF).
No change when neither `A` nor `B` is pressed (light retains state).

4. Key Features

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 34/36
Asynchronous Behavior: Responds immediately to `A` or `B` without a clock.
State Retention: The light remains ON or OFF after pressing a button.
No Debounce Logic: This assumes ideal switch signals. In real hardware, a debounce circuit
is recommended.

5. Edge Cases

Simultaneous Press (`A == 1` and `B == 1`):


The `always` block triggers twice; order of execution depends on simulation.
Since `B` appears after `A`, the light would turn OFF immediately.
To avoid this, ensure `A` and `B` are never pressed at the same time.

Alternative: Clocked Version

For a synchronous version, use a clock to sample button states:

verilog

// Synchronous Light Control with Clock


module light_control_sync (
input wire clk, // Clock Signal
input wire reset, // Reset (Active High)
input wire A, // Push-Button A
input wire B, // Push-Button B
output reg light // Light Output
);

always @(posedge clk or posedge reset) begin


if (reset)
light <= 1'b0; // Reset to OFF state
else if (A)
light <= 1'b1; // Turn ON if A is pressed
else if (B)
light <= 1'b0; // Turn OFF if B is pressed
end

endmodule

Comparison: Asynchronous vs. Synchronous

Asynchronous (`always @(posedge A or Synchronous (`always


Feature
posedge B)`) @(posedge clk)`)
Response Time Immediate (no delay) Delayed until `clk` edge
Hardware
Simpler Needs clock synchronization
Complexity
Noise Sensitivity High (may need debouncing) Low (can sample at intervals)

Power
Lower (no continuous clock) Higher (depends on clock rate)
Consumption

Final Thoughts

The asynchronous version is simpler and more responsive but requires debouncing.

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 35/36
The synchronous version is more stable in noisy environments.
Choose based on application needs (e.g., mechanical switches need debounce logic).

This completes the Verilog HDL Lab Level 2.3 implementation. Let me know if you need
modifications, explanations, or testbenches for these designs! 🚀

Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 36/36

You might also like