Flops with parametrized ALOAD aren't optimized and synthesis fails

It seems that the existing dff optimizations don’t detect when ALOAD and AD inputs are driven from the same or complementary signals. This prevents synthesis of designs that contain dffs with parametrized asynchronous reset logic:

read_verilog aldffs.v 
proc
hierarchy -check -top top
synth_ice40 -device lp -top top -json top.json

...
4.34. Executing DFFLEGALIZE pass (convert FFs to types supported by the target).
ERROR: FF top.$auto$ff.cc:337:slice$930 (type $_ALDFFE_PNP_) cannot be legalized: dffs with async set and reset are not supported
module aload_gdff(...);

   parameter INIT_SOURCE = 1'b1;
   parameter INIT_STATE = 1'b1;
   parameter ASYNC_SOURCE = 1'b1;
   parameter POLARITY = 1'b1;

   input CLK;
   input CE;
   input PT1;
   input PT2;
   input PT3;
   input DIN;
   input SET;
   input RES;
   output wire Q;

   wire	  shared_init;
   assign shared_init = INIT_SOURCE == 1'b1 ? PT1 : PT3;

   wire	  por_init;
   assign por_init = 1'b0 ^ shared_init;

   wire	  async_init;
   assign async_init = ASYNC_SOURCE == 1'b0 ? PT2 : ~ POLARITY;

   wire	  preset;
   assign preset = INIT_STATE == 1'b0 ? por_init : async_init;
   wire	  reset;
   assign reset  = INIT_STATE == 1'b1 ? por_init : async_init;

   wire   aload;
   assign aload = (preset == POLARITY) || (reset == POLARITY);

   wire   ad;
   assign ad = preset == POLARITY;

   always @(posedge CLK, posedge aload) begin
      if (aload)
	Q <= ad;
      else if (CE == 1'b1)
	Q <= DIN;
   end

endmodule // aload_gdff



module top(...);

   input CLK;
   input CE;
   input PT1;
   input PT2;
   input PT3;
   input [3:0] DIN;
   output [3:0]	Q;


   aload_gdff
     #(.INIT_SOURCE(1'b1), .INIT_STATE(1'b0), .ASYNC_SOURCE(1'b1), .POLARITY(1'b1))
   set_1_dff
     (.CLK(CLK), .CE(CE), .PT1(PT1), .PT2(PT2), .PT3(PT3), .DIN(DIN[0]), .Q(Q[0]));

   aload_gdff
     #(.INIT_SOURCE(1'b1), .INIT_STATE(1'b1), .ASYNC_SOURCE(1'b1), .POLARITY(1'b1))
   res_1_dff
     (.CLK(CLK), .CE(CE), .PT1(PT1), .PT2(PT2), .PT3(PT3), .DIN(DIN[1]), .Q(Q[1]));

   aload_gdff
     #(.INIT_SOURCE(1'b1), .INIT_STATE(1'b0), .ASYNC_SOURCE(1'b1), .POLARITY(1'b0))
   set_0_dff
     (.CLK(CLK), .CE(CE), .PT1(PT1), .PT2(PT2), .PT3(PT3), .DIN(DIN[2]), .Q(Q[2]));

   aload_gdff
     #(.INIT_SOURCE(1'b1), .INIT_STATE(1'b1), .ASYNC_SOURCE(1'b1), .POLARITY(1'b0))
   res_0_dff
     (.CLK(CLK), .CE(CE), .PT1(PT1), .PT2(PT2), .PT3(PT3), .DIN(DIN[3]), .Q(Q[3]));

endmodule // top

This is the situation after opt (ALOAD polarity is 1’b1 on both):

  • $440 has PT1 connected to both ALOAD and AD - equivalent to AD = 1’b1
  • $441 has inverted PT1 connected to AD - equivalent to AD = 1’b0

To get my current project through synthesis, I added functionality to opt_dff that detects situations where AD is the same or complement of ALOAD and replaces the input to AD with the respective constant. The existing opt_dff code then kicks in and replaces the $aldffe with a $adffe.
My changes are at opt_dff: Change AD to const if it’s identical or complementary to ALOAD and work good enough for me.

I could raise a PR if this is interesting for upstream.

I think async support in general is not very well supported in Yosys. In particular, abc9 doesn’t support ADFFs, which is (as an example) why synth_quicklogic has a techmap rule for pp3 to map ADFFs with constant CLR and PRE to a synchronous flop before calling abc9 (and then mapping back after). Note that the following script does finish:

read_verilog aldffs.v
proc
hierarchy -check -top top
synth_ice40 -device lp -top top -run :map_ffs
async2sync
synth_ice40 -device lp -top top -run map_ffs:

I will also say that unless I’m missing something, the change to opt_dff you linked resolves the set_1_dff from your example to a $_DFFE_PP1P_, but set_0_dff is still a $_ALDFFE_PNP_ and causes dfflegalize to raise an error.

Is the reason this currently doesn’t work because dfflegalize is trying to convert it to a DFFSRE but that’s not valid on ice40, which only supports set or reset but not both (i.e. $_DFFE_PP1P_ is allowed but $_DFFSRE_PPPP_ isn’t), but it’s missing the optimization that it only needs the set or reset? That does sound like something that should be opt_dff rather than dfflegalize, so in principle I think opening a PR to add support for that in opt_dff is fine (I wasn’t able to follow the aload_gdff code well enough to figure out if the problem was with the code or the changes to opt_dff that you linked were incomplete, and I didn’t look too closely at the tests included with the changes, so if you did open a PR I would want to double check that it is valid for all cases).

Thanks a lot for your feedback!
I’m quite new to the internals of yosys so I didn’t investigate abc or dfflegalize.

set_0_dff is a flop with AD = !ALOAD. The check for complement needs opt -sat to be active, so synthesis has to be split:

synth_ice40 -run :map_ram  -device lp -top top -json top.json
opt -sat
synth_ice40 -run map_ram:  -device lp -top top -json top.json

Guarding the qcsat… invocation with -sat is a pattern which I took over from run_constbits(). Not sure if it’s really required here as well.

I’ll run a PR, thanks again.

Let opt_dff combine flops with arbitrary forms of equivalent logic · Issue #5551 · YosysHQ/yosys · GitHub may also be related. @widlarizer do you have any thoughts/input on this?

My notes so far: SATing somewhat more should help in general here. The issue I put up for arbitrary equivalent logic is specifically about “opt_dff does an optimization, but not when equivalent comb logic is present”. Here we have “opt_dff doesn’t do an optimization for cases where sig_aload==sig_ad” so step 1 may be the non-SAT implementation. As for the current -sat flag, it should at least be a scratchpad variable so that it’s easy to turn it on globally (it’s not like synth* commands expose it as a flag of their own already)