MyHDL

External signal edge detect fails (always detects)

I have a tinyfpga board with e external FAULT signal which is active low and should in practice always stay high or otherwise the ‘machine’ is stopped until reset again.
My problem is: The machine is always stopped - ENABLE is low.

I initialize FAULT, ENABLE to be 1.
ie. in the pin config as follows:
set_io -pullup yes FAULT D9 #pin 15

and in the testbench:
FAULT = Signal(intbv(1)[1:])|
ENABLE= Signal(intbv(1)[1:])

Experiments with the following program show that at some time the FAULT must be read zero and the ‘machine’ gets halted - ENABLE is 0.
If i set FAULT to (fix) 1 in my program this will not happen and ENABLE is 1.

The FAULT input is treated ‘as usual’ with a synchronization flip flop or two for input.
I wrote some comments on my experiments in def negedge_detect() in the program.

Somebody sees what’s wrong here?

# unit_standalone tests only a interrupt with help of a led
# convert with run_standalone.py and upload to tinyfpga
from myhdl import *

@block
def unit_standalone(CLK,LED,USBPU,RESET,ENABLE,FAULT,CLK4M,CLK8K,MISO,MOSI,SCLK,RXRDY,TXRDY,SS_N,S1,S2,S3,S4,S5,S6,S7,RELAY,n,nspi):
	maxd=2
	clk4M  = Signal(bool(0))
	clk2M  = Signal(bool(0))
	pwmcounter = Signal(modbv(0, min=0,max=2**11))   #this counter is fix 11 bits for 16MHz CLK and 8kHz loop
	slowcounter= Signal(modbv(0, min=0,max=2**14))   #this counter is uses to produce RELAY_trig 2s after start
	#helpers:
	Fault_d = Signal(bool(1))
	Fault_dd= Signal(bool(1))
	Fault_ddd= Signal(bool(1))
	flag    = Signal(bool(0))
	latch   = Signal(bool(0))


	@always_seq(CLK.posedge, reset=RESET)   #CLK is 16MHz
	def clocks():
		pwmcounter.next=pwmcounter+1
		CLK4M.next = pwmcounter[1]		#8MHz for edge delay is output
		clk2M.next = pwmcounter[2]		#2MHz for edge delay
		CLK8K.next = pwmcounter[10]		#8kHz control loop clock is output

	@always_seq(flag.posedge, reset=RESET)
	def haltpwm():
			ENABLE.next=0


	@always_seq(CLK8K.negedge, reset=RESET)
	def probes():
		if ENABLE:
			LEDLat.next=not LEDLat
			LED.next=LEDLat     #debug: indicate with toggling led
		else:
			LED.next= 0

	@always_seq(CLK.posedge,reset=RESET)
	def negedge_detect():
		Fault_d.next=FAULT  #  1 asynchronous FAULT is simulated to be always 1
		Fault_dd.next=Fault_d  #therefore this is 1 too
		flag.next=  Fault_dd & ~Fault_d #as both are 1, flag is always 0 and ENABLE is always on as initialized so

	#As soon as FAULT is used instead of a 1 the machine will not start, i.e. the led will not show.
	return instances()

Therefore i assume that FAULT must have been 0 at some point in time since reset. How comes? Its an open pin and it is init to be 1. How to avoid this situation? (I tried a delay but no success).

The ‘testbench’ for conversion and interfacing is as follows:

import os
from myhdl import *
from unit_standalone import unit_standalone
from math import pi

print('run_standalone:')
print('defining bit width, defining external signals')
print('converting unit_standalone')

n=10			#bitwidth of internal signals limited by pwm counter to 11
nspi=16			#spi register width

def run_hw_bench(hdl):
	CLK=Signal(bool(0))
	LED=Signal(bool(0))
	USBPU=Signal(bool(0))
	RESET = ResetSignal(1,active = 0, isasync=True)
	CLK8K = Signal(bool(0))     #-> use to sync dsp
	MISO = Signal(bool(0))
	MOSI = Signal(bool(0))
	SCLK = Signal(bool(0))
	SS_N = Signal(bool(0))
	RXRDY = Signal(bool(0)) 	#rising edge: do now read rxdata
	TXRDY = Signal(bool(0)) 	#rising edge: do now prepare txdata
	CLK4M = Signal(bool(0))		#4MHz clock output to DSP
	S1	  = Signal(intbv(0)[1:])  #inverted output
	S2    = Signal(intbv(0)[1:])
	S3	  = Signal(intbv(0)[1:])
	S4 	  = Signal(intbv(0)[1:])
	S5	  = Signal(intbv(0)[1:])
	S6	  = Signal(intbv(0)[1:])
	S7	  = Signal(intbv(0)[1:])
	RELAY = Signal(intbv(0)[1:])
	FAULT = Signal(intbv(1)[1:])
	ENABLE= Signal(intbv(1)[1:])    # enable pwm unit


	dut = unit_standalone(CLK,LED,USBPU,RESET,ENABLE,FAULT,CLK4M,CLK8K,MISO,MOSI,SCLK,RXRDY,TXRDY,SS_N,S1,S2,S3,S4,S5,S6,S7,RELAY,n,nspi)

	dut.convert(hdl=hdl)

