Forum Discussion

DWolf's avatar
DWolf
Icon for New Contributor rankNew Contributor
7 years ago

M9K BRAM on Max 10: Data at wrong addresses?

I'm using an M9K BRAM in single clock, dual port mode. It is controlled by two state machines, one writing data, and one reading data. I'm looking at all of the signals in signal tap, and everything looks like it's operating correctly. However, assuming I'm writing data words {D1, D2, D3, D4} to adresses {A1, A2, A3, A4}, during readout I get {D1, D1, D2, D3} for addresses {A1, A2, A3, A4}. My first thought was that there was a setup time violation between data and the write clock, or address and the read clock. I can't find any documentation on timing requirements for the BRAM, but just to experiment, I increased the latency between data and write and address and read to 3 clock cycles. I still get the same error. Anyone know what's going on?

2 Replies

  • AndyN's avatar
    AndyN
    Icon for Occasional Contributor rankOccasional Contributor

    Can you share the code for your write state machine? The most likely situation is that the address and data are out of step with each other...

  • DWolf's avatar
    DWolf
    Icon for New Contributor rankNew Contributor

    So a little background. The purpose of the write module is to grab bits serially and put them into a shift register, and then write the contents of that shift register to the BRAM. This process is repeated for a programmable number of bits, depending on the amount of data in the shift register for a given cycle. New data is available to write as soon as the WAIT_READOUT state is done. You can see where I put some extra states between WAIT_READOUT and WRITE_MEM in to increase the latency between the address changing and the rising edge of the BRAM clock. This does not seem to have any effect. There is also at least one clock cycle of latency between the rising edge of the BRAM clock and the address being incremented.

    Edit:

    Forgot to include the block which assigns the address bits and the shift register. Those probably make the code more understandable. Also, the relevant signals for the BRAM are:

    logic [20:0] timestampData - this is a module port which gets padded out to 32 bits and connected to the BRAM data input.

    logic [3:0] timestampAddr - this is a module port, and gets directly connected to the BRAM write address.

    logic timestampWriteClk - this is a module port, and gets ORed with the read clock before being connected to BRAM.

    /************************************* State Machine *************************************/
     
    // State transition block.
    always_ff @ (posedge clk or negedge rst_n) 
    begin
    	if (!rst_n)
    		currentState <= IDLE;
    	else
    		currentState <= nextState;
    end
     
    // Next state block.
    always_comb
    begin
    	case (currentState)
    	
    		// Waiting for startReadout.
    		IDLE:
    		begin
    			if ((startReadout == 1'b1) && (numSamplesReg > 0) && (numSamplesReg <= 12))
    				nextState = RESET_COUNTER;
    			else
    				nextState = IDLE;
    		end
    		
    		// Reset the readout counter.
    		RESET_COUNTER:
    		begin
    			nextState = RESET_SYNC;
    		end
    		
    		// De-assert counter reset.
    		RESET_SYNC:
    		begin
    			if (timestampAddr == '0)
    				nextState = SAMPLE_FIRST;
    			else
    				nextState = READOUT;
    		end
    		
    		// Sample the first bit in the ROIC buffer.
    		SAMPLE_FIRST:
    		begin
    			nextState = SAMPLE_SYNC;
    		end
    		
    		// De-assert the sample first bit signal.  
    		//This state avoids errors due to ORing dataClk and the sample first bit signal.
    		SAMPLE_SYNC:
    		begin
    			nextState = READOUT;
    		end
    		
    		// Read out the timestamp bits.
    		READOUT:
    		begin
    			if (readoutTimerDone == 1'b1)
    				nextState = WAIT_READOUT;
    			else
    				nextState = READOUT;
    		end
    		
    		// Wait until the last readout clock cycle has been completed.
    		WAIT_READOUT:
    		begin
    			if (readoutCounterDone == 1'b1)
    				nextState = WRITE_SYNC1;
    			else
    				nextState = WAIT_READOUT;
    		end
    		
    		// Wait one clock cycle to ensure fresh data for the memory write.
    		WRITE_SYNC1:
    		begin
    			nextState = WRITE_SYNC2;
    		end
    		
    		WRITE_SYNC2:
    		begin
    			nextState = WRITE_SYNC3;
    		end
    		
    		WRITE_SYNC3:
    		begin
    			nextState = WRITE_MEM;
    		end
    		
    		// Assert write clock to write the timestamp bits to memory.
    		WRITE_MEM:
    		begin
    			nextState = WRITE_MEM_2;
    		end
    		
    		// De-assert write clock.
    		WRITE_MEM_2:
    		begin
    			nextState = INC_ADDRESS;
    		end
    		
    		// Increment the memory adress for the next write.
    		INC_ADDRESS:
    		begin
    			nextState = CHECK_DONE;
    		end
    		
    		// Check if the readout cycle is complete.
    		CHECK_DONE:
    		begin
    			if (timestampAddr == numSamplesReg)
    				nextState = IDLE;
    			else
    				nextState = RESET_COUNTER;
    		end
    		
    		default:
    		begin
    			nextState = IDLE;
    		end
    	endcase
    end
     
    // Output logic block.
    always_comb
    begin
    	case (currentState)
    	
    		// Waiting to begin readout cycle.
    		IDLE:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b1;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b1;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Reset the readout counter to load the correct period value.
    		RESET_COUNTER:
    		begin
    			readoutCounterRst = 1'b1;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// De-assert counter reset.
    		RESET_SYNC:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Sample the first bit.  The ROIC makes the first bit of the timestamps
    		// available before the first read clock cycle.
    		SAMPLE_FIRST:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b1;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// De-assert the sample first bit signal.
    		SAMPLE_SYNC:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Read out the rest of the timestamp bits.
    		READOUT:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b1;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Wait for the last readout clock tick.
    		WAIT_READOUT:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Wait a clock cycle to ensure fresh data for the memory write.
    		WRITE_SYNC1:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		WRITE_SYNC2:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		WRITE_SYNC3:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Strobe the write clock to write the timestamp to memory.
    		WRITE_MEM:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b1;
    		end
    		
    		// De-assert the write clock.
    		WRITE_MEM_2:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Increment the memory address.  This corresponds directly to the timestamp number.
    		INC_ADDRESS:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b1;
    			timestampWriteClk = 1'b0;
    		end
    		
    		// Check to see if all of the requested timestamps have been read out of the ROIC.
    		CHECK_DONE:
    		begin
    			readoutCounterRst = 1'b0;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    			timestampWriteClk = 1'b0;
    		end
    		
    		default:
    		begin
    			readoutCounterRst = 1'b1;
    			dataRdy = 1'b0;
    			sampleFirstBit = 1'b0;
    			dataClkEnQ = 1'b0;
    			resetAddr = 1'b0;
    			incrementAddr = 1'b0;
    		end
    	endcase
    end
     
    // Timestamp data shift register.
    always_ff @ (negedge sampleClk or negedge rst_n)
    begin
    	if (!rst_n)
    		timestampData <= '0;
    	else
    	begin
    		timestampData <= {dataOut, timestampData[20:1]};
    	end
    end
     
    // Timestamp memory address block.
    always_ff @ (posedge incrementAddr or posedge resetAddr)
    begin
    	if (resetAddr)
    		timestampAddr <= '0;
    	else
    		timestampAddr <= timestampAddr + 1'b1;
    end