RAM timing problems (SOLVED) - Page 1
Forum section: electronics / general

author: kpow8050
author_rank: Regular Contributor
timestamp: April 05, 2022, 01:39:25 pm
content: I have just started using Block RAM in my FPGA project, and so far I've been able to implement it in a crude way however I am not getting the results I expect. I have a fixed size block RAM which I am writing data to synchronously each clock cycle, and then reading out that data continuously on the next clock cycles. The RAM has 8 bit wide input and 1 bit output. I have added a delay between read and write, giving at least 2 cycles for each. Despite this, the RAM is behaving with uncertainty in output state, viewing the output on the oscilloscope shows that the RAM will not behave consistently and doesn't reflect the data I am storing in it. I have attached my source code below in case anyone is able to spot an obvious flaw in my implementationCode:module LaserControl (input data_in,input comms_clk,input execute,input clk_in,input in_a,input in_b,output reg ld_out);// Variable and memory constantsparameter bit USE_NEG_EDGE = 1 ;parameter N_BITS = 16; // Total number of bits supported by the incoming registerparameter N_DATA_BITS = 12; // How many bits of data can be recived in one blockparameter MAX_RAM = 4095;parameter VRAM_BANK2 = 4095;// Start address for bank 2 of vram// Control constantsparameter SET_PIXEL_OFFSET = 1;parameter SET_PIXEL_PERIOD = 5;parameter SET_SCAN_LINE_LIMIT = 6;parameter STORE_PIXEL_PACKET = 2; // Memory address is automatically incrementedparameter RESET_ADDRESS = 3;parameter UPDATE_VRAM = 4;parameter BLANK_BEAM = 7;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 ;reg debug_mode = 1 ; // 1 = display test pattern, 0 = display pattern from VRAMreg [4:0] function_pos = 0 ; // Create a program position pointer which can serve 32 states.reg [4:0] ram_state= 0 ; // Store the state of RAM operationreg [4:0] ram_readout_state = 0 ;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 [N_DATA_BITS-1:0] max_pixel_count = 50;reg [N_DATA_BITS-1:0] pixel_offset = 40; // how many pixels to delay the output laser pulsesreg [N_DATA_BITS-1:0] pixel_count = 0 ;reg [N_DATA_BITS-1:0] pixel_period = 50 ; // Period of the pixel clock (1 = period of main clock, 2 = 1/2 of main clock, etc..reg [N_DATA_BITS-1:0] pixel_period_timer_reg = 0;reg sync_in_dly = 0 ;// communcationsreg comms_clk_dly = 0 ;reg shift_bit_in = 0 ;reg [N_BITS-1:0] command_packet = 0 ;reg [2:0] command_reg = 0 ;reg [N_DATA_BITS-1:0] data_reg = 0 ;reg [N_DATA_BITS-1:0] data_reg_reversed = 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.// Video memory (can store up to 4095 pixels based on the current 12 bit address size)regwriteEnable = 0;// Write enable for the VRAMreg [7:0]dataWord = 0;// Data word to write to VRAMregVRAM_out = 0;// The output bit of the VRAMreg [12:0]VRAM_raddress = 0;// Read address for the VRAMreg [8:0]VRAM_waddress = 0;// Write address for the VRAMreg [12:0]VRAM_offset = 0;// Offset address for VRAM (for reading bank 1 or bank 2)VRAM activeRAM (.clock (clk_in),.data (dataWord),.rdaddress (VRAM_raddress),.wraddress (VRAM_waddress),.wren (writeEnable),.q (VRAM_out)); // Current VRAM for outputing pixel data// Reverse data on the input data registerassign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;// **************************************************// 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// **************************************************// Communications// **************************************************comms_clk_dly <= reg_comms_clk;// load a bit into the communications register every time the comms clock goes low to highshift_bit_in <= reg_comms_clk && !comms_clk_dly;if ( shift_bit_in ) command_packet[N_BITS-1:0] <= { command_packet[N_BITS-2:0] , reg_data_in } ;// store results in the setting register bankif (reg_execute) begincommand_reg <= command_packet[2:0]; // Store the command op-codedata_reg <= command_packet[N_BITS-1:N_BITS-N_DATA_BITS]; // Store the datacase (command_reg)SET_PIXEL_OFFSET : begin // Set the pixel offset registerpixel_offset <= data_reg_reversed;endSET_PIXEL_PERIOD : begin // Set pixel period registerpixel_period <= data_reg_reversed;endSET_SCAN_LINE_LIMIT : begin // Set scan line limit registermax_pixel_count <= data_reg_reversed;endBLANK_BEAM : begin // Blank or unblank the laser beam, also controls debug modeenable_laser <= data_reg_reversed;debug_mode <= data_reg_reversed;endSTORE_PIXEL_PACKET : begin // Store the pixels in the buffer RAMif(ram_state == 0) ram_state <= 1;// Initiate store cycleendRESET_ADDRESS : begin // reset the buffer address for buffer memory accessif (data_reg_reversed < MAX_RAM) VRAM_waddress <= data_reg_reversed; // 0 will reset it to 0, otherwise the buffer address can be set to an arbitrary address locationelse VRAM_waddress <= 1'b0;endUPDATE_VRAM : begin // Switch active bank of VRAM by changing address offsetif (VRAM_offset == 0) VRAM_offset <= VRAM_BANK2;else VRAM_offset <= 0;endendcaseend// **************************************************// 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;comms_clk_dly <= reg_comms_clk;// **************************************************// Begin Sequencer for controlling RAM// **************************************************case (ram_state)default : ram_state <= 0;// Undefined state, reset back to 00 : begin // Do nothing// Ready statewriteEnable <= 0;end1 : begin // Store data, setup RAM storage buffer registerwriteEnable <= 1;dataWord <= data_reg_reversed[7:0];// Store 1 word into VRAM bufferram_state <= ram_state + 1'b1;// Go to next function state (on next clock cyle)end2 : begin // Clock in data to RAM// Data word is clocked in, increment addressif ( VRAM_waddress < MAX_RAM ) VRAM_waddress <= VRAM_waddress + 1'b1; // Increment buffer addresselse VRAM_waddress <= 1'b0; // reset buffer addressram_state <= 0;// reset stateendendcase// **************************************************// 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 clockif( debug_mode == 1 ) laser_out <= pixel_count; // Laser is toggled based on the LSB of pixel counterelse begincase (ram_readout_state)default : ram_readout_state <= 0;// Undefined state, reset back to 00 : begin // Wait 1 cycle for RAM readylaser_out <= 1'b1;ram_readout_state <= ram_readout_state + 1'b1;end1 : begin // Read out VRAMlaser_out <= VRAM_out;ram_readout_state <= ram_readout_state + 1'b1;end2 : begin// Increment the address counterlaser_out <= 1'b1;VRAM_raddress <= VRAM_raddress + 1'b1 ; // Increment the VRAM read addressram_readout_state <= 1'b0;endendcase;endend5 : begin// Sweep finished// Turn laser onlaser_out <= 1'b0;VRAM_raddress <= VRAM_offset ;// reset the VRAM addressram_readout_state <= 0;function_pos <= 1; // Reset function counterendendcase// **************************************************// End Sequencer for driving the laser output// **************************************************end // !resetend // always @25 MHz clock.// **************************************************// TASKS - set laser pixel clock and address pointer.// **************************************************task SET_laser_clock(bit [N_DATA_BITS-1:0] period,bit [N_DATA_BITS-1:0] length);beginpixel_count <= length;pixel_period_timer_reg <= period;endendtask// **************************************************// TASKS - run laser pixel timer and address pointer.// **************************************************task RUN_laser_clock(bit [N_DATA_BITS-1: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.endendendtaskendmodule

author: Someone
author_rank: Super Contributor
timestamp: April 05, 2022, 01:49:04 pm
content: Not showing the ram module when you say the problem is with recovering data from it... look into my crystal ball.

author: BrianHG
author_rank: Super Contributor
timestamp: April 05, 2022, 02:35:16 pm
content: Since you are not showing you ram instantiation, we cannot help.Notesa) The data written in ram takes 2 clock to changes.b) When reading ram, depending on settings, the data output is usually delayed 2 clocks behind the sent in read address. IE, Clock cycle 1 latches the read address and passes it to the memory array. Clock cycle 2 latches the read data from the memory array and sends it to the rest of your HDL code.You can also prepare and set a .mif (memory initialization file) which presets the ram at powerup.(note when using a .mif, modelsim wants a copy of the .mif file in your main project simulation folder.)

author: kpow8050
author_rank: Regular Contributor
timestamp: April 05, 2022, 11:52:04 pm
content: Sorry guys, my bad for not including the RAM instantiation. Here it is below (generated using the MegaFunction wizard)Code:module VRAM (clock,data,rdaddress,wraddress,wren,q);inputclock;input[7:0] data;input[12:0] rdaddress;input[9:0] wraddress;inputwren;output[0:0] q;`ifndef ALTERA_RESERVED_QIS// synopsys translate_off`endiftri1clock;tri0wren;`ifndef ALTERA_RESERVED_QIS// synopsys translate_on`endifwire [0:0] sub_wire0;wire [0:0] q = sub_wire0[0:0];altsyncramaltsyncram_component (.address_a (wraddress),.clock0 (clock),.data_a (data),.wren_a (wren),.address_b (rdaddress),.q_b (sub_wire0),.aclr0 (1'b0),.aclr1 (1'b0),.addressstall_a (1'b0),.addressstall_b (1'b0),.byteena_a (1'b1),.byteena_b (1'b1),.clock1 (1'b1),.clocken0 (1'b1),.clocken1 (1'b1),.clocken2 (1'b1),.clocken3 (1'b1),.data_b (1'b1),.eccstatus (),.q_a (),.rden_a (1'b1),.rden_b (1'b1),.wren_b (1'b0));defparamaltsyncram_component.address_reg_b = "CLOCK0",altsyncram_component.clock_enable_input_a = "BYPASS",altsyncram_component.clock_enable_input_b = "BYPASS",altsyncram_component.clock_enable_output_a = "BYPASS",altsyncram_component.clock_enable_output_b = "BYPASS",altsyncram_component.intended_device_family = "Cyclone II",altsyncram_component.lpm_type = "altsyncram",altsyncram_component.numwords_a = 1024,altsyncram_component.numwords_b = 8192,altsyncram_component.operation_mode = "DUAL_PORT",altsyncram_component.outdata_aclr_b = "NONE",altsyncram_component.outdata_reg_b = "CLOCK0",altsyncram_component.power_up_uninitialized = "FALSE",altsyncram_component.read_during_write_mode_mixed_ports = "DONT_CARE",altsyncram_component.widthad_a = 10,altsyncram_component.widthad_b = 13,altsyncram_component.width_a = 8,altsyncram_component.width_b = 1,altsyncram_component.width_byteena_a = 1;endmodule

author: BrianHG
author_rank: Super Contributor
timestamp: April 06, 2022, 12:25:52 am
content: Take a look at these 2 files, add the lines in red:setup_lc.do:Quoterun_lc.do:QuoteThis is assuming that the file you listed above is called 'VRAM.v'.Also, the VRAM.V file needs to be in the same folder as all your other source files.Adding the correct 2 red lines means modelsim setup .do batch files should work.The part in green, ' -L cycloneive_ver ' may need to be changed as it tells modelsim to look for Altera's Cyclone IV-E functions, though altsyncram should be backwards/forwards compatible with Cyclone II.

author: BrianHG
author_rank: Super Contributor
timestamp: April 06, 2022, 12:31:58 am
content: Note that once you got your sim working, and you added all the new nets to your waveform, you will discover a few minor logic bombs in your code.From there, see what you can fix before we dive deeper into solutions.Also, I recommend in Quartus making a dummy initialization file for the dualport ram, so you will have dummy contents at powerup to help see what is going on.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 17, 2022, 12:10:38 pm
content: Hi BrianHG, sorry for the long delay - things came up that prevented me working on this project. I have come back to it now and my first attempt is to get the ModelSim running, however it seems that its not able to instantiate the VRAM module. The error message is shown below# Loading work.LaserControl_tb# Loading work.LaserControl# Loading work.VRAM# ** Error: (vsim-3033) VRAM.sv(50): Instantiation of 'altsyncram' failed. The design unit was not found.## Region: /LaserControl_tb/UUT/activeRAM# Searched libraries:# C:/Users/Keith/Documents/Electronic projects/UV laser lithography/LaserControl_v5/work# Error loading designWondering what would cause this error. All the modules are present in the working directory. I have attached the run_lc.do inside the .zip folder in case you need to look at that

author: BrianHG
author_rank: Super Contributor
timestamp: September 17, 2022, 01:44:44 pm
content: You forgot to add to your setup_lc.do:QuoteWhen adding an entire new module, you do need to rerun the setup_lc.do, or, re-run the 'vsim ' command line.Note that in your run_lc.do, it is common practice to make sure that the 'vlog -sv -work work {LaserControl_tb.sv}' is the last one in the list of 'vlog's. However it still seems to work.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 18, 2022, 12:01:08 am
content: That has solved the problem!After a bit of messing about with various errors I managed to get it to compile this far. Essentially it seems to be giving an error about continuous and procedural assignments. I'm not really sure what this refers to or how to fix it?# Loading altera_mf_ver.ALTERA_MF_MEMORY_INITIALIZATION# ** Error: (vsim-3838) LaserControl.sv(92): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(91): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(90): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(89): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(88): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(87): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(86): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(85): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(84): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(83): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(82): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# ** Error: (vsim-3838) LaserControl.sv(81): Variable '/LaserControl_tb/UUT/data_reg_reversed' written by continuous and procedural assignments. See LaserControl.sv(58).## Region: /LaserControl_tb/UUT# Error loading design# Error: Error loading design

author: BrianHG
author_rank: Super Contributor
timestamp: September 18, 2022, 12:10:39 am
content: Well, read the line numbers with the errors.Something register written by continuous at line 58 has also simultaneously procedural assignments on lines 81 through 92.You can define an assignment at one or the other, but not both.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 18, 2022, 12:46:52 am
content: I modified the data_reg_reversed to a wire and the error goes away. I get a new error instead it showsError (10028): Can't resolve multiple constant drivers for net "data_reg_reversed" at lasercontrol.sv(81)Essentially I cannot see what is wrong with this, it is only defined once in the code. Ideally all that is required of these lines is to reverse the data in a register. My assumption is that it would be easier to do on the FPGA side, rather than change the arduino code side. I would prefer this occurs in logic, rather than sequential way too// Reverse data on the input data registerassign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;assign data_reg_reversed = data_reg;

author: BrianHG
author_rank: Super Contributor
timestamp: September 18, 2022, 01:02:37 am
content: Line 58:reg [N_DATA_BITS-1:0] data_reg_reversed= 0;You cannot assign a fixed 0, and then assign a new value, it is not being used as clocked logic register which can change values. In fact, what you have there is actually a wire which you are assigning to another register. You should define it as:wire [N_DATA_BITS-1:0] data_reg_reversed;Though, the 'reg' without the =0 should still work. It is just better to comprehend how you are using the net name 'data_reg_reversed'.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 18, 2022, 01:16:26 am
content: Quote from: BrianHG on September 18, 2022, 01:02:37 amPerfect. That fixed it! I don't know how I missed that, I must still be stuck in my sequential programming mode

author: BrianHG
author_rank: Super Contributor
timestamp: September 18, 2022, 01:26:17 am
content: I'm not sure, but if you are only using the reversed data, why not change this line 135:if ( shift_bit_in ) command_packet[N_BITS-1:0] <= { command_packet[N_BITS-2:0] , reg_data_in } ;to:if ( shift_bit_in ) command_packet[N_BITS-1:0] <= { reg_data_in, command_packet[N_BITS-1:1] } ;Though your data comes in reverse, you would carefully need to modify lines 139 & 140 as the location of the data and command is reversed as well as the command ID name/numbers.Anyways, what you have is fine.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 18, 2022, 03:55:57 am
content: Makes sense. I think I will stick with what I have for now.Now that I am debugging the memory problem, I have decided to create a memory initialization file (.mif) to initialize the contents of the RAM to a defined state. I added the following line into the defparam section of the VRAM.sv file to load the memory contents (attached in the .zip is the .mif file)"altsyncram_component.init_file = "LaserControl.mif";"I setup the test bench so that the ld_out will output the contents of memory starting from address 0 and incrementing every time the pixel counter goes up. Currently there is no output from the ld_out, however there should be 0, 1, 0, 1 etc output based on the memory contents I have given it. I'm wondering if there is a way to check if the RAM was initialized properly? Either way the code is not working how I would expect.

author: BrianHG
author_rank: Super Contributor
timestamp: September 18, 2022, 05:04:28 am
content: Since you have the .mif set to 8 bits and the output set to 1bit, shouldn't the output go:0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1...Remember, you can specify the 'layout' of the .mif file to match the A input port, or the B output port.But, since you set the .mif to 8 bit, I assume you have the layout on port A.If you want 0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0..., you will need to make the first address 0 byte = 8'h55, (or 8'hAA, I don't remember the output bit addressing order.), and all other bytes = 0.

author: kpow8050
author_rank: Regular Contributor
timestamp: September 19, 2022, 03:20:31 am
content: I was under the impression that it would assume the port A format. But in any case wouldn't the output still result in 0,1,0,1... if it were defined as 8 bit or 1 bit, since when reading from the 1 bit output port each address increment effectively covers each bit in the 1 byte word. Maybe I should just stick with a symmetrical memory structure since i'm not really sure how this is working underneath. I could go with 8 bit in 8 bit out and have a counter to keep track of address and bit number on the output. When I test the design on the real hardware the memory seems to be corrupt or something, because it causes the device to lock up or get into an undefined state whenever I switch it into the memory reading mode. Debugging so far has not revealed the bug to me so I will need to continue to dig deeper on this. Worst case scenario I may go with an external static RAM chip as I'm pretty familiar with how to get those working

author: kpow8050
author_rank: Regular Contributor
timestamp: October 04, 2022, 04:31:55 am
content: I have solved the timing issues with the block RAM on the FPGA. Just thought I would post an image of a successful PCB exposure that I did with this device. There is still a few things that need to be tweaked and unknown issues such as distortion at the end of scan lines (not sure if its from the laser or due to packets of data being lost or both). But this project is a success.