FPGA output not consistent with ModelSim - Page 1
Forum section: electronics / general

author: kpow8050
author_rank: Regular Contributor
timestamp: March 20, 2022, 12:19:50 pm
content: I have written a state machine in Verilog which is designed to sync to a pulse train (generated by a photodiode from a scanning laser) and then output a series of pulses. The number of pulses it outputs after each sync trigger is determined by an internal register. The design works perfectly as expected when I simulate it using ModelSim. The first screen shot below shows 5 output pulses ("pixel_clk_out") after the sync pulse ("PLL_latch_ctrl_out") which is expected as the internal register ("scan_line_clk_reset_limit") is set to 5 or "101" in binary. Upon running the same design on real hardware (Cyclone II FPGA) with a 25 MHz clock source the output is completely different (see 2nd screen shot from oscilloscope). There are far greater than 5 pulses output, yet I have confirmed that the register is holding the correct value. I have noticed other strange unpredictable behavior on the FPGA depending on whether I add additional outputs for debugging it changes the behavior despite not modifying the underlying functions of the code. I am at a loss to what is going on. Would anyone know what could potentially cause such a discrepancy between model and real implementation? I can provide code if necessary but chose to omit it for now due to the length. ThanksModelsim output:FPGA output (same settings as Modelsim):

author: BrianHG
author_rank: Super Contributor
timestamp: March 20, 2022, 12:35:23 pm
content: There are 2 things you should note about using Modelsim with Quartus with regards to Cyclone II & III.You can do a RTL simulation, or a gate level simulation, with or without IO timing accuracy.The RTL will illustrate your HDL code's true net names in the waveform view as well as your ability to add traces of hidden nets / regs.For the gate level sims, the nodes you see in Modelsim aren't always the same names as what you have in your HDL since you are observing internal signals inside the FPGA as well as a bunch of your code's signals may be inverted or missing all together. For these gate level sims, only the IO pins will show true while if you went with the RTL sims, all your HDL nets will show true, but the IOs will appear to have 0ps delay.This may be your issue.One thing I always recommend is to learn how to use and code directly within Modelsim without Quartus writing a test-bench which sims your connected hardware. Then bring your code into Quartus to fit into a selected FPGA. Working in Modelsim has the vast advantage of under a second re-compile time each time you make a change to your code allowing you to flesh out everything in advance.

author: hamster_nz
author_rank: Super Contributor
timestamp: March 20, 2022, 06:30:17 pm
content: Ĺook very closely at your synthesis and implementation warning. Also check initial values for registers.Are you able to post code?

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 20, 2022, 06:34:58 pm
content: Sharing code would help.One general thought from your description. Am I right understanding your design uses its own clock, and then synchronizes to an external signal that is asynchronous to your "main" clock? If so and form your symptoms, it's likely you have a clock domain crossing issue.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 01:10:28 am
content: Quote from: SiliconWizard on March 20, 2022, 06:34:58 pmThat is correct. It is using a 25 MHz oscillator (external to the FPGA). The asynchronous source is 2 orders of magnitude lower frequency however (on the order of 25k Hz to 250 KHz) depending on the scan rate of the laser. I previously had the same design in block form, which worked fine and I didn't have any clock domain crossing issues. These issues started after I converted my block design into Verilog. Is it possible that Verilog is synthesizing the design differently to how it would be when block format?

author: hamster_nz
author_rank: Super Contributor
timestamp: March 21, 2022, 01:31:06 am
content: EDIT: Just seen your code... looking at it now.Common causes of such things:- You don't have any correct clock constraints - usually a problem in higher speed clock domains. Every clock should have a constraint.- You don't have flip-flips buffering async inputs. (gives about 1 in 100 or 1 in 1000 errors, depending on design speed)- You don't have synchronizers on async inputs (gives about 1 in a 1,000,000 or higher error rates in what is otherwise a trustworthy design).- Your reset strategy is not good. The design is either not properly resetting, or does flush out reset issues by design.- You do not register your design's outputs.- You have wrong sensitivity lists on async processes (this is a big pain).- You have unintended latches.- You have unintended gated clocks that are causing an issue.- You have badly constructed clock domains. Were possible, their should be only one clock signal using in your design (so a common signal for all rising_edge()/falling_edge() for VHDL or @posedge/@negedge for Verilo).- Where you must have clock domain crossing, they are not correctly handled.Really we can't say what is your problem until we see code.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 01:35:27 am
content: Quote from: hamster_nz on March 20, 2022, 06:30:17 pmI have initialized all registers to 0 I also have an initial reset which occurs on the first clock cycle to reset all latches, flip flops, memory etc. I will post my code below (sorry for the length)Code:module LaserControl (input wire[7:0] scanline_clk_reset_limit_debug,input data_in,input comms_clk,input execute,input clk_in,input in_a,input in_b,output wire ld_out,output reg [7:0] debug_reg,output reg debug_output);// Control constantslocalparam laser_ctrl_mode = 7;localparam set_pixel_offset_mode = 1;localparam set_pixel_clock_divider_mode = 5;localparam set_pixel_scan_line_limit_mode = 6;// Memory constantslocalparam number_length = 8;// Input data shift registerlocalparam IN_REG_BITS = 16;wire in_reg_clk;wire [IN_REG_BITS-1:0] in_reg_out;// Internal registersreg initial_reset = 0;reg GLOBAL_RESET = 0;// Variable definitionswire [7:0] inst_decode_output;// Instruction decoder outputwire SCHMITT_OUT;wire SCHMITT_OUT_INV;wire PLL_latch_ctrl_out;reg pixel_clk = 0;// Pixel clock sourcewire [number_length-1:0] pixel_clk_divider_reg_out;reg update_pixel_clk_divider_reg = 0;reg pixel_clk_src = 0;wire pixel_clk_out;wire [number_length-1:0] scanline_clk_reset_limit_out;reg update_pixel_scan_line_limit_reg = 0;wire line_clk_reset_out;wire [number_length-1:0] pixel_offset_reg_out;reg update_pixel_offset_reg = 0;wire pixel_offset_delay_trig_out;wire pixel_offset_clk_out;wire pixel_enable_out;reg laser_mux_sel = 0;reg laser_ctrl_reg = 0;reg update_laser_ctrl_code_reg = 0;wire[1:0] laser_ctrl_reg_out;// Instantiate the data input shift register (using SIPO shift reg)sipo_shift_reg #(IN_REG_BITS) in_reg (.clk (in_reg_clk | (GLOBAL_RESET & clk_in)),// Synchronous reset added.s_in (data_in),.resetn (~GLOBAL_RESET),.enable (1),.p_out (in_reg_out));// UPDATE DEBUG REGisteralways @(in_reg_out) begindebug_reg <= in_reg_out[7:0];end//always @(pixel_offset_clk_out) begin//debug_output <= pixel_offset_clk_out;//end// Input data shift register clock synchronization using D_FF (syncs input clock with FPGA clock for the data shift register)// Instantiate the sync D_FFd_ff sync_dff (.clk (clk_in | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (comms_clk | ~GLOBAL_RESET),.d (comms_clk),.q (in_reg_clk));// Output of this D_FF goes to the clock input of the shift register// Instantiate instruction decoderdecoder_3to8 inst_decode (.in (in_reg_out[2:0]),.out (inst_decode_output));// Initialize the PLL PD input SR latch (sync pulse)sr_latch pd_input (.s (in_a & in_b),.r (~(in_b | in_a)),.q (SCHMITT_OUT),.q_c (SCHMITT_OUT_INV));// Initialize the D flip flop for PLL latch resetd_ff PLL_latch_reset (.clk (SCHMITT_OUT_INV | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (~(line_clk_reset_out | GLOBAL_RESET)),.d (1),.q (PLL_latch_ctrl_out));// Pixel clock sourcealways @(clk_in) begin// On the first clock pulse, reset all registersif(initial_reset == 0) beginGLOBAL_RESET <= 1;initial_reset <= 1;end else beginGLOBAL_RESET <= 0;endpixel_clk <= PLL_latch_ctrl_out & clk_in;end// Pixel clock divider// This sets the pixel clock rate (how many pixels/per sec)gated_reg #(number_length) pixel_clk_divider_reg(.clk (update_pixel_clk_divider_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (pixel_clk_divider_reg_out));clock_divider #(number_length) pixel_clk_divider (.divider (pixel_clk_divider_reg_out),.clk (pixel_clk_src | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (GLOBAL_RESET),.clk_out (pixel_clk_out));// Scan line reset pulse// This determines how many pixels within 1 scan line before the scan line is resetgated_reg #(number_length) scanline_limit_reg (.clk (update_pixel_scan_line_limit_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (scanline_clk_reset_limit_out));clock_divider #(number_length) line_clk_divider (.divider (scanline_clk_reset_limit_debug),// CHANGE BACK TO scanline_clk_reset_limit_out (DEBUGGING).clk (pixel_clk_out | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (line_clk_reset_out | GLOBAL_RESET),.clk_out (line_clk_reset_out));// Scan line pixel offset register// This determines how many pixels to offset a scan line from start of scangated_reg #(number_length) pixel_offset_reg(.clk (update_pixel_offset_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (pixel_offset_reg_out));clock_divider #(number_length) pixel_offset_clk_divider (.divider (pixel_offset_reg_out),.clk (pixel_clk | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (pixel_offset_clk_out | GLOBAL_RESET),.clk_out (pixel_offset_clk_out));// Pixel clock enable FF from pixel offset counterd_ff pixel_enable_ff (.clk (pixel_offset_clk_out | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (~(line_clk_reset_out | GLOBAL_RESET)),.d (1),.q (pixel_enable_out));always @(pixel_clk or pixel_enable_out) beginpixel_clk_src <= pixel_clk & pixel_enable_out;end// Laser clock out selector MUXmux21 laser_clk_out_mux (.sel (laser_mux_sel),.s1 (laser_ctrl_reg),.s2 (pixel_clk_out),.q (ld_out));// Laser control code registergated_reg #(2) laser_ctrl_code (.clk (update_laser_ctrl_code_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-2]),.reset (GLOBAL_RESET),.out_reg (laser_ctrl_reg_out));always @(laser_ctrl_reg_out) beginlaser_ctrl_reg <= laser_ctrl_reg_out & laser_ctrl_reg_out;laser_mux_sel <= laser_ctrl_reg_out & ~laser_ctrl_reg_out;end// Execution controlalways @(execute or inst_decode_output) beginupdate_laser_ctrl_code_reg <= inst_decode_output & execute;update_pixel_offset_reg <= inst_decode_output & execute;update_pixel_clk_divider_reg <= inst_decode_output & execute;update_pixel_scan_line_limit_reg <= inst_decode_output & execute;endendmodule

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 01:46:40 am
content: Just to add to my previous post regarding the code. There are a few instances where I ignore the edge of the clockCode:always @(clk_in) beginfor example, where I need it to trigger on both edges to perform the logic. I am using asynchronous resets on the clock divider module (it has an internal register to keep track of how many incoming clock pulses). Without the async reset my design was not working, as by the time the next clock edge arrived the output driving reset had changed already. Again the same design worked fine in block design mode (not sure the proper name) but where you lay out the design using gates similar to circuit design. These problems started once I moved to Verilog. Thanks for the help so far @hamster_nz

