0

I recently started playing with the iE40 icestick evaluation board. I encountered what I think is strange behavior:

It seems that Yosys wants to optimizes away a module which takes a port connected to a RX gpio pin. IMO this should never happen as the state of this pin should never be considered constant. I have recreated this in a minimal verilog program below:

The following code, synthesized with: yosys -p 'synth_ice40 -noflatten -noabc -top top -json out/top.json' generates the following logic. Removing the (* keep *) above the rxi module instead generates following logic. Note that the rxi module is now gone, which I don't think is right. Is this expected behavior?

module top                                                                       
(                                                                                
    input CLK,                                                                   
    input RX,                                                                    
    output TX,                                                                   
);                                                                               
    reg[7:0] rx_buf;                                                             
    reg[7:0] tx_buf;                                                             
                                                                                 
    (* keep *) // <-- Prevents `rxi` from being optimized away.                  
    rx rxi                                                                       
    (                                                                            
        .clk(CLK),                                                               
        .rx(RX),                                                                 
        .rx_buf(rx_buf)                                                          
    );                                                                           
                                                                                 
    tx txi                                                                       
    (                                                                            
        .clk(CLK),                                                               
        .tx_buf(tx_buf),                                                         
        .tx(TX)                                                                  
    );                                                                           
                                                                                 
    always @(posedge clk) begin                                                  
        tx_buf <= rx_buf;                                                        
    end                                                                          
endmodule                                                                        
                                                                                 
module tx                                                                        
(                                                                                
    input wire clk,                                                              
    input wire[7:0] tx_buf,                                                      
    output wire tx                                                               
);                                                                               
    always @(posedge clk) begin                                                  
        tx <= tx_buf[0];                                                         
        tx_buf <= {tx_buf[0], tx_buf[7:1]};                                      
    end                                                                          
endmodule                                                                        
                                                                                 
module rx                                                                        
(                                                                                
    input wire clk,                                                              
    input wire rx,                                                               
    output reg[7:0] rx_buf                                                       
);                                                                               
    always @(posedge clk) begin                                                  
        rx_buf <= {rx, rx_buf[7:1]};                                             
    end                                                                          
endmodule 

My pin definitions are:

set_io --warn-no-port RX 9                                                           
set_io --warn-no-port TX 8                                                                                                                         
set_io CLK 21                                                                        

The (* keep *) gives me the expected logic, but I don't think that the module could ever be correctly optimized away?

1 Answers1

0

It got optimized away because there exists a top.CLK signal, but NOT a top.clk signal. The lowercase is okay in the modules, because .clk exists in their ports and are explicitly connected to top.CLK, but there is an always block in top that references posedge clk which should be posedge CLK instead.

In general, yosys is pretty proactive about trimming away unncessary logic, to the point that it can be extremely irritating that it isn't more explicit about telling you exactly why.

At most, you're likely to get a warning message, but not an error. I'm sure it'll improve in time, especially if affected people become sufficiently irritated to contribute fixes upstream, which seems a fairly safe bet.

For the time being, keep a very close look out for warnings, and keep an eye on logic and resource usage: Suddenly fitting a design with 99% unused resources is a dead giveaway that some unexpected early optimization occurred.

In this case and for ice40 in general, you probably want to run icepll -m or so to get a template for the SB_PLL40_CORE block you likely do want between CLK (the pin) and clk (the global clock). Even if you want the same frequency, the PLL will give a cleaner signal, so it's good practise. (Hint: you generally want to use the locked output to control your design reset, because the PLL will hold that signal low at boot until it is stable. Other hint: Active-low switch inputs using pullup option on pins tend to still be busy rising after config, so be sure to allow some time after locked goes true to ignore your buttons whilst they float up, else it will appear like there's a glitch on startup where all buttons appear initially 'pressed'. Also, don't drive Mosfet gates directly from an IO pin: Use a gate-driver IC, and use the FPGA DONE pin to drive chip enable so it's disabled until config is finished. Alternatively, drive optocoupler inputs using a pair of IO pins: one for the signal, and the other for a 'ground' that your design just sets to 1'b0. That will ensure the opto cannot be on accidentally.).

You'll likely find you can increase your system clock after checking what the max clock frequency is for the design using icetime, and it's trivial to pick up substantial performance that way.

40 to 60 MHz is ordinary for most FPGA logic. Default compilation without really trying to optimize much will typically get you that far at least - it's normally only above that range that optimization becomes critical, but many applications don't need the performance anyway.

The key motivator for always using the PLL for at least the global system clock is that it yields a more reliable system, due to having less phase noise I.E. more stable timing and therefore less likelyhood of a glitch. The input clock can be far worse quality without affecting the quality of the output.

RGD2
  • 443
  • 3
  • 8