0
\$\begingroup\$

I made a single-cycle RISC CPU in Verilog, and it works well on a real FPGA I have. It has multiple components that communicate over an internal bus. One of these components is a programmable timer, that has a prescaler and counter, and has multiple ways of counting time. It works fine, and can generate PWM output (verified).

My problem lies with the concept of timer generated interrupts: Many microcontrollers can generate internal interrupt signal when the timer counter resets, and so does my timer. It has an interrupt line, that it pulls low when it wraps around:

always @(posedge clk or negedge reset) begin
    if(!reset) begin
        timer_irq <= 1'b1;
    end
    else begin
        if ((prescaler_value >= (prescaler_threshold))
             && (counter_value >= (counter_threshold))) begin
            timer_irq <= 1'b0;
        end
        else begin
            timer_irq <= 1'b1;
        end
    end
end

This logic works fine conceptionally (verified in simulator and with a logic analyzer on real hardware: The interrupt signal is generated when it should be), but it will not be sampled by my CPU, since it also samples the interrupts in the write-back phase on rising clock edge:

always @(posedge clk)
begin
  // ...
  if (((~irq_sources & irq_mask) != 3'b0) && (in_isr == 1'b0)) begin
      ipc <= pc_next;
      pc <= irq_target;
      in_isr <= 1'b1;
   end
   else begin
      pc <= pc_next;
   end
end

So the interrupt goes low when my clock goes high, and the FPGA does not sample this as an interrupt. It basically "just misses" the interrupt. My interrupt implementation works fine with external interrupts. Maybe the problem appears because of propagation times? Does anybody have a hint how I could design this better?

Here is a GTKWave plot of the problem: The interrupt is only "sampled" one clock cycle later (the main program is idling at address 0x0028, and the ISR is at address 0x002C)

enter image description here

\$\endgroup\$
1
\$\begingroup\$

This type of thing is normal within the synchronous system. It is like you have one DFF after another - the signal will propagate to second DFF only when first has changed its state on previous clock edge.

Think about changing clock edge your interrupt line is activated with - you can use negative edge of the clock, and then next time posedge happens, just raised interrupt signal will be sampled down the line.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for this answer - But wouldn't using the falling edge introduce another clock domain and open my project up to issues regarding metastability? \$\endgroup\$ – Katharina Mar 5 at 19:40
  • 1
    \$\begingroup\$ It is the same clock you use, but another edge. Time between falling and raising edge should be enough for signal to propagate through the circuit to be sampled on opposite edge of the same clock. I say should be, but you anyway must test this at the model level and on real device. Example: clocking embedded RAM using posedge, and reading data from RAM using negedge, thus half clock period must be bigger than RAM access time... Ah yes, and mind clock duty cycle! \$\endgroup\$ – Anonymous Mar 5 at 19:43
  • \$\begingroup\$ Thank you! It now reacts to the IRQ on real hardware: i.imgur.com/VkO4GJv.png It now shows another problem in that it jumps to 0x0 instead of the ISR, which doesnt happen on the simulation. But that's something for me to debug now. Thanks for your help! \$\endgroup\$ – Katharina Mar 5 at 19:53
  • 1
    \$\begingroup\$ That may happen because you raise interrupt, but does not initialize jump vector. If it is the case, initialize jump vector value together with raising IRQ signal. In general - prepare everything for the interrupt handling in the always construct where you raise the interrupt (or in different always constructs driven by the negedge of the clock). \$\endgroup\$ – Anonymous Mar 5 at 20:04
  • 1
    \$\begingroup\$ I debugged it and figured it out: I had a priority-selector that checked which IRQ signals are low, and decided which ISR address from the jump vector to present to the CPU, with the GPIO IRQs being lower priority than the invalid instruction and timer interrupts. It was somehow bugged (nested if-else always executing the last else branch, giving address 0x0, the fallback). I now replaced it with individual if statements, and everything works fine. I dont understand why this didn't work, but oh well. It's fixed now: i.imgur.com/HxpspmO.png Thanks again for your help. \$\endgroup\$ – Katharina Mar 5 at 20:08
1
\$\begingroup\$

You could possibly change the logic so that you generate an interrupt pulse more than 1 clock cycle wide. Start when you wrap around but continue until the count is 2 or 3 maybe? Will an interrupt that is a few clock cycles wide cause your CPU to process it as multiple interrupts?

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ That would work too, since my CPU does not "remember" IRQs that arrived when it already handles an interrupt, and interrupts are disabled when inside an ISR. \$\endgroup\$ – Katharina Mar 5 at 19:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.