Prompt and Hints
Implement a synthesizable 17×4 LIFO stack using three modules:
- stack_data_mux – selects between two 4-bit inputs,
- stack_ram – a synchronous 17×4 RAM with asynchronous read, and
- stack_pointer – an up/down counter.
The stack pointer increments on push when not full, decrements on pop when not empty, and initializes to zero on reset. The RAM performs synchronous writes under stack_we and asynchronous reads gated by stack_re. The mux selects between stack_data_1_in and stack_data_2_in.
stack_mux_sel = 1 selects stack_data_1_in, and stack_mux_sel = 0 selects stack_data_2_in.
Top-level wiring must connect:
- mux output to RAM write data,
- stack pointer output to RAM address,
- RAM output to stack_data_out.
Inputs:
clk– clockstack_data_1_in– external 4-bit datastack_data_2_in– PC 4-bit datastack_reset– active-high resetstack_push– push requeststack_pop– pop requeststack_mux_sel– selects data sourcestack_we– RAM write enablestack_re– RAM read enable
Outputs:
stack_data_out– 4-bit read datafull_o– full flagempty_o– empty flag
Expose the internal stack pointer address on a top-level wire named stack_addr_w, and the mux output on a top-level wire named stack_data_in_w, so that external verification can observe these signals.
pointer_control: The pointer should only change when stack_push or stack_pop is explicitly requested. Think about how the pointer value should reflect the number of valid stack entries.
operation_timing: Consider the order in which RAM access and pointer updates logically occur during push and pop operations. The interaction between stack_we, stack_re, and pointer updates is often the key to correct behavior.
memory_access: The RAM write should occur based on the current pointer value, while the read should reflect the entry at the pointer location after any required updates. Reflect on how asynchronous read interacts with a synchronous write design.
boundary_conditions: Ensure that the pointer never moves beyond its valid range (0 to 16). Review how full and empty should behave at these boundaries and how push/pop requests should be handled when these limits are reached.
flag_consistency: The empty and full flags should always be consistent with the current pointer value. Revisit how these flags should behave immediately after push, pop, or reset.
mux_behavior: Think carefully about which data source should reach the RAM during a push operation and how stack_mux_sel governs this choice.
reset_handling: After reset, the system should reflect an empty stack. Consider which internal signals or registers need to be in a known state to satisfy this condition.
address_alignment: RAM addressing must always correspond to the pointer. If data appears incorrect when reading or writing, inspect how the address is being supplied to the RAM.
read_enable_semantics: stack_re should determine when stack_data_out reflects meaningful RAM contents. Consider what value stack_data_out should show when stack_re is not active.
debug_strategy: Trace the pointer, RAM address, and flags across several cycles of push and pop activity. Misalignment among these signals is often the cause of test failures.