run_hw_bench(hdl='Verilog')

unit_standalone doesn’t look like it will do anything as you’re not returning any instances. Is this the actual code you’re using?

Yes, though I edited a little upon publishing.

The last statement is return instances and other from the fact that it somehow reads a zero from FAULT input the code actually works in my tinyfpga. When setting a 1 instead of FAULT into the code the led will light at half power as expected when ENABLE is active.

Also i can change the code in the sense that i can directly toggle the led withinput FAULT high to low transition.

In FPGAs you can consider Flip-Flop reset value is ‘0’.
In your design, you define default values to be ‘1’ for some signals. This is a requirement for your design to work correctly and this works in simulation.
However, when converting, you don’t request the converter to set default values. So, your design doesn’t work in the FPGA.
Try dut.convert(hdl=hdl, initial_values=True)

I’ll be glad to try that - and also to understand better whats going on.

Unfortunately i bricked my fpga and cannot get a new one within days.

I’ll be back
Than you!

What do you mean by bricked ?

I wanted to say that my tinyfpga is not working anymore. But that is a hw fault by me measuring voltage with a multimeter set to measure current…

But that is a hw fault by me measuring voltage with a multimeter set to measure current…

Too bad.

I asked because, it is possible to brick a XO2 with bad configuration and sometimes it is possible to un-brick it.

Ahh, right.
I have written a little hw check now that flips all the io’s on the tinyfpga BX. Looks good so far. Next is checking the raspberry pi which failed to communicate over spi.

Finally i will follow up on the initialization.

Now that my hw works, i have investigated this:
the convert(hdl=hdl, initial_values=True) does not make a difference.

But with the scope i found that:

  • FAULT pin remains high at all times during reset and startup procedure (set_io -pullup yes FAULT D9)
  • A copy of FAULT now is my TEST pin ( set_io -nowarn TEST D2):
    TEST.next = FAULT

TEST pin indeed goes low during reset/startup for approx 75ms!
this may explain why i always detect a negedge on FAULT

During configuration, it is not surprising signals are held to ‘0’.

FPGA reset sequence is complex.
If your device is a MACHXO2, read this document, chapter Device Wake-up Sequence. You can read Once the internal DONE is asserted the MachXO2 will respond to input data. This means, the FAULT signal state is not considered till the full configuration of the chip is done.

You have to check how the reset signal is synthesized by the tool.
The RESET signal can be synthesized as GSR or standard signal. Both behave differently, especially at startup.

However, why do you initialize some signals to ‘1’ ?
Since you need falling edge detection, Fault_d and Fault_dd do not need to be set to ‘1’ at init/when reset is asserted.

Yes, i only need to initialize the signals to 1 which are at their output equipped with pull-up resistors to never go low without intent. (i.e. inverted gate driver output).

My device is a tinyfpga and the wake-up sequence is surely hw dependent.

Therefore i decided another approach:
I have a RELAY Signal which will go high after 4s (definitely long enough after wake-up).
Then i use RELAY to enable flag first after 4s like so:

@always_seq(flag.posedge, reset=RESET)
def haltloop():
	ENABLE.next=0

# precharge relay to close on RELAY_trig
@always_seq(RELAY_trig.posedge, reset=RESET)
def RelayOn():
	RELAY.next=1

@always_seq(CLK8K.posedge, reset=RESET)
def slow_counter():
	slowcounter.next=slowcounter+1
	RELAY_trig.next = slowcounter[14]  #precharge relay delay counter 4s

@always_seq(CLK8K.negedge, reset=RESET)
def control_loop():
	if ENABLE:
		LEDLat.next= ~LEDLat
		LED.next=LEDLat     #debug: indicate with toggling led
	else:
		LED.next= 0

@always_seq(CLK.posedge,reset=RESET)
def negedge_detect():
	Fault_d.next=FAULT  
	Fault_dd.next=Fault_d  
	flag.next= RELAY & Fault_dd & ~Fault_d

The result is still no led lighting up. (and then react by going out on FAULT pulled low once).
Hmmm…??

Your last excerpt of code shows multiple design mistakes.

A general rule is to use one clock in a design.
When this is not possible, specific synchronization techniques must be used to pass data from one clock domain to another.

In your code, you use standard signals (flag and RELAY_trig) as clocks. Unless you have very good reasons to do this, this is bad practice.
FPGAs have limited clock resources and these resources have specific requirements.

You use CLK8K on both positive and negative edges. Again, unless you have good reasons, don’t do that.

You use asynchronous signals directly, without synchronization logic :
Fault_d.next=FAULT : FAULT is asynchronous to CLK. Using such statements will have meta-stability problems. The best case is, your system does not work immediately. The worst case (most often) is your system works most of the time but fails “sometimes”.
Search for “FPGA metastability” on your preferred web search engine. You’ll get plenty of results.

The first thing to do in your design is to choose a master clock.
The second thing to do in your design is to synchronize external signals on the clock with, at least, 2 flip-flops to eliminate meta-stability problems.
Reminder : If you really need multiple clocks in your design, you have to use synchronization techniques to pass data from one clock domain to the other.

Allright!

These things may explain some.

i.e. i checked with the scope what happens on flag and it showed a 50ns pulse on TEST pin some 80ms after reset when using the following code:
(CLK is 16MHz)

@always_seq(CLK.negedge,reset=RESET)
def negedge_detect():
	Fault_d.next= FAULT
	flag.next=  ~Fault_d  #active low
	TEST.next= flag 

→ just an example of what goes wrong

So I’ll read some about clock synchronization techniques before doing further experiments.

Thanks!

This let me think you need to denoise the FAULT input. Or to make it stronger. Maybe the pull-up has a too high value for your application.

After following the tips by DrPi i finally solved my FAULT detector issue.

The first thing to do is not to rely on FAULT (low active input) and ENABLE (output and internal use for enabling) beeing set to 1 from reset onwards. (In fact these signals are 0 many ms after a reset).

So i defined new:
FAULT = Signal(bool(0)) # asynchronous FAULT input low active pulled up
DISABLE= Signal(bool(0)) # DISABLE pwm unit output low active pulled up

Then i need synchronization for the asynchronous FAULT input which needs two FF’s and a further FF for edge detect:

@always_seq(CLK.posedge,reset=RESET)
def fault_detect():
	Fault_1d.next= FAULT
	Fault_2d.next=Fault_1d
	Fault_3d.next=Fault_2d
	if  Fault_3d & ~Fault_2d:
		flag.next=1
		if cnt>10:
			DISABLE.next=0
		else:
			DISABLE.next=1

This code also incorporates a (very short) debounce on the FAULT input with counter cnt:

cnt    = Signal(modbv(0,min=0,max=2**4))        #debounce counter

@always_seq(flag.posedge, reset=RESET)
def debounce():
	cnt.next=cnt+1

Now my simulated control loop can indeed be interrupted by FAULT being pulled low:

@always_seq(CLK8K.posedge, reset=RESET)
def control_loop():
	if ~DISABLE:
		LEDLat.next= ~LEDLat
		LED.next=LEDLat     #debug: indicate working loop with toggling led
	else:
		LED.next= 0

The last thing about the clock domains i wasn’t able to follow.
I think that all my Signals are synchronous to CLK because they are derived with a counter (CLK8K, cnt etc.)
So i can only see one clock domain.

Actually the CLK8K and other divided clocks are separate clock domains. As they are generated by logic the timing (delay in regard with CLK) may/will differ from compilation to compilation. I have no idea how good the opensource tools handle this.

As Josy said, derived clocks must be considered separated clock domains.

You also have to keep in mind that clocks signals are routed with specific limited resources.

  • There are special locations in the FPGA that permit the connection between standard logic and clock traces but this should be avoided as much as possible.
  • Specific chip pins can be directly connected to the clock traces. With some FPGAs, it is also possible to connect standard I/Os to clock nets but with “degraded” performance.

Unless you have good reasons, you should not create derived clocks but use one clock and strobe signals.

Ah yes!
My problem is resolved so far and the use of one clock and strobe signal remains for me to get examined.
This clock domain topic is a bit off the title of this request for support and therefore i will try it and come back later if i don’t get it right.

Thanks a lot - those hints were just right!

You’re welcome :slight_smile: