Skip to content

Instantly share code, notes, and snippets.

@adamgreig
Created December 14, 2017 05:56
Show Gist options
  • Save adamgreig/a19bab78fc279552a29afd7346dffff8 to your computer and use it in GitHub Desktop.
Save adamgreig/a19bab78fc279552a29afd7346dffff8 to your computer and use it in GitHub Desktop.
AXI3 read-only slave in migen for Cyclone V SoC
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")))
)
#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;
}
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