fpga4fun.com - where FPGAs are fun.
Home
Welcome
Information


FPGA projects
Music box
Pong game
R/C servos
Text LCD module
Quadrature decoder
PWM and one-bit DAC
Debouncer
LED displays
Crossing clock domains
External contributions

FPGA interface projects
RS-232
JTAG
I2C
EPP
SPI
CNC steppers

FPGA advanced projects
Graphic LCD panel
Digital oscilloscope
10BASE-T interface
PCI interface
Spoc CPU core

Hands-on
A simple oscilloscope


FPGA introduction
What are FPGAs?
How FPGAs work
FPGA pins
Clocks and global lines
Download cables
Configuration
Learn more

FPGA software
Design software
Pin assignment
Design-entry/HDL
Simulation/HDL
Synthesis and P&R

FPGA electronic
SMD technology
Crystals and oscillators

HDL info
HDL tutorials
Verilog tips
VHDL tips

Quick-start guides
ISE
Quartus

Site
News
FPGA links
HDL tutorials
Forum


Digital oscilloscope - part 2

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 flipflops (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


>>> NEXT: Digital oscilloscope - part 3 >>>



This page was last updated on April 12 2005.