Cryptic "missing edge-sensitive event" error


#1

(See update below)

When attempting to upload/build my project, I get an error with no indication of where it came from:

ERROR: Missing edge-sensitive event for this signal!
scons: *** [hardware.blif] Error 1

Running “apio build -v” in the terminal gives me a tiny bit more information

3.7.7. Executing PROC_DFF pass (convert process syncs to FFs)
ERROR: Missing edge-sensitive event for this signal!
scons: *** [hardware.blif] Error 1

I’ve looked online for the cause of the error and haven’t found anything besides this unanswered yosys issue. Usually I’d use process of elimination to find what the offending code is, unfortunately my verilog file is over 700 lines long as it’s been generated from a netlist.

I have little experience with the toolchain and have no idea how to coax more information out of it.

If anyone knows how this error comes about, or how to find the line number/component name of the cause, it would be greatly appreciated. Knowing either of these would probally help me enough to fix it.

UPDATE

Ok, much debugging later I think I fixed it. I grep-ed for the error message, found it in yosys, downloaded the source, grep-ed again, found code that generates the message in passes/proc/proc_dff.cc, modified print statements to tell me what the element was, installed clang & 'gang, compiled yosys, replaced the executable files in the .apio folder, and ran it.

Almost everyone of my flip-flops caused the error, apparently code similar to the peice below would cause the “missing edge-sensitive event” error.

reg flipflop = 0;
always @(posedge thing1 or posedge thing2 or posedge thing3) begin
    if(thing1 || thing2)
        flipflop <= 0;
    else if(thing3)
        flipflop <= 1;

I’m a Verilog newbie but the code above appears valid to me, the fix was to change the above code to the below code

reg flipflop = 0;
always @(posedge thing1 or posedge thing2 or posedge thing3) begin
    if(thing1 || thing2)
        flipflop <= 0;
    else
        flipflop <= 1;

This now gives me a “Multiple edge sensitive events” error which is fixed with the below code

reg flipflop = 0;
always @(posedge thing1 or posedge thing2 or posedge thing3) begin
    if(thing1)
        flipflop <= 0;
    else if(thing2)
        flipflop <= 0;
    else
        flipflop <= 1;

Again, I believe all the example code works is identical behavior, valid Verilog (correct me if I’m wrong), which means yosys likes it a certain way. If this is the case, I’ll report it as a yosys bug. Anyway, it builds/uploads now (I had to reinstall the dev enviorment because I messed up something in yosys), still doesn’t behave how I want it to, but I guess thats my problem :wink:


#2

The main storage element for sequential logic inside an FPGA is a D-type flipflop. This loads its D input to the output at either the rising or falling edge of a single clock input. In the iCE40, they can also optionally have a synchronous or asynchronous set or reset input (but not both), which forces the output high or low. An asynchronous set/reset does this immediately, a synchronous input only does this at the next clock edge. In general, it is best practice to use synchronous set/reset, for reasons described later.

This means, to map correctly to the FPGA, the sensitivity list of a always block should have at most two edges (one for the clock and one for asynchronous set/reset) in it, and usually only one. It is always best to use as few clocks as possible - in almost all simple designs, one clock is enough. This allows the limited global routing resources to be used, and avoids metastability issues crossing clock domains.

It is bad practice to have asynchronous set/resets or clocks driven directly from combinational logic (clocks rarely need to come from logic of any kind, and should generally use IO directly, PLLs, internal oscillators, etc). This is because FPGA architectures are not glitch free, so spurious pulses can occur as signals change.

I would strongly advise you to rewrite your logic to use a single clock, and detect signal edges differently. For example, you can do something like this to detect a rising edge without needed to clock the flipflop with it directly

reg signal_last;

always @(posedge clk) begin
    signal_last <= signal;
    if (!signal_last && signal)
        // rising edge has occurred
end

#3

Hmm, the iCE40 DFFs only support one async reset? I wonder how that will affect the resource utilization…

I’m not an expert yet, but I understand sync VS async circuits fairly well, and know that aysnc logic development is fairly experimental at this point. If I designed this circuit from scratch, I’d implement it with minimal clocks and proper practices, however this circuit was designed ~44 years ago and is fundamentally based on async circuitry including latches. Re-writing it as a synchronous design would mean rebuilding it from scratch, which I don’t want to do as I’m trying to accurately re-create the original circuit. Your advice is appreciated though.

Note: If your wondering why I’m putting a decades-old async design on the tinyFPGA, look no further than here.


#4

In a case like the code you provide, the synthesis tool is able to fake multiple async resets - so long as they all set/reset to the same value, using a LUT as an OR gate - the “async set” signal is then used as a clock with the data connected to 1.

What you can’t build easily on the iCE40 is a D flipflop with clock/data and both async set and reset.

In all these cases, the fact FPGA logic isn’t glitch free is likely to cause debugging hell anyway.