fpga4fun.comwhere FPGAs are fun

Digital oscilloscope - dual-port RAM

The FIFO allowed us to get a working design very quickly.
But for our simple oscilloscope, it is overkill.

We need a mechanism to store data from one clock domain (100MHz) and read it in another (25MHz). A simple dual-port RAM does that.

The disadvantage of not using a FIFO is that all the synchonization between the 2 clock domains (that the FIFO was doing for us) has to be done "manually" now.

Trigger

The "FIFO based" oscilloscope design didn't have an explicit trigger mechanism.
Let's change that. Now the oscilloscope will be triggered everytime it receives a character from the serial port. Of course, that's still not a very useful design, but we'll improved on that later.

We receive data from the serial port:
wire [7:0] RxD_data;
async_receiver async_rxd(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));

Everytime a new character is received, "RxD_data_ready" goes high for one clock. We use that to trigger the oscilloscope.

Synchronization

We need to transfer this "RxD_data_ready went high" information from the "clk" (25MHz) domain to the "clk_flash" (100MHz) domain.

First, a signal "startAcquisition" goes high when a character is received.
reg startAcquisition;
wire AcquisitionStarted;

always @(posedge clk)
if(~startAcquisition)
  startAcquisition <= RxD_data_ready;
else
if(AcquisitionStarted)
  startAcquisition <= 0;

We use synchronizers in the form of 2 flip-flops (to transfer this "startAcquisition" to the other clock domain).
reg startAcquisition1; always @(posedge clk_flash) startAcquisition1 <= startAcquisition;
reg startAcquisition2; always @(posedge clk_flash) startAcquisition2 <= startAcquisition1;

Finally, once the other clock domain "sees" the signal, it "replies" (using another synchronizer "Acquiring").
reg Acquiring;
always @(posedge clk_flash)
if(~Acquiring)
  Acquiring <= startAcquisition2;  // start acquiring?
else
if(&wraddress)  // done acquiring?
  Acquiring <= 0;

reg Acquiring1; always @(posedge clk) Acquiring1 <= Acquiring;
reg Acquiring2; always @(posedge clk) Acquiring2 <= Acquiring1;
assign AcquisitionStarted = Acquiring2;

The reply resets the original signal.

Dual-port RAM

Now that the trigger is available, we need a dual-port RAM to store the data.
Notice how each side of the RAM uses a different clock.
ram512 ram_flash(
  .data(data_flash_reg), .wraddress(wraddress), .wren(Acquiring), .wrclock(clk_flash),
  .q(ram_output), .rdaddress(rdaddress), .rden(rden), .rdclock(clk)
);

The ram address buses are created easily using binary counters.
First the write address:
reg [8:0] wraddress;
always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;

and the read address:
reg [8:0] rdaddress;
reg Sending;
wire TxD_busy;

always @(posedge clk)
if(~Sending)
  Sending <= AcquisitionStarted;
else
if(~TxD_busy)
begin
  rdaddress <= rdaddress + 1;
  if(&rdaddress) Sending <= 0;
end

Notice how each counter uses a different clock.

Finally we send data to the PC:
wire TxD_start = ~TxD_busy & Sending;
wire rden = TxD_start;

wire [7:0] ram_output;
async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(ram_output));

The complete design

module oscillo(clk, RxD, TxD, clk_flash, data_flash);
input clk;
input RxD;
output TxD;

input clk_flash;
input [7:0] data_flash;

///////////////////////////////////////////////////////////////////
wire [7:0] RxD_data;
async_receiver async_rxd(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));

reg startAcquisition;
wire AcquisitionStarted;

always @(posedge clk)
if(~startAcquisition)
  startAcquisition <= RxD_data_ready;
else
if(AcquisitionStarted)
  startAcquisition <= 0;

reg startAcquisition1; always @(posedge clk_flash) startAcquisition1 <= startAcquisition ;
reg startAcquisition2; always @(posedge clk_flash) startAcquisition2 <= startAcquisition1;

reg Acquiring;
always @(posedge clk_flash)
if(~Acquiring)
  Acquiring <= startAcquisition2;
else
if(&wraddress)
  Acquiring <= 0;

reg [8:0] wraddress;
always @(posedge clk_flash) if(Acquiring) wraddress <= wraddress + 1;

reg Acquiring1; always @(posedge clk) Acquiring1 <= Acquiring;
reg Acquiring2; always @(posedge clk) Acquiring2 <= Acquiring1;
assign AcquisitionStarted = Acquiring2;

reg [8:0] rdaddress;
reg Sending;
wire TxD_busy;

always @(posedge clk)
if(~Sending)
  Sending <= AcquisitionStarted;
else
if(~TxD_busy)
begin
  rdaddress <= rdaddress + 1;
  if(&rdaddress) Sending <= 0;
end

wire TxD_start = ~TxD_busy & Sending;
wire rden = TxD_start;

wire [7:0] ram_output;
async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(ram_output));

///////////////////////////////////////////////////////////////////
reg [7:0] data_flash_reg; always @(posedge clk_flash) data_flash_reg <= data_flash;

ram512 ram_flash(
  .data(data_flash_reg), .wraddress(wraddress), .wren(Acquiring), .wrclock(clk_flash),
  .q(ram_output), .rdaddress(rdaddress), .rden(rden), .rdclock(clk)
);

endmodule