Verilog Source
Disclaimer
I'm not an expert on PCI, nor am I an experienced Verilog user. My code probably looks nothing like what an experienced core designer would create. It's likely that the PCI spec is violated (and with a slow CPLD, almost certain). No PAR#ity is generated. I welcome constructive criticism. I'd be very interested to know what back-side interfaces people graft on to this to drive PICs or other devices.
Space Savings
Properly implementing burst reads and writes was a fun exercise, but a lot of space could probably be saved by using STOP# to terminate transactions after a single data phase. Certain assumptions can be made for single cycle reads and writes and the incrementer for address can be eliminated.
Any place there are fewer different bits in the hardwired register space probably helps cut down on the number of product terms. Making the SUBSYSTEM_ID and VENDOR 0x0000, for example, or making them overlap the DEVICE_ID and VENDOR_ID significantly would help.
Entire states could be removed or simplified. If you don't need writes all that's required is to ignore CFGWRITE (going through the motions with DEVSEL# and TRDY#), and if there's a memory window the same applies to MEMWRITE. If you want to cheat and control the device with config cycles (which is discouraged by the spec, and may be very slow and/or inconvenient in the driver) you could eliminate memory window support and MEMREAD/MEMWRITE altogether.
pci.v
module pci(reset,clk,frame,irdy,trdy,devsel,idsel,ad,cbe,par,stop,inta,led_out);
input reset;
input clk;
input frame;
input irdy;
output trdy;
output devsel;
input idsel;
inout [31:0] ad;
input [3:0] cbe;
inout par;
output stop;
output inta;
output [3:0] led_out;
parameter DEVICE_ID = 16'h9500;
parameter VENDOR_ID = 16'h106d; // Sequent!
parameter DEVICE_CLASS = 24'hFF0000; // Misc
parameter DEVICE_REV = 8'h01;
parameter SUBSYSTEM_ID = 16'h0001; // Card identifier
parameter SUBSYSTEM_VENDOR_ID = 16'hBEBE; // Card identifier
parameter DEVSEL_TIMING = 2'b00; // Fast!
reg [2:0] state;
reg [31:0] data;
reg [1:0] enable;
parameter EN_NONE = 0;
parameter EN_RD = 1;
parameter EN_WR = 2;
parameter EN_TR = 3;
reg memen; // respond to baseaddr?
reg [7:0] baseaddr;
reg [5:0] address;
parameter ST_IDLE = 3'b000;
parameter ST_BUSY = 3'b010;
parameter ST_MEMREAD = 3'b100;
parameter ST_MEMWRITE = 3'b101;
parameter ST_CFGREAD = 3'b110;
parameter ST_CFGWRITE = 3'b111;
parameter MEMREAD = 4'b0110;
parameter MEMWRITE = 4'b0111;
parameter CFGREAD = 4'b1010;
parameter CFGWRITE = 4'b1011;
`define LED
`ifdef LED
reg [3:0] led;
`endif
`undef STATE_DEBUG_LED
`ifdef STATE_DEBUG_LED
assign led_out = ~state;
`else
`ifdef LED
assign led_out = ~led; // board is wired for active low LEDs
`endif
`endif
assign ad = (enable == EN_RD) ? data : 32'bZ;
assign trdy = (enable == EN_NONE) ? 'bZ : (enable == EN_TR ? 1 : 0);
assign par = (enable == EN_RD) ? 0 : 'bZ;
reg devsel;
assign stop = 1'bZ;
assign inta = 1'bZ;
wire cfg_hit = ((cbe == CFGREAD || cbe == CFGWRITE) && idsel && ad[1:0] == 2'b00);
wire addr_hit = ((cbe == MEMREAD || cbe == MEMWRITE) && memen && ad[31:12] == {12'b0, baseaddr});
wire hit = cfg_hit | addr_hit;
always @(posedge clk)
begin
if (~reset) begin
state <= ST_IDLE;
enable <= EN_NONE;
baseaddr <= 0;
devsel <= 'bZ;
memen <= 0;
`ifdef LED
led <= 0;
`endif
end
else begin
case (state)
ST_IDLE: begin
enable <= EN_NONE;
devsel <= 'bZ;
if (~frame) begin
address <= ad[7:2];
if (hit) begin
state <= {1'b1, cbe[3], cbe[0]};
devsel <= 0;
// pipeline the write enable
if (cbe[0])
enable <= EN_WR;
end
else begin
state <= ST_BUSY;
enable <= EN_NONE;
end
end
end
ST_BUSY: begin
devsel <= 'bZ;
enable <= EN_NONE;
if (frame)
state <= ST_IDLE;
end
ST_CFGREAD: begin
enable <= EN_RD;
if (~irdy || trdy) begin
case (address)
0: data <= { DEVICE_ID, VENDOR_ID };
1: data <= { 5'b0, DEVSEL_TIMING, 9'b0, 14'b0, memen, 1'b0};
2: data <= { DEVICE_CLASS, DEVICE_REV };
4: data <= { 12'b0, baseaddr, 8'b0, 4'b0010 }; // baseaddr + request mem < 1Mbyte
11: data <= {SUBSYSTEM_ID, SUBSYSTEM_VENDOR_ID };
16: data <= { 24'b0, baseaddr };
default: data <= 'h00000000;
endcase
address <= address + 1;
end
if (frame && ~irdy && ~trdy) begin
devsel <= 1;
state <= ST_IDLE;
enable <= EN_TR;
end
end
ST_CFGWRITE: begin
enable <= EN_WR;
if (~irdy) begin
case (address)
4: baseaddr <= ad[19:12]; // XXX examine cbe
1: memen <= ad[1];
default: ;
endcase
address <= address + 1;
if (frame) begin
devsel <= 1;
state <= ST_IDLE;
enable <= EN_TR;
end
end
end
ST_MEMREAD: begin
enable <= EN_RD;
if (~irdy || trdy) begin
case (address)
`ifdef LED
0: data <= { 28'b0, led };
`endif
default: data <= 'h00000000;
endcase
address <= address + 1;
end
if (frame && ~irdy && ~trdy) begin
devsel <= 1;
state <= ST_IDLE;
enable <= EN_TR;
end
end
ST_MEMWRITE: begin
enable <= EN_WR;
if (~irdy) begin
case (address)
`ifdef LED
0: led <= ad[3:0];
`endif
default: ;
endcase
address <= address + 1;
if (frame) begin
devsel <= 1;
state <= ST_IDLE;
enable <= EN_TR;
end
end
end
endcase
end
end
endmodule
Testbench
The testbench is left as an exercise for the reader.
pci.ucf
Here's my constraints file. Unless you happen to lay your board out identically to mine yours will be significantly different. My board has the CPLD on the "B" side with pin 1 toward the fingers, roughly centered. I made CLK a wire and routed everything else to avoid crossings that would require extra vias. GCK and GSR need to map to CLK and RESET# but other than that the other signals can go anywhere:
NET "clk" TNM_NET = "clk"; TIMESPEC "TS_clk" = PERIOD "clk" 30 ns HIGH 50 %; #PACE: Start of Constraints generated by PACE #PACE: Start of PACE I/O Pin Assignments NET "ad<0>" LOC = "P32"; NET "ad<10>" LOC = "P13"; NET "ad<11>" LOC = "P18"; NET "ad<12>" LOC = "P11"; NET "ad<13>" LOC = "P17"; NET "ad<14>" LOC = "P10"; NET "ad<15>" LOC = "P61"; NET "ad<16>" LOC = "P67"; NET "ad<17>" LOC = "P3"; NET "ad<18>" LOC = "P68"; NET "ad<19>" LOC = "P2"; NET "ad<1>" LOC = "P25"; NET "ad<20>" LOC = "P69"; NET "ad<21>" LOC = "P1"; NET "ad<22>" LOC = "P70"; NET "ad<23>" LOC = "P84"; NET "ad<24>" LOC = "P72"; NET "ad<25>" LOC = "P82"; NET "ad<26>" LOC = "P77"; NET "ad<27>" LOC = "P81"; NET "ad<28>" LOC = "P76"; NET "ad<29>" LOC = "P80"; NET "ad<2>" LOC = "P31"; NET "ad<30>" LOC = "P75"; NET "ad<31>" LOC = "P79"; NET "ad<3>" LOC = "P24"; NET "ad<4>" LOC = "P26"; NET "ad<5>" LOC = "P23"; NET "ad<6>" LOC = "P21"; NET "ad<7>" LOC = "P15"; NET "ad<8>" LOC = "P14"; NET "ad<9>" LOC = "P19"; NET "cbe<0>" LOC = "P20"; NET "cbe<1>" LOC = "P9"; NET "cbe<2>" LOC = "P4"; NET "cbe<3>" LOC = "P83"; NET "clk" LOC = "P12"; NET "devsel" LOC = "P6"; NET "frame" LOC = "P66"; NET "idsel" LOC = "P71"; NET "inta" LOC = "P58"; NET "irdy" LOC = "P5"; NET "led_out<0>" LOC = "P36"; NET "led_out<1>" LOC = "P35"; NET "led_out<2>" LOC = "P34"; NET "led_out<3>" LOC = "P33"; NET "par" LOC = "P62"; NET "reset" LOC = "P74"; NET "stop" LOC = "P63"; NET "trdy" LOC = "P65"; #PACE: Start of PACE Area Constraints #PACE: Start of PACE Prohibit Constraints #PACE: End of Constraints generated by PACE OFFSET = OUT 23 ns AFTER "clk" ; TIMESPEC "TS_P2P" = FROM "PADS" TO "PADS" 23 ns;