/*! * Module:cmd_frame_sequencer * @file cmd_frame_sequencer.v * @date 2015-06-30 * @author Andrey Filippov * * @brief Store/dispatch commands on per-frame basis * * @copyright Copyright (c) 2015 Elphel, Inc. * * License: * * cmd_frame_sequencer.v is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * cmd_frame_sequencer.v is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify this Program, or any covered work, by linking or combining it * with independent modules provided by the FPGA vendor only (this permission * does not extend to any 3-rd party modules, "soft cores" or macros) under * different license terms solely for the purpose of generating binary "bitstream" * files and/or simulating the code, the copyright holders of this Program give * you the right to distribute the covered work without those independent modules * as long as the source code for them is available from the FPGA vendor free of * charge, and there is no dependence on any encrypted modules for simulating of * the combined code. This permission applies to you if the distributed code * contains all the components and scripts required to completely simulate it * with at least one of the Free Software programs. */ `timescale 1ns/1ps // Comments from the x353 code: // This command sequencer is designed (together with i2c sequencer) to provide // pipelined operation of the sensor, FPGA pre-processor and compressor, to avoid // requirement of resetting the circuitry and loosing several frames when the sensor // acquisition parameters are changed (especially geometry - WOI, decimation). // It also reduces real-time requirements to the software, as it is possible to // program parameters related to the events several frames in the future. // // // Controller is programmed through 32 locations. Each registers but the control require two writes: // First write - register address (AXI_WR_ADDR_BITS bits), second - register data (32 bits) // Writing to the contol register (0x1f) resets the first/second counter so the next write will be "first" // 0x0..0xf write directly to the frame number [3:0] modulo 16, except if you write to the frame // "just missed" - in that case data will go to the current frame. // 0x10 - write seq commands to be sent ASAP // 0x11 - write seq commands to be sent after the next frame starts // // 0x1e - write seq commands to be sent after the next 14 frame start pulses // 0x1f - control register: // [14] - reset all FIFO (takes 32 clock pulses), also - stops seq until run command // [13:12] - 3 - run seq, 2 - stop seq , 1,0 - no change to run state // New - [1:0] - 0: NOP, 1: clear IRQ, 2 - Clear IE, 3: set IE module cmd_frame_sequencer#( parameter CMDFRAMESEQ_ADDR= 'h780, parameter CMDFRAMESEQ_MASK= 'h7e0, parameter AXI_WR_ADDR_BITS = 14, parameter CMDFRAMESEQ_DEPTH = 64, // 32/64/128 parameter CMDFRAMESEQ_ABS = 0, parameter CMDFRAMESEQ_REL = 16, parameter CMDFRAMESEQ_CTRL = 31, parameter CMDFRAMESEQ_RST_BIT = 14, parameter CMDFRAMESEQ_RUN_BIT = 13, parameter CMDFRAMESEQ_IRQ_BIT = 0 // [1:0] used )( input mrst, input mclk, // for command/status // programming interface input [7:0] cmd_ad, // byte-serial command address/data (up to 6 bytes: AL-AH-D0-D1-D2-D3 input cmd_stb, // strobe (with first byte) for the command a/d // frame sync and frame number input frame_sync, // @posedge mclk output [ 3:0] frame_no, // @posedge mclk // command mux interface output [AXI_WR_ADDR_BITS-1:0] waddr, // write address, valid with wr_en_out output valid, // output data valid output [31:0] wdata, // write data, valid with waddr_out and wr_en_out input ackn, // command sequencer address/data accepted // Interrupt mask, status, request output is, // interrupt status (not masked) output im // interrupt mask ); localparam PNTR_WIDH = (CMDFRAMESEQ_DEPTH > 32) ?((CMDFRAMESEQ_DEPTH > 64) ? 7 : 6) : 5; wire [4:0] cmd_a; // 3 cycles before data wire cmd_we; // 3 cycles befor data reg [2:0] cmd_we_r; // cmd_we_r[2] - with cmd_data wire [31:0] cmd_data; reg [3:0] wpage_asap; // FIFO page were ASAP writes go reg [3:0] wpage_prev; // unused page, currently being cleared reg [3:0] wpage_next; // next page to be used reg [3:0] wpage_w; // FIFO page where current writes go reg [1:0] wpage_inc; // increment wpage_asap (after frame sync or during reset), and [1] next clock cycle after [0] wire reset_cmd; wire run_cmd; reg reset_on; // reset FIFO in progress reg seq_enrun=0; // enable seq reg we_fifo_wp; // enable writing to fifo write pointer memory reg next_frame_rq; // request to switch to the new frame page, clear pointer for the one just left wire pre_wpage_inc; reg [PNTR_WIDH-1:0] fifo_wr_pointers_ram [0:15]; wire [PNTR_WIDH-1:0] fifo_wr_pointers_outw=fifo_wr_pointers_ram[wpage_w]; wire [PNTR_WIDH-1:0] fifo_wr_pointers_outr=fifo_wr_pointers_ram[page_r]; reg [PNTR_WIDH-1:0] fifo_wr_pointers_outw_r; reg [PNTR_WIDH-1:0] fifo_wr_pointers_outr_r; reg d_na; // register counting address(0) or data(1) when writing sequences reg [AXI_WR_ADDR_BITS-1:0] address_hold; // register to hold command address to write to the sequencer simultaneously with data wire [63:0] cmdseq_di = {{32 - AXI_WR_ADDR_BITS{1'b0}},address_hold,cmd_data}; // data to write to the command sequencer reg [2:0] por=0; //power on reset reg initialized; // command fifo initialized wire cmd_we_ctl_w = cmd_we && (cmd_a == CMDFRAMESEQ_CTRL); // 3 cycles before data reg [2:0] cmd_we_ctl_r; // cmd_we_ctl_r[2] - with data wire cmd_we_abs_w = cmd_we && ((cmd_a & 'h10) == CMDFRAMESEQ_ABS); // 3 cycles before data reg cmd_we_abs_r; // 2 cycles before data wire cmd_we_rel_w = cmd_we && ((cmd_a & 'h10) == CMDFRAMESEQ_REL) && (cmd_a != CMDFRAMESEQ_CTRL); // 3 cycles before data reg cmd_we_rel_r; // 2 cycles before data reg [2:0] cmd_we_any_r; // any of the abs or rel (valid 2, 1 and 0 cycles before data) wire reset_seq_done; reg [PNTR_WIDH+3:0] seq_cmd_wa; // width of in-page pointer plus 4 (number of pages) wire [PNTR_WIDH+3:0] seq_cmd_ra; // width of in-page pointer plus 4 (number of pages) reg [3:0] page_r; // FIFO page from where commands are generated reg [1:0] page_r_inc; // increment page_r - signal and delayed version reg [PNTR_WIDH-1:0] rpointer; // FIFO read pointer for current page reg [1:0] read_busy; // reading and sending command reg conf_send; // valid && ackn wire commands_pending; // wants to send some commands reg [1:0] ren; // 1-hot ren to BRAM, then regen to BRAM wire pre_cmd_seq_w; // 1 cycle before starting command read/send sequence reg valid_r; wire [63:0] cmdseq_do; // output data from the sequence wire [1:0] irq_bits = cmd_data[CMDFRAMESEQ_IRQ_BIT +: 2]; reg is_r; // interrupt status (not masked) reg im_r; // interrtupt mask wire irq_ctrl; assign is = is_r; // interrupt status (not masked) assign im = im_r; // interrtupt mask assign irq_ctrl = cmd_we_ctl_r[2] && (|irq_bits); assign waddr = cmdseq_do[32 +:AXI_WR_ADDR_BITS]; assign wdata = cmdseq_do[31:0]; assign seq_cmd_ra = {page_r,rpointer}; assign frame_no = wpage_asap; assign reset_cmd = (!reset_on && cmd_we_ctl_r[2] && cmd_data[CMDFRAMESEQ_RST_BIT]) || (por[1] && !por[2]); assign run_cmd = (!reset_on && cmd_we_ctl_r[2] && cmd_data[CMDFRAMESEQ_RUN_BIT]); // assign reset_seq_done = reset_on && wpage_inc[0] && (wpage_asap == 4'hf); assign reset_seq_done = reset_on && wpage_inc[0] && (&wpage_asap[3:1]); // ends after 'he // assign pre_wpage_inc = (!cmd_we && !(|cmd_we_r) ) && (!wpage_inc[0] && !wpage_inc[1]) && ((next_frame_rq && initialized) || reset_on) ; // During reset_on write pointer every cycle: // assign pre_wpage_inc = (!cmd_we && !(|cmd_we_r) ) && ((next_frame_rq && initialized) || reset_on) ; assign pre_wpage_inc = (!cmd_we && !(|cmd_we_r) ) && ((next_frame_rq && !wpage_inc[0] && initialized) || reset_on) ; assign commands_pending = rpointer != fifo_wr_pointers_outr_r; // only look at the current page different pages will trigger page increment first assign pre_cmd_seq_w = commands_pending & ~(|page_r_inc) & seq_enrun; assign valid = valid_r; // parameter CMDFRAMESEQ_IRQ_BIT = 0, // parameter CMDFRAMESEQ_IRQ_BITS = 2 always @ (posedge mclk) begin if (mrst) is_r <= 0; else if (frame_sync) is_r <= 1; else if (irq_ctrl && (irq_bits == 2'b01)) is_r <= 0; if (mrst) im_r <= 0; else if (irq_ctrl && irq_bits[1]) im_r <= irq_bits[0]; end always @ (posedge mclk) begin if (mrst) por <= 0; else por <= {por[1:0], 1'b1}; if (mrst) seq_enrun <= 0; else if (reset_cmd) seq_enrun <= 0; else if (run_cmd) seq_enrun <= cmd_data[CMDFRAMESEQ_RUN_BIT-1]; if (mrst) initialized <= 0; else if (reset_seq_done) initialized <= 1; if (mrst) d_na <= 0; else if (cmd_we_ctl_w) d_na <= 0; else if (cmd_we) d_na <= ~ d_na; if (mrst) valid_r <= 0; else if (ren[1]) valid_r <= 1; else if (ackn) valid_r <= 0; end always @ (posedge mclk) begin cmd_we_ctl_r <= {cmd_we_ctl_r[1:0],cmd_we_ctl_w}; cmd_we_r <= {cmd_we_r[1:0], cmd_we}; cmd_we_abs_r <= cmd_we_abs_w; cmd_we_rel_r <= cmd_we_rel_w; cmd_we_any_r <= {cmd_we_any_r[1:0], cmd_we_abs_w | cmd_we_rel_w}; // signals related to writing to seq FIFO if (cmd_we_r[1] && !d_na) address_hold <= cmd_data[AXI_WR_ADDR_BITS-1:0]; // decoded commands // write pointer memory wpage_inc <= (&por[1:0]) ? {wpage_inc[0],pre_wpage_inc} : 2'b0; if (reset_cmd || !por[1]) wpage_next <= 1; else if (wpage_inc[0]) wpage_next <= wpage_next + 1; // if (reset_cmd || !por[1]) wpage_asap <= 0; else if (wpage_inc[0]) wpage_asap <= wpage_next; // valid at cmd_we_*_r if (reset_cmd || !por[1]) wpage_prev <= 4'hf; else if (wpage_inc[0]) wpage_prev <= wpage_asap; // valid at cmd_we_*_r if (!por[1]) reset_on <= 0; else if (reset_cmd) reset_on <= 1; else if (reset_seq_done) reset_on <= 0; if (!por[1]) next_frame_rq <= 0; else if (frame_sync) next_frame_rq <= 1; else if (wpage_inc[0]) next_frame_rq <= 0; // now cmd_we_abs_r or cmd_we_rel_r can not happen with wpage_inc[0] - earliest at the next cycle if (cmd_we_abs_r) wpage_w <= (cmd_a[3:0] == wpage_prev)? wpage_asap : cmd_a[3:0]; else if (cmd_we_rel_r) wpage_w <= wpage_asap + cmd_a[3:0]; else if (wpage_inc[0]) wpage_w <= wpage_asap; // will now be previous (switched at the same cycle) we_fifo_wp <= cmd_we_any_r[1] || wpage_inc[0]; if (cmd_we_any_r[1]) fifo_wr_pointers_outw_r <= fifo_wr_pointers_outw; // register pointer RAM output (write port) // write to pointer RAM (to the same address as just read from if read) if (we_fifo_wp) fifo_wr_pointers_ram[wpage_w] <= wpage_inc[1] ? {PNTR_WIDH{1'b0}}:(fifo_wr_pointers_outw_r + 1); if (cmd_we_any_r[1]) seq_cmd_wa <= {wpage_w, fifo_wr_pointers_outw}; fifo_wr_pointers_outr_r <= fifo_wr_pointers_outr; // just register write pointer for the read page page_r_inc <= {page_r_inc[0], (~read_busy[0] | conf_send) & // not busy or will not be busy next cycle (when page_r_inc active) ~(|page_r_inc) & // read_page was not just incremented, so updated read pointer had a chance to propagate (rpointer == fifo_wr_pointers_outr_r) & // nothing left in the frame FIFO pointed page_r (page_r != wpage_asap)}; // the page commands are taken from is not the ASAP (current) page if (!por[1] || reset_on) page_r <= 0; else if (page_r_inc[0]) page_r <= page_r+1; // if (reset_on || reset_cmd || page_r_inc[0]) rpointer <= 0; // TODO: move to rst ? if (!por[1] || reset_on || page_r_inc[0]) rpointer <= 0; // TODO: move to rst ? else if (ren[0]) rpointer <= rpointer + 1; conf_send <= valid && ackn; // if (reset_on || reset_cmd) read_busy <= 0; if (!por[1] || reset_on) read_busy <= 0; else read_busy <= {read_busy[0], read_busy[0]? (~conf_send) : pre_cmd_seq_w}; ren <= {ren[0], pre_cmd_seq_w}; // TODO: check generation of the reset sequence end cmd_deser #( .ADDR (CMDFRAMESEQ_ADDR), .ADDR_MASK (CMDFRAMESEQ_MASK), .NUM_CYCLES (6), .ADDR_WIDTH (5), .DATA_WIDTH (32), .WE_EARLY (3) // generate cmd_we and cmd_a three cycles before cmd_data is valid ) cmd_deser_32bit_i ( .rst (1'b0), // rst), // input .clk (mclk), // input .srst (mrst), // input .ad (cmd_ad), // input[7:0] .stb (cmd_stb), // input .addr (cmd_a), // output[3:0] .data (cmd_data), // output[31:0] .we (cmd_we) // output ); // Generate one x64 BRAM, 3 of x32 or 3 x64, depending on the sequnecer depth (CMDFRAMESEQ_DEPTH): 32/64/128 commands per frame generate if (CMDFRAMESEQ_DEPTH == 32) begin ram_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(6), .LOG2WIDTH_RD(6), .DUMMY(0) ) ram_var_w_var_r_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[8:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[4:0]}), // input[8:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do), // output[63:0] .wclk (mclk), // input // VDT TODO: make conditions in generate skip parsing if condition does not match // .waddr (seq_cmd_wa), // input[8:0] .waddr ({seq_cmd_wa[PNTR_WIDH+3 -:4],seq_cmd_wa[4:0]}), // input[8:0] // just to make VDT happy .we (cmd_we_any_r[2]), // input .web (8'hff), // input[7:0] .data_in (cmdseq_di) // input[63:0] ); end else if (CMDFRAMESEQ_DEPTH == 64) begin ram18_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram18_var_w_var_r_dl_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[9:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[15:0]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .we (cmd_we_any_r[2]), // input .web (4'hf), // input[3:0] .data_in (cmdseq_di[15:0]) // input[15:0] ); ram18_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram18_var_w_var_r_dh_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[9:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[31:16]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .we (cmd_we_any_r[2]), // input .web (4'hf), // input[3:0] .data_in (cmdseq_di[31:16]) // input[15:0] ); ram18_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram18_var_w_var_r_ad_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[9:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[47:32]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[5:0]}), // input[9:0] .we (cmd_we_any_r[2]), // input .web (4'hf), // input[3:0] .data_in (cmdseq_di[47:32]) // input[15:0] ); end else if (CMDFRAMESEQ_DEPTH == 128) begin ram_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram_var_w_var_r_dl_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[10:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[15:0]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .we (cmd_we_any_r[2]), // input .web (8'hff), // input[7:0] .data_in (cmdseq_di[15:0]) // input[15:0] ); ram_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram_var_w_var_r_dh_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[10:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[31:16]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .we (cmd_we_any_r[2]), // input .web (8'hff), // input[7:0] .data_in (cmdseq_di[31:16]) // input[15:0] ); ram_var_w_var_r #( .REGISTERS(1), .LOG2WIDTH_WR(4), .LOG2WIDTH_RD(4), .DUMMY(0) ) ram_var_w_var_r_ad_i ( .rclk (mclk), // input // .raddr (seq_cmd_ra), // input[10:0] .raddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .ren (ren[0]), // input .regen (ren[1]), // input .data_out (cmdseq_do[47:32]), // output[15:0] .wclk (mclk), // input // .waddr (seq_cmd_wa), // input[9:0] .waddr ({seq_cmd_ra[PNTR_WIDH+3 -:4],seq_cmd_ra[6:0]}), // input[10:0] .we (cmd_we_any_r[2]), // input .web (8'hff), // input[7:0] .data_in (cmdseq_di[47:32]) // input[15:0] ); end else begin // cause some error - invalid CMDFRAMESEQ_DEPTH end endgenerate endmodule