In the code fragment below is the definition for an asynchronous external SRAM interface for a product I built several years ago.
Code: Select all
// Image Memory (Dual Asynchronous 512k x 16 RAM) Interface
output [ 1:0] nRCE, // SRAM Chip Enable: 0 - PH #0, 1 - PH #1
output nROE, // SRAM Output Enable
output nRWE, // SRAM Write Enable
output [ 1:0] nRBE, // SRAM Byte (Write) Enable
output [18:0] RA, // SRAM Address
inout [15:0] RD, // SRAM Data Bus
I designed and implemented a module to arbitrate access to the external SRAM by several other modules. The following code fragment demonstrates how the connections from the top level ports are made to the internal SRAM interface control module.
Code: Select all
////////////////////////////////////////////////////////////////////////////////
//
// Instantiate External Memory Interface
//
MPHC_MemIF RAM (
.Rst(Rst),
.Clk(Clk),
.RCE(RCE),
.ROE(ROE),
.RWE(RWE),
.RBE(RBE),
.RA(RA),
.RD(RD),
.Rqst(Rqst),
.Grnt(Grnt),
.MemIF_WE(MemIF_WE),
.MemIF_Cmd(MemIF_Cmd),
.MemIF_AO(MemIF_AO),
.MemIF_DO(MemIF_DO),
.MemIF_FF(MemIF_FF),
.MemIF_EF(MemIF_EF),
.MemIF_DI_Valid(MemIF_DI_Valid),
.MemIF_DI(MemIF_DI),
.SM(MemIF_SM)
);
assign nRCE = ~RCE;
assign nROE = ~ROE;
assign nRWE = ~RWE;
assign nRBE = ~RBE;
The "wires" declared in the top level port list are simply connected to the SRAM interface module. My convention is to use only active high signals, i.e. no mixed logic, within the FPGA. This is why the active low outputs to the external SRAM are "assigned" through inverters at the top most level.
The following code fragment shows the definitions for the SRAM interface module ports:
Code: Select all
module MPHC_MemIF(
input Rst,
input Clk,
// MPHC_MemIF External SRAM Interface Signals
output reg [1:0] RCE,
output reg ROE,
output reg RWE,
output reg [ 1:0] RBE,
output reg [18:0] RA,
inout [15:0] RD,
// MPHC_MemIF Request/Grant Arbiter Interface
input [2:0] Rqst, // Memory Interface Request: (hold until done)
// 2 - PH[0],
// 1 - PH[1],
// 0 - External Controller (Computer)
output reg [2:0] Grnt, // Memory Interface Grant (Acknowledge)
// MPHC_MemIF Client Interface Signals
inout MemIF_WE, // Memory Interface Write Enable
inout MemIF_Cmd, // Memory Interface Cmd (0 - Read; 1 - Write)
inout [19:0] MemIF_AO, // Memory Interface Address FIFO
inout [15:0] MemIF_DO, // Memory Interface Data Out FIFO
output MemIF_FF, // Memory Interface FIFO Full Flag
output MemIF_EF, // Memory Interface FIFO Empty Flag
output reg [2:0] MemIF_DI_Valid, // Memory Interface Data Input Valid
output [15:0] MemIF_DI, // Memory Interface Data Input
// MPHC_MemIF State Machine
output reg [1:0] SM // Transaction processing state machine
);
The bidirectional definition of the SRAM Data bus, RD, is carried into the module. This is not necessary, but for this implementation it was acceptable. The FPGA does not actually have a bidirectional bus. In other words, only on the IO pin side of the IO block (IOB) does a "bidirectional" signal exist. Coming into the IOB from the FPGA logic fabric, and going into the FPGA logic fabric from the IOB, the signals are unidirectional like every where else in the FPGA.
Thus within the SRAM interface module, I have an SRAM Data Output bus and an SRAM Data Input bus. It is these busses that are connected to the "bidirectional" FPGA pins. The use of separate signal names allows the synthesizer to determine the input/output connections to the input and output buffers/registers of the IOB. Since I generally synthesize with the pack into IOB option on, the synthesizer and mapper attempt to place these I/O components into the IOB itself. (I check the reports to see that the number of IOB registers matches my expectations for the design. I also look for warnings that indicate that some signals have not been merged into IOBs as I expect.)
The following code fragment shows how the connections of the internal signals are made to the "bidirectional" bus so that the appropriate input and output registers/buffers are inferred and placed in the IOB:
Code: Select all
// Generate the RAM output buffer enable
assign RDO_OE = |SM & CurCmd;
// Drive the RAM Bidirectional Data Bus with RDO when RDO_OE asserted
assign RD = ((RDO_OE) ? RDO : 16'bZ);
// Capture the RAM Bidirectional Data Bus during read cycles using the delay
// line for generating MemIF_DI_Valid to the clients
assign CE_RDI = |(RdCycle[1]);
always @(posedge Clk)
begin
if(Rst)
RDI <= #1 0;
else if(CE_RDI)
RDI <= #1 RD;
end
The assign RD statement demonstrates that at least a tri-state output buffer is expected to be inferred. The RDO_OE signal controls the output enable of the tri-state output buffer in the IOB. The CE_RDI signal creates a clock enable with which to sample the asynchronous output data from the SRAM. Notice that the "inout" signal, RD, appears both on the left and on the right of the assignment statements. Because of this, the synthesizer infers the RDI register and "places" it in the IOB as a registered input.