author: BrianHG
author_rank: Super Contributor
timestamp: March 21, 2022, 02:02:13 am
content: Quote from: kpow8050 on March 21, 2022, 01:46:40 amI believe that makes your logic operate as latch enables, enabling all the logic while 'clk_in' is high, and freezing the logic state while 'clk_in' is low. Some logic like counters will mess up here in hardware, but may simulate differently. If you want clocking on both edges, you need @(posedge clk_in or negedge clk_in).In any case, it is far superior to just use the FPGA PLL to double your source clock frequency and make everything work off of a single (posedge pll_clk_out). So, if in the future if you need further refinement, you may just set your PLL to 4x, or 8x, or 10x...Remember, for a simple pulse and timing generator, properly written, you can most likely get your code running at 500MHz on a Cyclone II. And if synthesizing / aligning a lock with 2ns clock alignment isn't good enough for a 250KHz signal, well... I do have a trick which can refine that jitter period below 1ns with the 500MHz clock.

author: hamster_nz
author_rank: Super Contributor
timestamp: March 21, 2022, 02:07:07 am
content: Quote from: kpow8050 on March 21, 2022, 01:46:40 amThese look odd to me, and definitely looks like a gated clock:Code:d_ff pixel_enable_ff (.clk (pixel_offset_clk_out | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (~(line_clk_reset_out | GLOBAL_RESET)),...And this sensitivity list looks wrong:Code:always @(execute or inst_decode_output) beginupdate_laser_ctrl_code_reg <= inst_decode_output & execute;update_pixel_offset_reg <= inst_decode_output & execute;update_pixel_clk_divider_reg <= inst_decode_output & execute;update_pixel_scan_line_limit_reg <= inst_decode_output & execute;endIn general you can't do things on both edges of a clock (well, a FF can only latch on the rising or falling edge, not both.Still trying to understand the code... I'll let you know if I see anything.

author: BrianHG
author_rank: Super Contributor
timestamp: March 21, 2022, 02:11:20 am
content: Quote from: hamster_nz on March 21, 2022, 02:07:07 amYup. Hard to understand his true requirements. Not compatible with my 100% synchronous coding style.

author: hamster_nz
author_rank: Super Contributor
timestamp: March 21, 2022, 03:01:56 am
content: I'm pretty sure this is the bit that doesn't work. It doesn't make it though in-head Verilog to VHDL translator.It is most likely inferring latches or something.Code:// Pixel clock sourcealways @(clk_in) begin// On the first clock pulse, reset all registersif(initial_reset == 0) beginGLOBAL_RESET <= 1;initial_reset <= 1;end else beginGLOBAL_RESET <= 0;endpixel_clk <= PLL_latch_ctrl_out & clk_in;endThe first half should be clocked on an edge. The second half seems weird.Code:pixel_clk <= PLL_latch_ctrl_out & clk_in;It show most likely be a DDR register, with the the signal inputs of "PLL_latch_ctrl_out" and "0", clocked by clk_in.That would generate a sequence ofPLL_latch_ctrl_out , 0 , PLL_latch_ctrl_out, 0, PLL_latch_ctrl_out, 0...With the period that matches clk_in.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 21, 2022, 03:23:06 am
content: Quote from: kpow8050 on March 21, 2022, 01:10:28 amI don't know what "block format" is, so I can't answer this.As to clock domain crossing, the "frequency" of the signals do not matter. What essentially matters is that you can get metastability issues if the external signal is not resynchronized properly, it's not a matter of frequency, but basically when edges appears relative to one another.Those issues often lead to the exact symptoms you're seing, and the probability of metastability happening highly depends on how the code is synthesized and then placed and routed, which can be influenced by even very slight changes in your code.Haven't looked at your code yet, but I highly suspect you haven't resynchronized the external signal properly.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 03:26:20 am
content: Quote from: hamster_nz on March 21, 2022, 03:01:56 amWhen I usedCode:always @(clk_in)I was under the assumption it would trigger whenever clk_in changed state. Am I mistaken here? If it is only trigger when clk_in == HIGH then it will cause problems for my code. I will change it toCode:always @(posedge clk_in or negedge clk_in)and see if that solves my problem. There are probably many aspects of my code that aren't ideal. I would like to learn how to do this properly as I am still a beginner when it comes to Verilog.Also the purpose of that function (Code:pixel_clk <= PLL_latch_ctrl_out & clk_in;is to simply gate the clk_in with the PLL_latch_ctrl_out signal

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 03:28:24 am
content: Quote from: SiliconWizard on March 21, 2022, 03:23:06 amI see. If that is the case, I may have to do a big overhaul of my code. I am currently using a D flip flop to re-sync the external clock with the internal clock, but I am not sure if this is how you would do it, or if it even has the desired effect you mention.

author: BrianHG
author_rank: Super Contributor
timestamp: March 21, 2022, 03:46:49 am
content: Quote from: kpow8050 on March 21, 2022, 03:28:24 amQuestions:Why not use the external clock as your clock?Do you need to generate output faster than that external clock?Is that external clock too slow to PLL off of?Or are you trying to sync to a really slow asynchronous event?What is currently generating your 25MHz clock?I have a sneaky suspicion your code is completely all over the place for the wrong reasons.If we take a few steps back and works out what you are trying to achieve, I'm sure you can really eaily make a clean legible piece of synchronous verilog code.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 04:09:43 am
content: Quote from: BrianHG on March 21, 2022, 03:46:49 amHi Brian, I'll do my best to explain the purpose of my code and answer your questions below1. Why not use the external clock as your clock?The external clock has a small duty cycle. It isn't really a clock, it is a pulse. The duty cycle would be close to 0.01%. The pulse actually comes from a laser which is swept with a polygon mirror. Once the beam gets to the end of the sweep it hits a photodiode which gives out a pulse. I am using this pulse to sync the main clock which is essentially to output pulses to the laser so it can form a dot pattern. I've been able to get this working perfectly in my previous block design, but after switching to Verilog it behaves sporadically.Do you need to generate output faster than that external clock?Yes based on above the sync pulse arrives at the end of a scan line (so much slower than the time scale I want to modulate the output). Im looking to put at least 2000 pulses out for every 1 pulse receivedIs that external clock too slow to PLL off of?Because of its odd duty cycle I would say it may give problemsWhat is currently generating your 25MHz clock?Its a packaged crystal oscillator, I've soldered it to another board external to the FPGA, a small wire feeds that clk to the FPGA. Never had issues with that so far that I know of.Short description of what I'm trying to doEssentially I want to output a series of pulses to a laser to form a dotted line when its swept using a polygon mirror. These pulses need to be synced to the sweep of the laser, so a photodiode is used to detect the end of a scan line and fed to the FPGA for syncing. So far this I can get working perfectly. It only started having issues once I tried to add fancy features such as adjusting the pixel offset delay, number of pixels and the pixel clock (switched to Verilog to do this)At the end of the day I will be reading from a memory source to output an arbitrary pattern of about 2000 pixels/pulses. To do that I plan to replace the clock source with a 2000 bit register and feed the clock source to that with an address counter and serial output.When I first designed this I did it from the point of view as if I were designing it to work in 74 series logic gates. However as the design evolved I found it were easier to code it in Verilog on an FPGA

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 04:14:12 am
content: FYI. In case anyone is wondering what I mean by the previous design I refer to as block design I have attached a screen shot of it below

author: hamster_nz
author_rank: Super Contributor
timestamp: March 21, 2022, 08:39:00 am
content: My Verilog skills are very poor and this is non-idiomatic Verilog, but should show how you can make a fully synchronous design for your data interfaces, in a behavioral way, not structural. It really does help to make your designs maintainable and extendable without lots of design work.It's also got a very simplistic synchronizers for the control signals. Hopefully it helps rather than distracts.Code:`timescale 1ns / 1psmodule design1(input clk,// Shift register interfaceinput s_data,input s_clock,input s_execute,input reset,// Just a generic ouput signaloutput reg ctrl);reg synced_s_data_last = 1'b0, synced_s_data = 1'b0, unsafe_s_data = 1'b0;reg synced_s_clock_last = 1'b0, synced_s_clock = 1'b0, unsafe_s_clock = 1'b0;reg synced_s_execute_last = 1'b0, synced_s_execute = 1'b0, unsafe_s_execute = 1'b0;reg synced_reset = 1'b0, unsafe_reset = 1'b0;reg [15:0] shift_reg;reg[7:0] my_reg_5;reg[7:0] my_reg_6;reg[7:0] my_reg_7;always @(posedge clk) begin// Synchonize everyting to the fast system clock// Note this is a very nieve implementation of a synchronizer, but should work.synced_s_execute_last <= synced_s_execute;synced_s_execute <= unsafe_s_execute;unsafe_s_execute <= s_execute;synced_s_clock_last <= synced_s_clock;synced_s_clock <= unsafe_s_clock;unsafe_s_clock <= s_clock;synced_s_data_last <= synced_s_data;synced_s_data <= unsafe_s_data;unsafe_s_data <= s_data;// A super-poor reset synchronizersynced_reset <= unsafe_reset;unsafe_reset <= reset;end// Move bits into the sift register and act on themalways @(posedge clk) beginif(synced_reset) begin// Set safe power on variablesmy_reg_5 <= 8'b10101010;my_reg_6 <= 8'b00000000;my_reg_7 <= 8'b00000000;shift_reg <= 16'b0000000000000000;ctrl <= 1'b0;end else begin// If we have seen the rising edge on the execute signalif(synced_s_execute_last == 1'b0 && synced_s_execute == 1'b1) begin// Execute the command in bits 2:0case(shift_reg[2:0])3'b001: ctrl <= 1'b0; // Do something on command 13'b010: ctrl <= 1'b1; // Do something on command 23'b101: my_reg_5 <= shift_reg[15:8]; // Update register values3'b110: my_reg_6 <= shift_reg[15:8];3'b111: my_reg_7 <= shift_reg[15:8];endcaseend// If we have seen the rising edge on the shift register clock signalif(synced_s_clock_last == 1'b0 && synced_s_clock == 1'b1) begin// Shift in a new bitshift_reg <= { shift_reg[14:0], synced_s_data};endendendendmodule

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 09:05:49 am
content: Thanks for providing an alternative design. It should hopefully help me, but I will need to spend some time to understand how this works before I can implement it. What is the main difference between behavioral and structural? I'm assuming my current design was structural? Also I forgot to mention I'm actually dealing with 3 CLK signals! 1 CLK source comes from an external microcontroller which clocks the shift register interface, the other CLK source is the main (25 MHz) and a third CLK source which is an async pulse also external

author: hamster_nz
author_rank: Super Contributor
timestamp: March 21, 2022, 09:37:49 am
content: Shtuchtual is 'connect these bits together like this'Behavioral is "I want the design to act this ways'Most modules are a bit of both. Some things wired together, and some logic/code describing how things should workThe easy way to resolve the issue of having so many clocks it to sample the slower clock signals using a much faster system clock. You can then use the current and prior samples to work out where the edges were.For example, you might find your photodiode has a rising edge every 25,000 system clocks, and use that information to time your laser diode pulses.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 10:05:26 am
content: I see. My first design was more like connecting bits together, which was intuitive for me as I look at an FPGA as like a CPLD

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 10:12:34 am
content: I have modified my design somewhat. Firstly I removed all the intermediate registers and therefore was able to remove all of the always# statements except the one for the main clock. So essentially all registers are replaced with wires. I have used the assign statement for the replacements, where combinational logic was actually required. The new code actually has somewhat fixed my issues. I now get a stream of pulses somewhat resembling what I expect, although there is still some unpredictable behavior. For instance the clock divider only seems to work in multiples of 1, 2, 4, 8, 16, 32 etc. Anything between it gives funny outputs. The code is shown belowCode:module LaserControl (//input wire[7:0] scanline_clk_reset_limit_debug,input data_in,input comms_clk,input execute,input clk_in,input in_a,input in_b,output wire ld_out,output wire [7:0] debug_reg,output reg debug_output);// Control constantslocalparam laser_ctrl_mode = 7;localparam set_pixel_offset_mode = 1;localparam set_pixel_clock_divider_mode = 5;localparam set_pixel_scan_line_limit_mode = 6;// Memory constantslocalparam number_length = 8;// Input data shift registerlocalparam IN_REG_BITS = 16;wire in_reg_clk;wire [IN_REG_BITS-1:0] in_reg_out;// Internal registersreg initial_reset = 0;reg GLOBAL_RESET = 0;// Variable definitionswire [7:0] inst_decode_output;// Instruction decoder outputwire SCHMITT_OUT;wire SCHMITT_OUT_INV;wire PLL_latch_ctrl_out;wire pixel_clk;// Pixel clock sourcewire [number_length-1:0] pixel_clk_divider_reg_out;wire update_pixel_clk_divider_reg;wire pixel_clk_src;wire pixel_clk_out;wire [number_length-1:0] scanline_clk_reset_limit_out;wire update_pixel_scan_line_limit_reg;wire line_clk_reset_out;wire [number_length-1:0] pixel_offset_reg_out;wire update_pixel_offset_reg;wire pixel_offset_delay_trig_out;wire pixel_offset_clk_out;wire pixel_enable_out;wire laser_mux_sel;wire laser_ctrl_reg;wire update_laser_ctrl_code_reg;wire[1:0] laser_ctrl_reg_out;// Instantiate the data input shift register (using SIPO shift reg)sipo_shift_reg #(IN_REG_BITS) in_reg (.clk (in_reg_clk | (GLOBAL_RESET & clk_in)),// Synchronous reset added.s_in (data_in),.resetn (~GLOBAL_RESET),.enable (1),.p_out (in_reg_out));// Input data shift register clock synchronization using D_FF (syncs input clock with FPGA clock for the data shift register)// Instantiate the sync D_FFd_ff sync_dff (.clk (clk_in | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (comms_clk | ~GLOBAL_RESET),.d (comms_clk),.q (in_reg_clk));// Output of this D_FF goes to the clock input of the shift register// Instantiate instruction decoderdecoder_3to8 inst_decode (.in (in_reg_out[2:0]),.out (inst_decode_output));// Initialize the PLL PD input SR latch (sync pulse)sr_latch pd_input (.s (in_a & in_b),.r (~(in_b | in_a)),.q (SCHMITT_OUT),.q_c (SCHMITT_OUT_INV));// Initialize the D flip flop for PLL latch resetd_ff PLL_latch_reset (.clk (SCHMITT_OUT_INV | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (~(line_clk_reset_out | GLOBAL_RESET)),.d (1),.q (PLL_latch_ctrl_out));// Pixel clock sourcealways @(posedge clk_in) begin// On the first clock pulse, reset all registersif(initial_reset == 0) beginGLOBAL_RESET <= 1;initial_reset <= 1;end else beginGLOBAL_RESET <= 0;endendassign debug_reg = in_reg_out[7:0];assign pixel_clk = PLL_latch_ctrl_out & clk_in;// Pixel clock divider// This sets the pixel clock rate (how many pixels/per sec)gated_reg #(number_length) pixel_clk_divider_reg(.clk (update_pixel_clk_divider_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (pixel_clk_divider_reg_out));clock_divider #(number_length) pixel_clk_divider (.divider (pixel_clk_divider_reg_out),.clk (pixel_clk_src | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (GLOBAL_RESET),.clk_out (pixel_clk_out));// Scan line reset pulse// This determines how many pixels within 1 scan line before the scan line is resetgated_reg #(number_length) scanline_limit_reg (.clk (update_pixel_scan_line_limit_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (scanline_clk_reset_limit_out));clock_divider #(number_length) line_clk_divider (.divider (scanline_clk_reset_limit_out),// CHANGE BACK TO scanline_clk_reset_limit_out (DEBUGGING).clk (pixel_clk_out | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (line_clk_reset_out | GLOBAL_RESET),.clk_out (line_clk_reset_out));// Scan line pixel offset register// This determines how many pixels to offset a scan line from start of scangated_reg #(number_length) pixel_offset_reg(.clk (update_pixel_offset_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-number_length]),.reset (GLOBAL_RESET),.out_reg (pixel_offset_reg_out));clock_divider #(number_length) pixel_offset_clk_divider (.divider (pixel_offset_reg_out),.clk (pixel_clk | (GLOBAL_RESET & clk_in)),// Synchronous reset added.reset (pixel_offset_clk_out | GLOBAL_RESET),.clk_out (pixel_offset_clk_out));// Pixel clock enable FF from pixel offset counterd_ff pixel_enable_ff (.clk (pixel_offset_clk_out | (GLOBAL_RESET & clk_in)),// Synchronous reset added.resetn (~(line_clk_reset_out | GLOBAL_RESET)),.d (1),.q (pixel_enable_out));//always @(pixel_clk or pixel_enable_out) beginassign pixel_clk_src = pixel_clk & pixel_enable_out;//end// Laser clock out selector MUXmux21 laser_clk_out_mux (.sel (laser_mux_sel),.s1 (laser_ctrl_reg),.s2 (pixel_clk_out),.q (ld_out));// Laser control code registergated_reg #(2) laser_ctrl_code (.clk (update_laser_ctrl_code_reg | (GLOBAL_RESET & clk_in)),// Synchronous reset added.data (in_reg_out[IN_REG_BITS-1:IN_REG_BITS-2]),.reset (GLOBAL_RESET),.out_reg (laser_ctrl_reg_out));//always @(laser_ctrl_reg_out) beginassign laser_ctrl_reg = laser_ctrl_reg_out & laser_ctrl_reg_out;assign laser_mux_sel = laser_ctrl_reg_out & ~laser_ctrl_reg_out;//end// Execution control//always @(execute or inst_decode_output) beginassign update_laser_ctrl_code_reg = inst_decode_output & execute;assign update_pixel_offset_reg = inst_decode_output & execute;assign update_pixel_clk_divider_reg = inst_decode_output & execute;assign update_pixel_scan_line_limit_reg = inst_decode_output & execute;//endendmoduleI have attached photos of the laser output (showing this code now works ~ sort of, not perfect yet but better than before after removing the registers)CLK divider = 1CLK divider = 2CLK divider = 4As you can see the laser dots compress with a larger clock divider (pulses come out slower for the same unit time/angular velocity of the rotation for the mirror) so that part makes sense. However I can't choose a CLK divider between 2 and 4 or 4 and 8, the output goes haywire, which is not consistent with my modelsim results still

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 10:17:21 am
content: Also I am not sure if this is related to my FPGA output not being consistent with ModelSim, but the compiler states that timing requirements aren't met. The details are listed belowCritical Warning (332148): Timing requirements not metInfo (332146): Worst-case setup slack is -1.068Info (332119): Slack End Point TNS ClockInfo (332119): ========= ============= =====================Info (332119): -1.068 -11.490 clk_inInfo (332146): Worst-case hold slack is -3.219Info (332119): Slack End Point TNS ClockInfo (332119): ========= ============= =====================Info (332119): -3.219 -9.698 clk_inInfo (332146): Worst-case recovery slack is -0.773Info (332119): Slack End Point TNS ClockInfo (332119): ========= ============= =====================Info (332119): -0.773 -2.024 clk_inInfo (332146): Worst-case removal slack is 0.000Info (332119): Slack End Point TNS ClockInfo (332119): ========= ============= =====================Info (332119): 0.000 0.000 clk_in

author: BrianHG
author_rank: Super Contributor
timestamp: March 21, 2022, 07:44:25 pm
content: Ok, I have an idea of what you want to do. If you will be adding functions, your task will grow exponentially difficult with your current coding attempts to rewrite 74LS logic gates into Verilog HDL. I have some time today, Wednesday and Thursday and I'm willing to help you properly learn/rewrite HDL your design properly using the abilities in Verilog so you will not have trouble adding functions.As it stands, adding more features will grow the complexity and ability to work out bugs in your design. What you want to do isn't difficult and with a little guidance, you can have it properly working in a day or 2, even with 2048 bytes of ram which you and address and change in realtime to draw any dotted pattern you like.If so, we will begin with your basic functionality you requested. If you have a current testbench .v code, can you supply it? If we begin, we would work directly in Modelsim alone to generate exactly what you like and see the FPGA perform exactly that way when you finally compile in Quartus.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 21, 2022, 10:34:23 pm
content: Awesome, that would be great @BrianHG if you could help me with this. I really appreciate your offer. Knowing how to properly code in HDL will definitely be an asset for me in the future too. I have attached the current workbench I'm using to test my code. It currently does not test the command interface via the shift register, so instead I directly write to a register through an input. I have also attached the associated module files. All are contained in the .zip file belowCheers,Keith

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 12:44:59 am
content: Ok, here we go.I've done a basic setup for you.I've tested it in an old ModelSim 10.1.There is an annoying bug in 10.1 where if you quick recompile your design more than 50-100 consecutive times, you may need to quit, then rerun Modelsim. The new Modelsims don't have the bug, but since you are using Cyclone II, I assumed you were on an old modelsim.Step 1, unzip the LaserControl_v2 into it's own folder.Step 2, open Modelsim on it's own, no need for Quartus.Step 3, in Modelsim, Select 'File' and 'Change Directory' and navigate to the LaserControl_v2 folder.Step 4, in Modelsim's transcript window, type:'do setup_lc.do'When first opening Modelsim, you need to do these 4 steps to setup the modelsim.Every time you make a change in the HDL code, you then just type in the transcript window:'do run_lc.do'This does a quick re-compile and re-run.Ok, for the code. I did a basic setup for you showing you how to begin.In the 'LaserControl_v2.sv', I:Made a dummy override reset.Then a multi-function case statement which basically keeps the laser off,then waits for 'in_a' to rise,then oscillates the laser at 12.5MHz until 'in_a' falls, then back to waiting.I left 2 blank 'TASK's for setting and running your laser pixel output generator which we will work on creating.Then we will look at loading controls and adding a line of memory.In the 'LaserControl_v2_tb.sv', I:Made the clock an authentic 25MHz.According to your scope-shot, I make the in_a pulse 250us, or 250000ns and the off time 2250000ns.However, this is positive logic and your scopeshot shows negative logic. We would need to fix this if your hardware is negative logic as we want to simulate reality.I also added an autostop 'ENDTIME' to the sim.Look around first then lets take a look at how to proceed.

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 12:52:15 am
content: I've attached the modelsim output from the above code.I guess we should start with, is the 'in_a' reference stimuli correct?Does it match what your scope sees you feeding the FPGA input?Or is it inverted?What does 'in_b' do?Do not worry about the ld_out yet. I just made cheap dummy code there.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 01:14:31 am
content: What does 'in_b' do? This is actually part of the debouncing for the photodiode. in_a and in_b are from a resistor divider network, which is then feeding into a debounce circuit consisting of a flip flop and some combinational logic to clean up the pulse. Without debouncing the pulse is very messy, impossible to trigger off.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 01:20:31 am
content: So essential IN_A and IN_B are part of a schmitt trigger circuit which provides some hysteresis for the photodiode pulse (since its super messy and has a bunch of intermediate transitions). I have attached the topology of the circuit below. I would prefer to keep this block as is, if possible since it works. But if there are better ways to debounce I'm also open to those

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 01:24:23 am
content: Quote from: BrianHG on March 22, 2022, 12:52:15 amAlso based on the timing diagram ld_out should only be active on the low side of the in_a (after the pulse essentially), but I assume this is still being worked on. I will attach a drawing soon showing what I intend the signals to look like if that will help. Also in_a cannot be used directly to trigger off, as it needs to be de-bounced (see my previous post). The signal I'm using to trigger off is a conversion of in_a and in_b after a schmitt trigger, the output is called "SCHMITT_OUT_INV"

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 01:35:51 am
content: Quote from: kpow8050 on March 22, 2022, 01:20:31 amRemember everything coming in boils down to a clocked output, so, we need to create the equivalent to your circuit, but, clocked right at the beginning.( Damn, whats with the inverting....)Will this work (Synchronous version):Code:reg sync_in = 0 ;always @(posedge clk_in) begin // remember, we are always operating inside here:if (in_a && in_b) sync_in <= 1'b1; // Both high, so set.else if !(in_a || in_b) sync_in <= 1'b0; // Both low, so clear, otherwise, hold the last value.endNote that there are other ways to debounce as well, like looking for a clean train of 2-10 sequential solid single values as well as the possibility of combining it with what you have above. However, it looks like you are sampling an analog signal.Unless you show me the true signal, I bet you don't need such a circuit. All we need is a single clean transition event. We do not care that there is noise once switched, we can ignore the trailing junk.

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 01:41:28 am
content: With optics, I usually trigger on the 'strike' edge and ignore the after noise. However, I did not create your hardware.Step 1, fix the stimulation in the test-bench setup to reflect as close to reality as possible.Step 2, make the laser perform and adjustable pattern.Ok, lets see you fix the 'LaserControl_v2_tb.sv' so that 'in_a' and 'in_b' are closer to reality.Remember, being truthful here means the FPGA will do exactly what we see when we run the code in hardware.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 01:46:17 am
content: The synchronous version looks like it should work. I can test it later on the actual hardware, but the logic is the same so I don't see why it wouldn't. I will post the drawing of the signals in a sec

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 01:52:39 am
content: Do you have a drawing of what you want the output to look like?

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 02:00:35 am
content: Quote from: BrianHG on March 22, 2022, 01:52:39 amYes. Just finished it. I have attached it below

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 02:11:17 am
content: Quote from: kpow8050 on March 22, 2022, 02:00:35 amOk, easy as pie.Only from experience with optical triggers, I bet locking onto the rise of the 'trig pulse' and adding a long programmable delay through the pulse until after, then running the laser will have a cleaner function.The only reason for this is that usually when you get to see the pulse, it is the light hitting the photo diode having a cleaner edge instead of the release side where what you see is the photodiode's pull-down resistor.It is up to you.Ok, first fix lines 50-55 in the testbench source file to match what you have drawn with in_a and in_b pulsing with a small delay between them.Next, you will add my code replacing lines 41 and 42 in the source HDL, and making a second delayed reg 'sync_in' to trigger off of the transition edge of your choice.Test, and post your resulting code here.Once ok, we will construct your programmable period pixel timer.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 02:44:12 am
content: QuoteAlright, I will get to it and post once I have finished

author: kpow8050
author_rank: Regular Contributor
timestamp: March 22, 2022, 05:20:58 am
content: So I have updated your code to include the schmitt trigger for in_a and in_b, which will convert that into sync_in. I have also added a counter to keep track of how many pixels are output (since I want this to be adjustable). Its working as I expect, although when the pixel count is set to 20 I'm only getting 10 pixels out. I suspect thats because its counting clk_in pulses rather than actual divided output pulses. I have attached a screenshot of the output waveforms below and the code in a zip folder

author: BrianHG
author_rank: Super Contributor
timestamp: March 22, 2022, 09:00:47 am
content: Ok, so far, so good.Some bugs:line 69:else if (!(reg_in_a || reg_in_a)) sync_in <= 1'b0; // Both...The red reg_in_ashould be a 'b'.Now, the issue with line 88:if ( sync_in && !reg_in_a ) function_pos <= 2 ; // When...Remember, sync_in is filtered, but not reg_in_a. A good idea is to add a line 70:sync_in_dly <= sync_in ;We are making a transition detector here, IE seeing what happened since the previous clock before and after.Now, to help simplify your ' if () ' statements everywhere, at around line 32, let's add these 3 'wires'.Code:wire sync_pos_trigger = sync_in && !sync_in_dly ;wire sync_neg_trigger = !sync_in && sync_in_dly ;wire sync_trigger_on = USE_NEG_EDGE ? sync_neg_trigger : sync_pos_trigger ; // Use a parameter or reg to select the trigger edge.And at the top of your code , add this parameter:parameter bit USE_NEG_EDGE = 1 ;Now back to line 88: (now probably line ~92)if ( sync_trigger_on ) function_pos <= 2 ; // When---------------------------Ok, next part, counters, timers and what the 'TASK' does.The Verilog 'TASK' is akin to a function call, but behaves a bit more like a macro function.We typically use if for when we want to repeatedly issue a bunch of the same commands as the result of different 'if()' commands, or inside multiple 'case' statement positions.Here is the beginning of what I have in mind (go down to line approximately 125):Code:// **************************************************// TASKS - set laser pixel clock and address pointer.// **************************************************task SET_laser_clock(bit [15:0] period,bit [15:0] length);beginpixel_count <= length ;pixel_period <= period ;endendtask// **************************************************// TASKS - run laser pixel timer and address pointer.// **************************************************task RUN_laser_clock(bit [15:0] period);beginif (pixel_count !=0) pixel_count <= pixel_count - 1'b1 ; // Count down until 0else function_pos <= function_pos + 1'b1 ; // Once 0, auto-increment the function_pos for the case statement.endendtask(Note that I have decided to use 16bit counters since you mentioned 2000 pixels and I figured you might want additional room if you expand the system clock to 100MHz or 200MHz for ultra precision.)Now, in case function_pos = 1, add:SET_laser_clock ( pixel_period_width_reg, pixel_offset_reg );Now, in case function_pos = 2, remove your counter, in fact remove everything and just place:RUN_laser_clock ( pixel_period_width_reg );As you can see from my RUN_laser_clock task, once the countdown reaches 0, the case function_pos will increment by 1 to become 3.Lets see if you can work out the case function_pos = 3,4,5...In your sim output, it may be useful to add to the waveforms the following:Code:add wave -divider "LaserControl Internals"add wave -unsigned sim:/LaserControl_v2_tb/UUT/function_posAdding these lines to line 15 of the 'setup_lc.do' will automatically place them in your waveform, in the order you list them, every time you begin and do the first setup.I would also add the new nets:sync_trigger_onpixel_countLet's see if you can get the code up to the next stage with the pixel offset and pixel count.Update your code and sim.Next, we will improve the 'RUN_laser_clock' to have a pixel period, or pixel width.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 23, 2022, 09:44:41 am
content: Sorry for the delay. Been busy with work. I have added the functions for pixel offset and pixel count into the task construct as you described. I have also made some small modifications, rather than counting down I have changed it to count up as this is more intuitive for me. The output waveforms are as expected. One thing I couldn't figure out however was to make the laser remain on at all times, other than when it is oscillating. The reason the laser must remain on is for a sync pulse to be generated requires laser light. So without the laser being on initially there will never be a sync pulse and the events will never trigger. I have included the updated code below and attached a screenshot of the waveform outputs showing a pixel offset (delay from sync pulse) and finite number of pixels output.Code:module LaserControl_v2 (input data_in,input comms_clk,input execute,input clk_in,input in_a,input in_b,output reg ld_out,output wire [7:0] debug_reg,output reg debug_output);// Control constants//localparam laser_ctrl_mode = 7;//localparam set_pixel_offset_mode = 1;//localparam set_pixel_clock_divider_mode = 5;//localparam set_pixel_scan_line_limit_mode = 6;parameter bit USE_NEG_EDGE = 1 ;reg reset = 1 ; // since you do not have a reset, we are artificially making preset to on.reg enable_laser = 0 ; // A reg to block the laser from coming on.reg laser_out = 0 ; // A reg to block the laser from coming on.reg [4:0] function_pos = 0 ; // Create a program position pointer which can serve 32 states.reg reg_data_in = 0 ; // Register inputsreg reg_comms_clk = 0 ;reg reg_execute = 0 ;reg reg_in_a = 0 ;reg reg_in_b = 0 ;reg sync_in = 0 ;// Internal registers for keeping track of statereg [15:0] max_pixel_count = 20;reg [15:0] pixel_offset = 100; // how many pixels to delay the output laser pulsesreg [15:0] pixel_offset_count= 0 ;reg [15:0] pixel_count = 0 ;reg [15:0] pixel_period = 1 ; // Period of the pixel clock (1 = period of main clock, 2 = 1/2 of main clock, etc..reg sync_in_dly = 0 ;wire sync_pos_trigger = sync_in && !sync_in_dly ;wire sync_neg_trigger = !sync_in && sync_in_dly ;wire sync_trigger_on = USE_NEG_EDGE ? sync_neg_trigger : sync_pos_trigger ; // Use a parameter or reg to select the trigger edge.// **************************************************// Everything should run from the 25MHz clock.// **************************************************always @(posedge clk_in) begin// Cleanly register FPGA inputs.reg_data_in <= data_in ;reg_comms_clk <= comms_clk ;reg_execute <= execute ;reg_in_a <= in_a ;reg_in_b <= in_b ;// **************************************************// During the reset condition, do these things.// **************************************************if (reset) beginreset <= 0 ; // disable the reset.enable_laser <= 0 ; // Force laser off.ld_out <= 0 ; // Force laser off.function_pos <= 0 ; // Clear the function position so that position 0 runs once the reset is released.end else begin// **************************************************// Once the reset condition has cleared, do these things.// **************************************************ld_out <= enable_laser && laser_out ; // For now, always drive the laser output pin when these are high and !reset.// We will change this to a memory lookup when we create the 2048 pixel line memory// **************************************************// Schmitt trigger// **************************************************if (reg_in_a && reg_in_b) sync_in <= 1'b1; // Both high, so set.else if (!(reg_in_a || reg_in_b)) sync_in <= 1'b0; // Both low, so clear, otherwise, hold the last value.sync_in_dly <= sync_in;// **************************************************// Begin Sequencer driving for the laser output// **************************************************case (function_pos)default : function_pos <= 0 ; // If an undefined position has been reached, goto position 0.0 : begin // This state begins after the reset has been releasedenable_laser <= 1 ; // Force laser on (default should be on, so that photodiode has a signal to sync).function_pos <= 1 ; // Go to position 1.end1 : beginif ( sync_trigger_on ) function_pos <= 2 ; // Goto function 2 when the sync_trigger goes from high to low// Otherwise stay here doing nothing.SET_laser_clock ( pixel_period, max_pixel_count );end2 : beginRUN_laser_clock ( pixel_period );endendcase// **************************************************// End Sequencer for driving the laser output// **************************************************// **************************************************// External commands input registering// **************************************************end // !resetend // always @25 MHz clock.// **************************************************// TASKS - set laser pixel clock and address pointer.// **************************************************task SET_laser_clock(bit [15:0] period,bit [15:0] length);beginmax_pixel_count <= length;pixel_period <= period;endendtask// **************************************************// TASKS - run laser pixel timer and address pointer.// **************************************************task RUN_laser_clock(bit [15:0] period);begin// Wait for pixel offset delayif (pixel_offset_count < pixel_offset) beginpixel_offset_count <= pixel_offset_count + 1'b1;end else beginenable_laser <= 1 ; // Enable the laser output.laser_out <= !laser_out ; // Oscillate the laserif (pixel_count < max_pixel_count) beginpixel_count <= pixel_count + 1'b1 ; // Count up until we reach the max pixel lengthend else beginpixel_count = 1'b0; // Reset the pixel counterpixel_offset_count = 1'b0; // Reset the pixel offset counterfunction_pos <= function_pos + 1'b1 ; // Once pixel count reached max length, auto-increment the function_pos for the case statement.endendendendtaskendmoduleSo for the next step I guess you will help me implement a way to define the pixel period? I decided not to add my own way of doing that as you probably have a more elegant method than what I have in mind. Thanks again for the help so far

author: kpow8050
author_rank: Regular Contributor
timestamp: March 23, 2022, 11:03:00 am
content: I made a small change to the code. I've added code to adjust the pixel period using a counter. The following register was added to keep track of the pixel period countCode:reg [15:0] pixel_period_count= 0 ; // Keeps track of of how many clock pulses are counted for dividing the main clockAnd additional logic was added to the RUN_LASER_CLOCK task as followsCode:task RUN_laser_clock(bit [15:0] period);begin// Wait for pixel offset delayif (pixel_offset_count < pixel_offset) beginpixel_offset_count <= pixel_offset_count + 1'b1;end else beginenable_laser <= 1 ; // Enable the laser output.if (pixel_period_count < pixel_period ) beginpixel_period_count <= pixel_period_count + 1'b1; // increment the pixel period countend else begin// Pixel period reachedpixel_period_count <= 1'b0; // reset the pixel period counterlaser_out <= !laser_out ; // Oscillate the laserif (pixel_count < max_pixel_count) beginpixel_count <= pixel_count + 1'b1 ; // Count up until we reach the max pixel lengthend else beginpixel_count = 1'b0; // Reset the pixel counterpixel_offset_count = 1'b0; // Reset the pixel offset counterfunction_pos <= function_pos + 1'b1 ; // Once pixel count reached max length, auto-increment the function_pos for the case statement.endendendendendtaskThe laser pixel clock period is now adjustable

author: BrianHG
author_rank: Super Contributor
timestamp: March 23, 2022, 02:53:01 pm
content: Quote from: kpow8050 on March 23, 2022, 09:44:41 amOk.I did not say modify the 'task'.You went and messed up my fully 'multi' functional timer.This is what I said:Quote from: BrianHG on March 22, 2022, 09:00:47 amOK, step 1. Roll back the timer 'TASK's to my above attached code.Step 2, paste in this new 'case sequencer function_pos = 0,1,2,3,4,5,6.' and fill in the necessary voids I left.Code:// **************************************************// Begin Sequencer driving for the laser output// **************************************************case (function_pos)default : function_pos <= 0 ; // If an undefined position has been reached, goto position 0.0 : begin // This state begins after the reset has been releasedenable_laser <= 0 ; // Force laser off.function_pos <= 1 ; // Go to position 1.end// **********************************************************// ***** FUNCTION 1 : Wait for first trigger event// ***** Also make sure laser is forced off// **********************************************************1 : begin// hint - we want to turn off the laser// and only once the sync in event happens, we want to increment the 'function_pos'.end// **********************************************************// ***** FUNCTION 2 : Set timer to pixel offset// *****// **********************************************************2 : begin// hint - SET_laser_clock( period_reg, offset_reg );// also, don't forget to increment the function pos.end// **********************************************************// ***** FUNCTION 3 : Run pixel timer until it reaches 0// *****// **********************************************************3 : begin// hint - RUN_laser_clock( period_reg );// warning, I have already built into the 'RUN_laser_clock' task an increment of the 'function_pos'// once the countdown reaches 0.end// **********************************************************// ***** FUNCTION 4 : Set actual number of pixels on a line// *****// **********************************************************4 : begin// hint - SET_laser_clock( period_reg, scanline_reg );end// **********************************************************// ***** FUNCTION 5 : Turn on laser and run pixel timer// ***** until it reaches 0.// **********************************************************5 : begin// hint - RUN_laser_clock( period_reg );// hint - enable_laser <= 1// also, for visualization in the sim, modulate the 'laser_out' reg.end// **********************************************************// ***** FUNCTION 6 : Turn off laser and go back to function 1// *****// **********************************************************6 : begin// hint - enable_laser <= 0// set 'function_pos' to 1.endendcase// **************************************************// End Sequencer for driving the laser output// **************************************************Get this, and only this working, simulating and upload you new code her so we can see how it is going and we will decide to tackle next.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 23, 2022, 08:19:42 pm
content: Forgive me for my ignorance, but I'm a bit confused as to what the sequencer is actually supposed to do in the other 3 steps (4-6)? I was under the impression that most of the code would be written within the task itself.

author: BrianHG
author_rank: Super Contributor
timestamp: March 23, 2022, 08:51:41 pm
content: Quote from: kpow8050 on March 23, 2022, 08:19:42 pmThe 2 tasks I made are nothing more than a timer.1 task sets the beginning of the counter.the other task counts down to 0, and once at 0, it will increment the 'function_pos' register.The case statement, which can also be equivalent to an:Code:if (finction_pos == 0) beginblah, blah, blah.end else if (function_pos == 1) beginblah, blah, blah.end else ...I made comments after each possible (function_pos == 0, 1, 2, 3, 4, 5, 6...)Think about what you want to do.in position #1, I wrote in the comment to turn off the laser & once the sync event comes in, IE: 'sync_trigger_on', increment the function_pos reg.in position 2, I said you should set the universal timer's delay, 'SET_laser_clock' to your 'pixel offset' setting and increment the function_pos.in position 3, I said to just run the timer, ie the 'RUN_laser_clock'. Remember, inside run laser clock, once the countdown reaches 0, it will increment the function_pos for you.in position 4, I said to set the laser clock to your number of pixels you want to display on a line & increment the function_pos.in position 5, I said to turn on the laser, toggle the laser output for effect, and run the 'RUN_laser_clock' timer remembering that inside run laser clock task, once the countdown reaches 0, it will increment the function_pos for you.in position 6, I said to turn off the laser and set reg function_pos <= 1, meaning that the next cycle, we will be back at position 1, waiting for the 'sync_trigger_on' before we go to position 2. This is basically a loop.Now, in your head, follow these instructions ion order and emulate how this home-made instruction step program will run. Remember, inside each position start, all the equations, or flip-flop regs are running always at your master 25MHz clock input.So, when we are in state 1, whatever you type inside that begin ... end, will be operating at 25MHz while the functions or operations inside all the other states are ignored, or frozen.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 23, 2022, 11:53:37 pm
content: QuoteAhh, I get it now. Will update the code and upload it soon

author: kpow8050
author_rank: Regular Contributor
timestamp: March 24, 2022, 02:03:30 am
content: I have implemented the changes to the code. I have attached a .zip file containing the new code below. The output is working as expected. I have modified the laser toggle to ensure that the laser is always on when not toggling. This is the effect I desire

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 09:47:13 am
content: Ok, after adding a few nets to Modelsim's waveform and setting the grid to 40ns matching your system clock, looking at my 2 snapshots, you can get an idea of how easy it is to see what your code is doing when it is doing it and how you may add or adapt your required sequence of events.

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 09:53:56 am
content: Found mistakes, lines :SET_laser_clock (pixel_period, pixel_offset ); // setup the laser clock for a delay (pixel offset delay)RUN_laser_clock (pixel_period); // counting pixel offset delaySET_laser_clock ( pixel_period, max_pixel_count ); // Set the number of pixels to display in the scan line'pixel_period' is currently the actual counter for the period. This means that looking at line 161 in the code:// **************************************************// TASKS - set laser pixel clock and address pointer.// **************************************************task SET_laser_clock(bit [15:0] period,bit [15:0] length);beginpixel_count <= length;pixel_period <= period;pixel_period_timer_reg <= period ;endendtaskAnd later, use the new dedicated timer reg for it's purpose.Quote from: kpow8050 on March 24, 2022, 02:03:30 amCareful here, take a look at line 74 in your code:ld_out <= enable_laser && laser_out ;Make sure this is the true behavior you want. Maybe keep my original enable/disable laser control and change this line to:ld_out <= !(enable_laser && laser_out) ;or:ld_out <= (!enable_laser || laser_out) ;The purpose of that 'enable', at least in my head when I recommended it, was to enable drawing dots from what would eventually be a 2000 pixel line memory. So, you may consider changing line 74 and keeping my original enable/disable logic.

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 10:01:08 am
content: Next, the period.Looking at task 'RUN_laser_clock', see if you can fill in the missing 'xxxxxx' to make the period work for you.Code:// **************************************************// TASKS - run laser pixel timer and address pointer.// **************************************************task RUN_laser_clock(bit [15:0] period);beginif ( xxxxxxxxx != 0 ) xxxxxxxx ;else beginxxxxxxx ;if (pixel_count !=0) pixel_count <= pixel_count - 1'b1 ; // Count down until 0else function_pos <= function_pos + 1'b1 ; // Once 0, auto-increment the function_pos for the case statement.endendendtaskNext, take a look at line 126:laser_out <= !laser_out;Try changing that line so that your laser output matches the lsb of your pixel_count.Once done and simulated, we will take a look at making your external control interface work.

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 11:02:29 am
content: One last thing about lines:96 - function_pos<= 1; // Go to position 1.104 - if ( sync_trigger_on ) function_pos<= 2; // Goto function 2 when the sync_trigger goes from high to low117 - function_pos<= 4; // Goto next caseIn these cases, where you are actually only incrementing the function_pos instead of jumping to a complete random point, it would take less logic and fpga resources to write:function_pos <= function_pos + 1'b1 ;Since the add 1 happens so many times elsewhere or even if you add additional position steps, the compiler will simplify that duplicate logic and determine it is an 'add 1 enable' when needed, while each discrete custom jump you create are be considered additional exclusion cases increasing logic gates & potentially impacting FMAX.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 24, 2022, 11:09:21 am
content: Quote from: BrianHG on March 24, 2022, 10:01:08 amOk, I've got those working. And updated the function_pos to have the more efficient jumping. Code posted below:Code:module LaserControl_v2 (input data_in,input comms_clk,input execute,input clk_in,input in_a,input in_b,output reg ld_out,output wire [7:0] debug_reg,output reg debug_output);// Control constants//localparam laser_ctrl_mode = 7;//localparam set_pixel_offset_mode = 1;//localparam set_pixel_clock_divider_mode = 5;//localparam set_pixel_scan_line_limit_mode = 6;parameter bit USE_NEG_EDGE = 1 ;reg reset = 1 ; // since you do not have a reset, we are artificially making preset to on.reg enable_laser = 0 ; // A reg to block the laser from coming on.reg laser_out = 0 ; // A reg to block the laser from coming on.reg [4:0] function_pos = 0 ; // Create a program position pointer which can serve 32 states.reg reg_data_in = 0 ; // Register inputsreg reg_comms_clk = 0 ;reg reg_execute = 0 ;reg reg_in_a = 0 ;reg reg_in_b = 0 ;reg sync_in = 0 ;// Internal registers for keeping track of statereg [15:0] max_pixel_count = 20;reg [15:0] pixel_offset = 100; // how many pixels to delay the output laser pulsesreg [15:0] pixel_count = 0 ;reg [15:0] pixel_period = 16 ; // Period of the pixel clock (1 = period of main clock, 2 = 1/2 of main clock, etc..reg [15:0] pixel_period_timer_reg = 0;reg sync_in_dly = 0 ;wire sync_pos_trigger = sync_in && !sync_in_dly ;wire sync_neg_trigger = !sync_in && sync_in_dly ;wire sync_trigger_on = USE_NEG_EDGE ? sync_neg_trigger : sync_pos_trigger ; // Use a parameter or reg to select the trigger edge.// **************************************************// Everything should run from the 25MHz clock.// **************************************************always @(posedge clk_in) begin// Cleanly register FPGA inputs.reg_data_in <= data_in ;reg_comms_clk <= comms_clk ;reg_execute <= execute ;reg_in_a <= in_a ;reg_in_b <= in_b ;// **************************************************// During the reset condition, do these things.// **************************************************if (reset) beginreset <= 0 ; // dispixel_period_timer_regable the reset.enable_laser <= 0 ; // Force laser off.ld_out <= 0 ; // Force laser off.function_pos <= 0 ; // Clear the function position so that position 0 runs once the reset is released.end else begin// **************************************************// Once the reset condition has cleared, do these things.// **************************************************ld_out <= !(enable_laser && laser_out) ; // For now, always drive the laser output pin when these are high and !reset.// We will change this to a memory lookup when we create the 2048 pixel line memory// **************************************************// Schmitt trigger// **************************************************if (reg_in_a && reg_in_b) sync_in <= 1'b1; // Both high, so set.else if (!(reg_in_a || reg_in_b)) sync_in <= 1'b0; // Both low, so clear, otherwise, hold the last value.sync_in_dly <= sync_in;// **************************************************// Begin Sequencer driving for the laser output// **************************************************case (function_pos)default : function_pos <= 0 ; // If an undefined position has been reached, goto position 0.0 : begin // This state begins after the reset has been releasedenable_laser <= 0 ; // Force laser offfunction_pos <= function_pos + 1'b1 ; // Go to next function (1).end1 : beginSET_laser_clock ( pixel_period, pixel_offset ); // setup the laser clock for a delay (pixel offset delay)if ( sync_trigger_on ) function_pos <= function_pos + 1'b1 ; // Goto next function when the sync_trigger goes from high to lowend2 : beginRUN_laser_clock ( pixel_period ); // counting pixel offset delayend3 : beginenable_laser <= 1 ; // Force laser onSET_laser_clock ( pixel_period, max_pixel_count ); // Set the number of pixels to display in the scan linefunction_pos <= function_pos + 1'b1; // Goto next functionend4 : beginRUN_laser_clock ( pixel_period ); // counting laser output pixel clocklaser_out <= pixel_count; // Laser is toggled based on the LSB of pixel counterend5 : begin// Turn laser onlaser_out <= 1'b0;function_pos <= 1; // Reset function counterendendcase// **************************************************// End Sequencer for driving the laser output// **************************************************// **************************************************// External commands input registering// **************************************************// External commands input registering// **************************************************end // !resetend // always @25 MHz clock.// **************************************************// TASKS - set laser pixel clock and address pointer.// **************************************************task SET_laser_clock(bit [15:0] period,bit [15:0] length);beginpixel_count <= length;pixel_period_timer_reg <= period;endendtask// **************************************************// TASKS - run laser pixel timer and address pointer.// **************************************************task RUN_laser_clock(bit [15:0] period);beginif ( pixel_period_timer_reg != 0 ) pixel_period_timer_reg <= pixel_period_timer_reg - 1'b1 ; // decrement the pixel period countelse beginpixel_period_timer_reg <= period; // reset the period counterif (pixel_count !=0) pixel_count <= pixel_count - 1'b1 ; // Count down until 0else function_pos <= function_pos + 1'b1 ; // Once 0, auto-increment the function_pos for the case statement.endendendtaskendmoduleAnd here is an image of the output waveform with significantly larger period than the main clock

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 11:20:43 am
content: Ok, nice sim.Note that without adding some choice net from your DUU, like I had in my sim 2 posts back, it would make debugging in the future easier.Decide to test on the FPGA.The, tell me about your control interface.Is it parallel, serial, normal UART, I2c?How fast does it need to be?Will you need to fill your 2000 pixel memory through this interface?

author: kpow8050
author_rank: Regular Contributor
timestamp: March 24, 2022, 11:31:31 am
content: I won't get a chance to simulate on the actual device until after work tomorrow - about to head to bed. Once I do I'll let you know the results hopefully sometime tomorrow.So my control interface is non-standard serial interface. Essentially I just send data to a shift register on the FPGA and its clocked externally from an arduino for each bit I send across. It is formated so that the 3 least significant bits contain the operation code (8 functions possible) to indicate what exactly the FPGA should do with the data stored in the remaining bits of the shift register. The execute signal is then sent from the Arduino to let the FPGA know that the data is shifted into the register and its time for the command to be executed. The list of commands it will support are:Opcode - Command_______________0 - Store pixel array1 - Set pixel offset2 - Increment memory address for pixel array (increments in size of the data width (eg 16 bits etc)3 - Load new scan line (load the contents of the buffer register into the active scan line register - ie the live pixels that are being scanned)4 - NA5 - Set pixel clock divider/period6 - Set pixel scan length7 - Force control of laser state (0 = off, 1 = on, 2 = controlled by FPGA)I don't need to send this data super fast, as long as it can be sent into the FPGA before the next line needs to be scanned (dependent on the photoresist I use). I would be aiming for something like 100k- 500k baud, whatever is error free.Also another thing is that the FPGA needs to be outputting pixels while the arduino sends in the next line without pausing. So I would assume we need 1 pixel buffer (to store the new pixels the arduino is putting in and another pixel register for the active scan line so that no matter how slow the Arduino is, its not going to pause the output of the scan line

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 11:46:53 am
content: Quote from: kpow8050 on March 24, 2022, 11:31:31 amIf the arduino has a uart, this may be advantageous when coding as the old fashioned bit-bang approach can run really slow written in C code. See what the arduino's uart baud rate can run at. Having a normal uart means that you can also use an FTDI USB-> TTL RS232 dongle or IC and have PC connectivity as well. 921600 baud should work on both devices and it is above your speed requirements. Also, it is a 2 wire interface which gives your both directions. Error free isn't a problem.QuoteNot a problem.With the numbered function_pos I sent you, you can add and remove steps at different points. Check buffer status, what has been filled, what's next. You can even store something like a 32 line back buffer with a small Cyclone II as we will be storing the pixels of each line in blockram. Just look up on how many bits of blockram is available in your Cyclone II. Figure 1 line buffer will eat 2048bits of mem.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 24, 2022, 11:54:02 am
content: I'm using the only UART on the MCU (AT328P) I believe, and it is connected to an FTDI USB for PC connectivity. The MCU is required for controlling other parts of the device such as polygon mirror speed, steppers and such. Also the wires are soldered in, so for now I prefer not to deviate from the current bit banging method. If it proves to be too slow for my application I may have to change it.The Cyclone II has 1 Mbit of RAM (https://engineering.purdue.edu/ece477/Archive/2007/Fall/F07-Grp02/nb/Notes/Cyclone-II_Memory_Blocks.pdf) so that should be way more than I'll ever need

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 11:59:19 am
content: Ok, you will only need 3 wires for 1 bit data, 1 clock and 1 data ready, or, better yet, 2-3 control wires and 1 8bit wide bus to get the cpu cycles down to only a few per command.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 24, 2022, 12:03:49 pm
content: Quote from: BrianHG on March 24, 2022, 11:59:19 amSounds good. We'll have to stick with 1 data wire, since I've also used all the general purpose pins on the AT328P

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 12:19:28 pm
content: So, we are speaking like:input data_in,input comms_clk,input execute,...reg_data_in <= data_in ;reg_comms_clk <= comms_clk ;reg_execute <= execute ;dly_comms_clk <= reg_comms_clk ;// load a bit into the 20bit register every time the comms clock goes low to high, or goes from high to low.// bi-directional sensitivity on the comm clock means 1 less write to the MCU port for every bit.shift_bit_in <= reg_comms_clk != dly_comms_clk;if ( shift_bit_in ) command_packet[19:0] <= { command_packet[18:0] , reg_data_in } ;// store results in the 16 bit setting register bank 4 word arrayif (reg_execute) reg_bank[command_packet[19:16]] <= command_packet[15:0] ;This should get you started with 16 available 16 bit setting registers.You should be able to bit-blast up to ~8 megabaud with your 25MHz source clock.

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 12:23:56 pm
content: You can shrink the 20 bit reg into a 16 bit one and have 12bit data, 4 bit address.This way in your MCU, you use only 2 bytes per command, storing the destination address in the upper 4 bits of the 16 bit word.This means the pixel clock period and pixel length will now only be 12 bits, or 0-4095.

author: BrianHG
author_rank: Super Contributor
timestamp: March 24, 2022, 12:53:36 pm
content: You can also change the bit clock sensitivity to:shift_bit_in <= reg_comms_clk && !dly_comms_clk;If you want to trigger exclusively on the rising edge.It depends on your bitbang routing.I know I would do bare metal in assembly to send 2 bytes by writing 35 lines of code like:clrf port (mircochip style)bittestsc byte1,bit0set port,databitset port,clkclr port,databitbittestsc byte1,bit1set port,databitclr port,clk.....At 20 mips on the MCU, this would transmit at 4 megabaud.

author: pcprogrammer
author_rank: Super Contributor
timestamp: March 24, 2022, 03:20:54 pm
content: Quote from: kpow8050 on March 24, 2022, 11:54:02 amThe AT328P does also have SPI and TWI. The TWI is only 400KHz i2c, but will be faster than bit blasting. The SPI needs three wires for bidirectional communication but is a lot faster.So maybe an option. I'm sure BrianHG has some module for this lying around.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 25, 2022, 05:38:37 am
content: Quote from: pcprogrammer on March 24, 2022, 03:20:54 pmGood point! Is it possible to use any pins for SPI? Or are there dedicated pins for that function. I prefer not to have to de-solder anything at this stage

author: pcprogrammer
author_rank: Super Contributor
timestamp: March 25, 2022, 06:04:13 am
content: Unfortunately these pins are only available on fixed locations. See section 14.3 in the ATmega48A/PA/88A/PA/168A/PA/328/P datasheet.https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdfFor SPI the pins are:PB5 => SCKPB4 => MISOPB3 => MOSIPB2 => SS (Slave select, which is not needed in a single slave device on the bus setup)Attached the assembler instruction manual. These 8 bit AVR devices are fairly simple to program in assembler if better performance is needed over output from C.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 25, 2022, 10:23:46 am
content: Quote from: BrianHG on March 24, 2022, 11:20:43 amI've tested the new code on the real hardware and its working great - as expected. I've attached a photo of the laser output below which clearly shows it working. So I guess the next step is to implement the communication interface and get it working with commands

author: BrianHG
author_rank: Super Contributor
timestamp: March 25, 2022, 10:24:46 am
content: Quote from: pcprogrammer on March 24, 2022, 03:20:54 pmAnd after everything I taught you 2 over the past week, including triggering on different edge events and serial / parallel stacking clocked data input registers. You should be able to make your own SPI or I2C slave within a few lines of code and make it synchronous to you system clock compared to many of existing online examples which use their own clock domain and then transfer that data to your system clock with those possible headaches. But, there is alwayswww.Opencores.org. This is an opportunity to further develop some HDL skills.

author: kpow8050
author_rank: Regular Contributor
timestamp: March 25, 2022, 10:34:36 am
content: No worries. I think you've taught me enough that I can now proceed on my own with this project. I appreciate your guidance and helping me learn HDL basics. If I need more help hopefully you'll be here to answer in the future

author: pcprogrammer
author_rank: Super Contributor
timestamp: March 25, 2022, 11:49:47 am
content: Quote from: BrianHG on March 25, 2022, 10:24:46 amI'm sure I will be able to make such an interface on my own now. And you are right there is lots of information online. I just needed the jump startAnd one more thanks to you BrianHG,Peter