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;