Fpga verilog dual access

designdigital-logicfpgaverilog

I need to write to a register from 2 sources.. in this case, a pci host
and a microcontroller. The 2 will never access the register at the same time (basically once the PCI is done , it hands it over the other host, which will then have exclusive access until it's finished).

Since the clock domains are different, I can't just have multiple
processes writing to the same register..

always @(posedge pciclk or negedge nrst)
   if pci_wr & pci_addr == 0
        cntl_reg = pci_data

always @(posedge mcclk or negedge nrst)
   if mc_wr & mc_addr == 0
        cntl_reg = mc_data

So how is this typically done? Not sure if this a fundamental architectural question, or just a syntax question on how to write the sensitivity list.

Best Answer

The practical FPGA register (a set of D flip-flops) only has one clock pin so it serves exactly one clock domain. Keep this low-level hardware in mind when writing HDL code.

When a signal crosses from one clock domain into another, it's common to use a pipeline of three registers, with the first stage in the source clock domain and the second and third registers in the destination clock domain. This causes some predictable latency, but avoids race conditions and undefined behavior.

// From Xilinx ISE 14.1 Language Templates
// Verilog | Synthesis Constructs | Coding Examples | Misc | Asynchronous Input Synchronization
module async_input_sync(
    input clk,
    (* TIG="TRUE", IOB="FALSE" *) input async_in,
    output reg sync_out
    );
(* ASYNC_REG="TRUE", SHREG_EXTRACT="NO", HBLKNM="sync_reg" *) reg [1:0] sreg;
always @(posedge clk) begin
    sync_out <= sreg[1];
    sreg <= {sreg[0], async_in};
end
endmodule

Synchronize pci_data, pci_wr, and pci_addr into the mcclk clock domain. For example:

wire [xxx] pci_mc_data; // pci_ signal in pciclk clock domain, source mc_data
wire pci_mc_wr; // pci_ signal in pciclk clock domain, source mc_data
wire [xxx] pci_mc_addr; // pci_ signal in pciclk clock domain, source mc_data
async_input_sync(pciclk, mc_data[xxx], pci_mc_data[xx]);
async_input_sync(pciclk, mc_wr, pci_mc_wr);
async_input_sync(pciclk, mc_addr[xxx], pci_mc_addr[xx]);

Synchronize mc_data, mc_wr, and mc_addr into the pciclk clock domain. For example:

wire [xxx] mc_pci_data; // mc_ signal in mcclk clock domain, source pci_data
async_input_sync(mcclk, pci_data[xxx], mc_pci_data[xx]);

You will have one register in the pciclk clock domain, and another register in the mcclk clock domain. Both registers have the same data after the signal propagates across the clock domain. For example, the register on the pci side might look like this:

// All of these signals are in the pciclk clock domain
always @(posedge pciclk) begin
    if (pci_wr & pci_addr == 0) begin
        pci_cntl_reg <= pci_data;
    end
    if (pci_mc_wr & pci_mc_addr == 0) begin
        pci_cntl_reg <= pci_mc_data;
    end
end

Also note the use of non-blocking assignment <= to help the synthesis tool recognize that you're requesting a set of D flip flops. The verilog register keyword is just a data type, it doesn't necessarily always result in synthesizing a D flip-flop.