Memory Modules
Memory Modules
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!
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.
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
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
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
);
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.)
verilog
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
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
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:
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
// 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)
// 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);
// 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);
// 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:
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)
);
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;
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
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).
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
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 12/36
0111: A / B (division)
1000: A % B (modulus)
verilog
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.
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
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).
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).
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
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.
verilog
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:
This circuit is a straightforward storage element controlled by two inputs, fulfilling the requirements
for the light bulb control.
Code
verilog
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`.
`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
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)?
Code
verilog
endmodule
Explanation
1. Functionality
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
3. Memory Structure
plaintext
4. Operation
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)?
Code
verilog
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
);
// 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
3. Memory Structure
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 21/36
plaintext
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
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)?
Code
verilog
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
endmodule
Explanation
1. Functionality
`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
(Default example where each ROM location stores its address value)
4. 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)?
Code
verilog
endmodule
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 24/36
Explanation
1. Functionality
3. Memory Structure
plaintext
(Default example where each ROM location stores its address value)
4. Operation
5. Key Features
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
Would you like me to proceed with the next code (Asynchronous FIFO)?
Code
verilog
// 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)
);
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
Explanation
1. Functionality
3. FIFO Structure
plaintext
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
Would you like me to proceed with the next code (Synchronous FIFO)?
Code
verilog
// 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)
);
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.
3. FIFO Structure
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 30/36
plaintext
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
6. Edge Cases
Would you like me to proceed with the next code (ALU using Task and Function)?
Code
verilog
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
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.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 32/36
Signal Type Description
3. Operation Codes
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
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
endmodule
Explanation
1. Functionality
3. Working Mechanism
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
verilog
endmodule
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