Picoblaze interrupts and edge detection

1. Using a pushbutton to trigger the ISR and toggle an LED





To detect the rising edge, the following debouncing circuit is used.
When btn_interrupt == btn_clean == 0 at the beginning, it stays at db_count <= 0 and never runs the 'else' part.
As soon as a difference is detected, db_count starts incrementing. However for vibrations, it may never reach the max count and it will go back to the first conition and set db_count to 0 again.
When the delay is satisfied, it indicates that the bounces are gone and the signal is stable so we can give btn_interrupt to btn_clean which is a '1'.
Then is repeats this process to output bounce-free square pulses at btn_clean.

    // 1. De-bouncer (Ignores noise for ~10ms)
    // ---------------------------------------------------------
    reg [19:0] db_count = 0;
    reg btn_clean = 0;
    always @(posedge clk) begin
        if (btn_interrupt == btn_clean)
            db_count <= 0;
        else begin
            db_count <= db_count + 1;
            if (db_count == 20'd1_000_000) btn_clean <= btn_interrupt;
        end
    end


In the following edge detection code, 'else if (btn_clean && !btn_prev)', if the result in the parenthesis is 1 that means btn_clean is 1 and btn_prev is 0 which defines a rising edge.
The PicoBlaze has a dedicated interrupt_ack signal. Once the processor starts its Interrupt Service Routine (ISR), it sends this "handshake" signal back. The code then resets interrupt_reg to 0 so the processor isn't interrupted repeatedly for the same single press.

In this logic, the handshake ensures that a single event (like a button press) is captured reliably and processed exactly once. Think of it as a three-step relay:
Request (The Button): Your external logic (the btn_clean rising edge) sets interrupt_reg to 1. This is your hardware saying, "Hey, something happened!"
Acknowledgment (The CPU): The PicoBlaze processor samples its interrupt input every two clock cycles. Once it detects the 1, it saves its state, jumps to the Interrupt Service Routine (ISR), and pulses its interrupt_ack output pin. This is the CPU saying, "I got your message, I'm handling it now."
Clear (The Logic): Your code sees the interrupt_ack signal and immediately resets interrupt_reg back to 0. This is your hardware saying, "Great, I'll put the sign down so I don't bother you again until the next button press."

Why this specific order matters:
If you didn't have Step 2 (ACK): You might clear the signal before the CPU even saw it, and the button press would be ignored.
If you didn't have Step 3 (Clear): The CPU would finish the ISR, see that the interrupt signal is still high, and immediately start the ISR again—trapping your program in a loop forever.

 // ---------------------------------------------------------
    // 2. Edge Detection & Interrupt Handshake
    // ---------------------------------------------------------
    reg btn_prev = 0;
    reg interrupt_reg = 0;
   
    always @(posedge clk) begin
        btn_prev <= btn_clean;
       
        if (interrupt_ack)
            interrupt_reg <= 0;      // Clear when PicoBlaze acknowledges
        else if (btn_clean && !btn_prev)
            interrupt_reg <= 1;      // Set on rising edge
    end

The PicoBlaze interrupt pin acts as an external signal to force the processor to pause its current task and jump to a special interrupt service routine (ISR). The interrupt_ack (Interrupt Acknowledge) pin is an output signal from the processor confirming that the interrupt has been accepted, allowing external hardware to reset the interrupt request.

Interrupt Input Pin: When driven high (logic '1'), the processor finishes its current instruction, saves the program counter and flags, and jumps to a predefined vector address (default is 3FF hex).
Interrupt_ack Output Pin: Used to inform external hardware that the interrupt request is being serviced. This pin goes high during the RETURNI instruction in the ISR to signal the external device to lower its interrupt signal.
Purpose: These pins allow the PicoBlaze to react to asynchronous events (like button presses or timer timeouts) in the FPGA fabric without constantly polling for status changes.



PicoBlaze (KCPSM6) clears the interrupt_ack pin by itself.
The interrupt_ack signal is a single clock cycle pulse. It is automatically generated by the processor's internal state machine at the moment it recognizes the interrupt and begins the transition to the interrupt vector.

How the Pulse Works
Trigger: When the interrupt input is driven High and interrupts are enabled, PicoBlaze detects the signal.
Acknowledgment: The processor immediately drives the interrupt_ack pin High for exactly one clock cycle.
Automatic Clear: After that single clock cycle, PicoBlaze automatically returns the interrupt_ack pin to Low ('0') without requiring any specific instruction in your code

if (write_strobe && port_id == 8'h00)
This condition acts as an address decoder and write enabler.
write_strobe: A signal that indicates a write operation is occurring.
port_id == 8'h00: Checks if the target address (port) is 00.
&&: Both conditions must be true. The register only updates if a write is enabled and the address matches port 00.



The instruction that makes write_strobe true is OUTPUT.
In PicoBlaze architecture, the OUTPUT instruction performs two simultaneous hardware actions:
It places the value of a register (like s0) onto the out_port bus.
It asserts the write_strobe signal for exactly one clock cycle to tell the external hardware (your led_out_reg) to capture that data.



Registers ( through ): These are high-speed storage locations inside the CPU itself. The processor uses them directly to perform math or logic (like your XOR and LOAD commands). They are the "workspace."
Memory (Scratchpad): This is a separate storage area (accessed with STORE and FETCH). It is slower than registers but holds more data.

LOAD s0, 00: You are putting data into a register to work on it.
STORE s0, scratch_loc: You are moving that data out of the CPU and into memory for safekeeping.

RETURNI stands for Return from Interrupt. It’s a specialized command used specifically at the end of an Interrupt Service Routine (ISR) like the one in your code. When a PicoBlaze processor handles an interrupt, it automatically "locks the door" (disables interrupts) so a second interrupt doesn't crash the first one. RETURNI is the proper way to "unlock the door" and go back home in a single step.

When the PicoBlaze reaches the instruction OUTPUT s0, led_port:
Data: It puts the value of register s0 (e.g., 8'h00) onto the wires named out_port.
Address: It puts the value of led_port (defined as 00) onto the wires named port_id.
The "Now!" Signal: It flips a switch called write_strobe to High (1) for exactly one clock cycle.

Note that '.port_id(port_id)' leaves the wire port_id a variable. The assembly code line 'OUTPUT s0, led_port', the 'led_port' defines the port_id for this write operation.



This applies to applications where the output is connected to different ports. The code could be written as:

reg [7:0] led_out_reg   = 8'h00;
reg [7:0] seven_seg_reg = 8'h00; 

always @(posedge clk) begin
    if (write_strobe) begin
        if (port_id == 8'h00) led_out_reg   <= out_port;
        if (port_id == 8'h01) seven_seg_reg <= out_port; // Logic to load it
    end
end

For the '.in_port(8'h00)' connection, .in_port(8'h00) is "shorted" to zero because this particular program doesn't need to read any external data.
Here is the breakdown of why it was designed that way:
1. It relies on Interrupts, not Polling
In many programs, the CPU constantly "polls" (checks) a button by reading an input port. However, this code uses the Interrupt system:
The hardware (your Verilog edge detector) "pokes" the PicoBlaze using the interrupt pin.
The PicoBlaze immediately jumps to the isr_routine.
Because the interrupt already told the CPU that the button was pressed, the CPU doesn't need to actually "read" the button's state via an input port.
2. Avoiding "Floating" Inputs
In digital logic, you never want an input pin to be "floating" (disconnected). If you aren't using the input feature of the PicoBlaze, you must tie it to a constant value (like 00) to ensure the processor doesn't read random electrical noise if an INPUT instruction is accidentally executed.
3. Simplicity
Since the only goal of this specific code is to toggle an LED based on a button press, and that button press is handled by the interrupt logic, adding an input multiplexer would just add unnecessary complexity to the Verilog.





2. Count the button press events using the seven segment display modules

The major adition to the verilog code is:



To handle the counting, the following code needs to be included in your assembly:





3. Using an external pulse to trigger ISR and count the rising edges

Create a 3-bit vector to store the JA[0] values. 





Here is the assembly code:






Tasks:
1. Repeat the work in Section 1 to toggle one LED by pressing a button and trigger the ISR. Show video demos (10 points).
2. Count the button press events and display it on the seven segment diaplay module (counts from 0 to 15 then resets to 0). The counting must happen in the ISR. Show video demos (20 points)
3. Count 0.5 Hz 0-3.3V function generator rising edges through JA[0] and diaply the counts on the seven segment display module. Show video demos (20 points)