Created
December 14, 2017 05:56
-
-
Save adamgreig/a19bab78fc279552a29afd7346dffff8 to your computer and use it in GitHub Desktop.
AXI3 read-only slave in migen for Cyclone V SoC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from migen import Module, Signal, FSM, If, NextState, NextValue | |
class AXI3SlaveReader(Module): | |
""" | |
AXI3 read-only slave. | |
Input signals are from the AXI3 master AR and R ports. | |
`regfile` is an Array which is indexed to respond to reads. | |
Does not support ARLOCK, ARCACHE, or ARPROT at all. | |
Only supports ARBURST=FIXED or INCR, but not WRAP. | |
Only supports ARSIZE=0b010, i.e., 32bit reads. | |
Does support burst reads. | |
Responds SLVERR if invalid ARBURST or ARSIZE given, or if the | |
read address is beyond 4*len(regfile). | |
AXI3 outputs are: | |
`self.arready`, `self.rid`, `self.rdata`, `self.rresp`, | |
`self.rlast`, and `self.rvalid`. | |
Connect them to the AXI3 master. | |
""" | |
def __init__(self, arid, araddr, arlen, arsize, arburst, arvalid, rready, | |
regfile): | |
# AXI3 Slave Reader outputs | |
self.arready = Signal() | |
self.rid = Signal(12) | |
self.rdata = Signal(32) | |
self.rresp = Signal(2) | |
self.rlast = Signal() | |
self.rvalid = Signal() | |
# Store the control parameters for the active transaction. | |
self.readid = Signal(arid.nbits) | |
self.readaddr = Signal(araddr.nbits) | |
self.burstlen = Signal(4) | |
self.burstsize = Signal(3) | |
self.bursttype = Signal(2) | |
self.beatcount = Signal(4) | |
self.response = Signal(2) | |
self.submodules.fsm = FSM(reset_state="READY") | |
# In READY, we assert ARREADY to indicate we can immediately receive | |
# a new address to read from, and continuously register the input | |
# AR parameters. When ARVALID becomes asserted, we transition to | |
# the PREPARE state. | |
self.fsm.act( | |
"READY", | |
# Assert AREADY, don't assert RVALID | |
self.arready.eq(1), | |
self.rvalid.eq(0), | |
# Capture all input parameters | |
NextValue(self.readid, arid), | |
NextValue(self.readaddr, araddr), | |
NextValue(self.burstlen, arlen), | |
NextValue(self.burstsize, arsize), | |
NextValue(self.bursttype, arburst), | |
# Initialise beatcount to 0 | |
NextValue(self.beatcount, 0), | |
# Begin processing on ARVALID | |
If(arvalid, NextState("PREPARE")), | |
) | |
# In PREPARE, we load the required data without yet asserting RVALID, | |
# and also determine the RRESP response and incremenet the read | |
# address and beat count. | |
self.fsm.act( | |
"PREPARE", | |
self.arready.eq(0), | |
self.rvalid.eq(0), | |
# Output the current RID and RDATA | |
self.rid.eq(self.readid), | |
self.rdata.eq(regfile[self.readaddr >> 2]), | |
# Return an error for burst size not 4 bytes, or WRAP burst type, | |
# or an invalid read address. | |
If((self.burstsize != 0b010) | |
| (self.bursttype == 0b10) | |
| (self.readaddr > 4*len(regfile)), | |
NextValue(self.response, 0b10)).Else( | |
NextValue(self.response, 0b00)), | |
self.rresp.eq(self.response), | |
# Increment the read address if INCR mode is selected. | |
If((self.bursttype == 0b01) & (self.beatcount != 0), | |
NextValue(self.readaddr, self.readaddr + 4)), | |
# Incremenet the beat count | |
NextValue(self.beatcount, self.beatcount + 1), | |
# Output whether this is the final beat of the burst | |
self.rlast.eq(self.beatcount == self.burstlen + 1), | |
NextState("WAIT"), | |
) | |
# In WAIT, we assert RVALID, and then wait for RREADY to be asserted | |
# before either returning to PREPARE (if there are more beats in this | |
# burst) or READY (if not). | |
self.fsm.act( | |
"WAIT", | |
# Assert RVALID | |
self.rvalid.eq(1), | |
# Continue outputting RID, RDATA, RRESP, RLAST set in PREPARE | |
self.rid.eq(self.readid), | |
self.rdata.eq(regfile[self.readaddr >> 2]), | |
self.rresp.eq(self.response), | |
self.rlast.eq(self.beatcount == self.burstlen + 1), | |
# Wait for RREADY before advancing | |
If(rready, | |
If(self.rlast, NextState("READY")).Else(NextState("PREPARE"))) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/mman.h> | |
#define H2F_LW_BASE (0xFF200000) | |
#define H2F_LW_SIZE (0x00200000) | |
int main() { | |
void *h2f_lw, *reg0, *reg1, *reg2, *reg3; | |
int fd; | |
// Open /dev/mem so we can mmap parts of physical memory into our space | |
fd = open("/dev/mem", O_RDWR|O_SYNC); | |
if(fd == -1) { | |
printf("Error opening /dev/mem\n"); | |
return 1; | |
} | |
// Memory map the FPGA manager into our address space | |
h2f_lw = mmap(NULL, H2F_LW_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, | |
fd, H2F_LW_BASE); | |
if(h2f_lw == MAP_FAILED) { | |
printf("Error performing mmap\n"); | |
close(fd); | |
return 1; | |
} | |
// Get pointers to useful registers | |
reg0 = h2f_lw + 0; | |
reg1 = h2f_lw + 4; | |
reg2 = h2f_lw + 8; | |
reg3 = h2f_lw + 12; | |
printf("Reg0 | Reg1 | Reg2 | Reg3\n"); | |
while(true) { | |
printf("\r"); | |
printf("0x%08X | ", *(uint32_t*)reg0); | |
printf("0x%08X | ", *(uint32_t*)reg1); | |
printf("0x%08X | ", *(uint32_t*)reg2); | |
printf("0x%08X", *(uint32_t*)reg3); | |
fflush(stdout); | |
usleep(100000); | |
} | |
munmap(h2f_lw, H2F_LW_SIZE); | |
close(fd); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from migen import Module, Signal, Instance, Cat, Array, ClockDomain, If | |
from migen.build.platforms import de0nanosoc | |
from axi3 import AXI3SlaveReader | |
class Registers(Module): | |
""" | |
Simple register file. | |
reg0 maps to the user LEDs. | |
reg1 maps to the user keys. | |
reg2 is a counter. | |
reg3 is always 0xDEADBEEF. | |
""" | |
def __init__(self, platform): | |
leds = Cat([plat.request("user_led", x) for x in range(8)]) | |
keys = Cat([plat.request("key", x) for x in range(2)]) | |
switches = Cat([plat.request("sw", x) for x in range(4)]) | |
counter = Signal(32) | |
self.sync += counter.eq(counter + 1) | |
sw = [Signal(4) for _ in range(6)] | |
self.sync += [sw[i].eq(1-keys[i]) for i in range(2)] | |
self.sync += [sw[i].eq(switches[i-2]) for i in range(2, 6)] | |
reg0 = Signal(32) | |
reg1 = Signal(32) | |
reg2 = Signal(32) | |
reg3 = Signal(32) | |
self.sync += leds.eq(reg0[:8]) | |
self.sync += reg1.eq(Cat(sw)) | |
self.sync += reg2.eq(counter) | |
self.sync += reg3.eq(0xDEADBEEF) | |
self.regfile = Array([reg0, reg1, reg2, reg3]) | |
class Top(Module): | |
def __init__(self, platform): | |
# Wire up the outputs from the HPS2FPGA lightweight bridge | |
arid = Signal(12) | |
araddr = Signal(21) | |
arlen = Signal(4) | |
arsize = Signal(3) | |
arburst = Signal(2) | |
arlock = Signal(2) | |
arcache = Signal(4) | |
arprot = Signal(3) | |
arvalid = Signal() | |
rready = Signal() | |
# Set up the clock | |
clk50 = plat.request("clk1_50") | |
self.clock_domains.sys = ClockDomain("sys") | |
self.comb += self.sys.clk.eq(clk50) | |
# Create a simple registers file | |
self.submodules.registers = Registers(platform) | |
# Create the AXI3 read-only slave | |
axi3sr = AXI3SlaveReader(arid, araddr, arlen, arsize, arburst, arvalid, | |
rready, self.registers.regfile) | |
self.submodules += axi3sr | |
# Instantiate the HPS2FPGA lightweight bridge. | |
# We connect its outputs to the Signals above, and its inputs to | |
# the outputs of the AXI3SlaveReader. | |
self.specials += Instance( | |
"cyclonev_hps_interface_hps2fpga_light_weight", | |
o_arid=arid, o_araddr=araddr, o_arlen=arlen, o_arsize=arsize, | |
o_arburst=arburst, o_arlock=arlock, o_arcache=arcache, | |
o_arprot=arprot, o_arvalid=arvalid, o_rready=rready, | |
i_arready=axi3sr.arready, i_rid=axi3sr.rid, i_rdata=axi3sr.rdata, | |
i_rresp=axi3sr.rresp, i_rlast=axi3sr.rlast, i_rvalid=axi3sr.rvalid, | |
i_clk=self.sys.clk, | |
) | |
# We'll stick some debug information on GPI for easy checking | |
gpi = Signal(32) | |
self.sync += If(arvalid, gpi.eq(Cat( | |
arid, araddr[:4], arlen, arsize, arburst, arlock, arcache))) | |
self.specials += Instance( | |
"cyclonev_hps_interface_mpu_general_purpose", i_gp_in=gpi) | |
if __name__ == "__main__": | |
import sys | |
plat = de0nanosoc.Platform() | |
top = Top(plat) | |
plat.add_platform_command( | |
"set_global_assignment -name NUM_PARALLEL_PROCESSORS ALL") | |
if len(sys.argv) >= 2 and sys.argv[1] == "build": | |
plat.build(top) | |
if len(sys.argv) >= 3 and sys.argv[2] == "load": | |
prog = plat.create_programmer() | |
prog.load_bitstream("build/top.sof") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment