Let's try to control LEDs from the PCI Express bus.
Xilinx's "Endpoint Block Plus" core allows us to work at the transaction layer level, so it's just going to take us a few lines of code.
Instead of providing data on a 32-bit bus, "Endpoint Block Plus" uses a 64-bit bus (so we get twice as much data at each clock cycle).
That's not a problem and a simple state-machine will handle simple memory reads & writes.
// we use signals from Xilinx's "Endpoint Block Plus" // first we declare that we are always ready to get data assign trn_rdst_rdy_n = 1'b0; // then we create a state machine that triggers when we get a PCI Express memory read or write reg RXstate; reg [63:0] RXrd; always @(posedge clk) case(RXstate) // we are going to handle simple memory reads & writes // we know that with the "Endpoint Block Plus" core, such simple transactions always happens // using two cycles so we just need a two-states state machine // first, we wait for the beginning of a memory transaction with up to 32-bit data (i.e. with length=1) 1'b0: if(~trn_rsrc_rdy_n && ~trn_rsof_n && trn_rd[61:56]==6'b0_00000 && trn_rd[41:32]==10'h001) begin RXstate <= 1'b1; RXrd <= trn_rd; end // then the second state waits for the end of the transaction 1'b1: if(~trn_rsrc_rdy_n) RXstate <= 1'b0; endcase
Now we are ready to update the LEDs.
wire [31:0] RXaddr = trn_rd[63:32]; // memory address (read or write) (valid during the second state of the state machine) wire [31:0] RXdata = trn_rd[31:0]; // memory data (for a write) (valid during the second state of the state machine) wire RXrdwr = RXrd[62]; // 0 for a read, 1 for a write wire RXRead = ~trn_rsrc_rdy_n & RXstate & ~RXrdwr; // true when a read is happening wire RXwrite = ~trn_rsrc_rdy_n & RXstate & RXrdwr; // true when a write is happening // update two LEDs using the two LSBs from the data written reg [1:0] LEDs; always @(posedge clk) if(RXwrite) LEDs <= RXdata[1:0];
For a memory write, that's all there is to it. For a memory read, you need to create a response packet with the data to return. Generating an interrupt is also very easy - just assert a signal called "cfg_interrupt_n".
Want more? Check Dragon-E's startup-kit for a more complete example, and Xilinx's UG341 Endpoint Block Plus specification documentation for a description of all the signals.