Compare commits

...

5 Commits

6 changed files with 342 additions and 36 deletions

View File

@ -0,0 +1,26 @@
{ pkgs ? import <nixpkgs> {}
, hx ? import ../default.nix { inherit pkgs; }}:
let
symbiflowInput = pkgs.runCommand "simplesoc-symbiflow-input" {
buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.heavycomps hx.minerva])) hx.yosys ];
}
''
mkdir $out
python ${./simplesoc_ecp5.py} > $out/top.il
cat > $out/top.lpf << EOF
LOCATE COMP "clk100" SITE "P3";
IOBUF PORT "clk100" IO_TYPE=LVDS;
LOCATE COMP "serial_tx" SITE "A11";
IOBUF PORT "serial_tx" IO_TYPE=LVCMOS33;
EOF
echo -n "--um-45k --package CABGA381" > $out/device
'';
in
hx.symbiflow.buildBitstream {
name = "simplesoc-bitstream";
src = symbiflowInput;
}

View File

@ -0,0 +1,74 @@
from nmigen import *
from nmigen.back import rtlil
from heavycomps import uart, wishbone
from minerva.core import Minerva
class SimpleWishboneSerial(Elaboratable):
def __init__(self, tx, sys_clk_freq, baudrate=115200):
self.tx = tx
self.bus = wishbone.Interface()
self.ftw = round(2**32*baudrate/sys_clk_freq)
def elaborate(self, platform):
m = Module()
m.submodules.tx = tx = uart.RS232TX(self.ftw)
m.d.comb += [
tx.stb.eq(self.bus.cyc & self.bus.stb & self.bus.we),
tx.data.eq(self.bus.dat_w),
self.bus.ack.eq(tx.ack),
self.tx.eq(tx.tx)
]
return m
class Top(Elaboratable):
def __init__(self):
self.clk100 = Signal()
self.serial_tx = Signal()
def elaborate(self, platform):
m = Module()
cd_sync = ClockDomain(reset_less=True)
m.domains += cd_sync
m.d.comb += cd_sync.clk.eq(self.clk100)
m.submodules.cpu = cpu = Minerva(with_icache=False, with_dcache=False)
m.submodules.ram = ram = wishbone.SRAM(Memory(32, 1024))
m.submodules.uart = uart = SimpleWishboneSerial(self.serial_tx, 100e6)
m.submodules.con = con = wishbone.InterconnectShared(
[cpu.ibus, cpu.dbus],
[
(lambda a: ~a[20], ram.bus),
(lambda a: a[20], uart.bus)
], register=True)
# work around https://github.com/m-labs/nmigen/issues/30
m.d.comb += [
cpu.external_interrupt.eq(0),
cpu.timer_interrupt.eq(0),
cpu.fetch.ibus.dat_w.eq(0),
cpu.fetch.ibus.sel.eq(0b1111),
cpu.fetch.ibus.we.eq(0),
cpu.fetch.ibus.cti.eq(0),
cpu.fetch.ibus.bte.eq(0),
cpu.loadstore.dbus.cti.eq(0),
cpu.loadstore.dbus.bte.eq(0),
ram.bus.err.eq(0),
uart.bus.err.eq(0),
uart.bus.dat_r.eq(0)
]
return m
def main():
top = Top()
output = rtlil.convert(Fragment.get(top, None),
ports=(top.clk100, top.serial_tx))
print(output)
if __name__ == "__main__":
main()

View File

@ -1,20 +0,0 @@
from nmigen import *
class RoundRobin(Elaboratable):
def __init__(self, n):
self.n = n
self.request = Signal(n)
self.grant = Signal(max=n)
def elaborate(self, platform):
m = Module()
with m.Switch(self.grant):
for i in range(self.n):
with m.Case(i):
with m.If(~self.request[i]):
for j in reversed(range(i+1, i+self.n)):
t = j % self.n
with m.If(self.request[t]):
m.d.sync += self.grant.eq(t)
return m

View File

@ -2,6 +2,9 @@ from nmigen import *
from nmigen.lib.cdc import MultiReg
__all__ = ["RS232RX", "RS232TX"]
class RS232RX(Elaboratable):
def __init__(self, tuning_word):
self.rx = Signal()
@ -23,12 +26,16 @@ class RS232RX(Elaboratable):
rx_busy = Signal()
rx_done = self.stb
rx_data = self.data
m.d.sync += rx_done.eq(0)
m.d.sync += rx_r.eq(rx)
m.d.sync += [
rx_done.eq(0),
rx_r.eq(rx)
]
with m.If(~rx_busy):
with m.If(~rx & rx_r): # look for start bit
m.d.sync += rx_busy.eq(1)
m.d.sync += rx_bitcount.eq(0)
m.d.sync += [
rx_busy.eq(1),
rx_bitcount.eq(0)
]
with m.Else():
with m.If(uart_clk_rxen):
m.d.sync += rx_bitcount.eq(rx_bitcount + 1)
@ -38,8 +45,10 @@ class RS232RX(Elaboratable):
with m.Elif(rx_bitcount == 9):
m.d.sync += rx_busy.eq(0)
with m.If(rx): # verify stop bit
m.d.sync += rx_data.eq(rx_reg)
m.d.sync += rx_done.eq(1)
m.d.sync += [
rx_data.eq(rx_reg),
rx_done.eq(1)
]
with m.Else():
m.d.sync += rx_reg.eq(Cat(rx_reg[1:], rx))
with m.If(rx_busy):
@ -75,23 +84,29 @@ class RS232TX(Elaboratable):
tx_reg = Signal(8)
tx_bitcount = Signal(4)
tx_busy = Signal()
m.d.sync += self.ack.eq(0),
m.d.sync += self.ack.eq(0)
with m.If(self.stb & ~tx_busy & ~self.ack):
m.d.sync += tx_reg.eq(self.data)
m.d.sync += tx_bitcount.eq(0)
m.d.sync += tx_busy.eq(1)
m.d.sync += self.tx.eq(0)
m.d.sync += [
tx_reg.eq(self.data),
tx_bitcount.eq(0),
tx_busy.eq(1),
self.tx.eq(0)
]
with m.Elif(uart_clk_txen & tx_busy):
m.d.sync += tx_bitcount.eq(tx_bitcount + 1)
with m.If(tx_bitcount == 8):
m.d.sync += self.tx.eq(1)
with m.Elif(tx_bitcount == 9):
m.d.sync += self.tx.eq(1)
m.d.sync += tx_busy.eq(0)
m.d.sync += self.ack.eq(1),
m.d.sync += [
self.tx.eq(1),
tx_busy.eq(0),
self.ack.eq(1)
]
with m.Else():
m.d.sync += self.tx.eq(tx_reg[0])
m.d.sync += tx_reg.eq(Cat(tx_reg[1:], 0))
m.d.sync += [
self.tx.eq(tx_reg[0]),
tx_reg.eq(Cat(tx_reg[1:], 0))
]
with m.If(tx_busy):
m.d.sync += Cat(phase_accumulator_tx, uart_clk_txen).eq(phase_accumulator_tx + self.tuning_word)
with m.Else():

View File

@ -0,0 +1,210 @@
from enum import Enum
from functools import reduce
from operator import or_
from nmigen import *
from nmigen.hdl.rec import *
__all__ = ["Cycle", "Interface", "Arbiter", "Decoder", "InterconnectShared"]
class Cycle(Enum):
CLASSIC = 0
CONSTANT = 1
INCREMENT = 2
END = 7
wishbone_layout = [
("adr", 30, DIR_FANOUT),
("dat_w", 32, DIR_FANOUT),
("dat_r", 32, DIR_FANIN),
("sel", 4, DIR_FANOUT),
("cyc", 1, DIR_FANOUT),
("stb", 1, DIR_FANOUT),
("ack", 1, DIR_FANIN),
("we", 1, DIR_FANOUT),
("cti", 3, DIR_FANOUT),
("bte", 2, DIR_FANOUT),
("err", 1, DIR_FANIN)
]
class Interface(Record):
def __init__(self):
Record.__init__(self, wishbone_layout)
def _do_transaction(self):
yield self.cyc.eq(1)
yield self.stb.eq(1)
yield
while not (yield self.ack):
yield
yield self.cyc.eq(0)
yield self.stb.eq(0)
def write(self, adr, dat, sel=None):
if sel is None:
sel = 2**len(self.sel) - 1
yield self.adr.eq(adr)
yield self.dat_w.eq(dat)
yield self.sel.eq(sel)
yield self.we.eq(1)
yield from self._do_transaction()
def read(self, adr):
yield self.adr.eq(adr)
yield self.we.eq(0)
yield from self._do_transaction()
return (yield self.dat_r)
class SRAM(Elaboratable):
def __init__(self, mem, read_only=False, bus=None):
self.mem = mem
self.read_only = read_only
if bus is None:
bus = Interface()
self.bus = bus
def elaborate(self, platform):
m = Module()
if self.mem.width > len(self.bus.dat_r):
raise NotImplementedError
# read
m.submodules.rdport = rdport = self.mem.read_port()
m.d.comb += [
rdport.addr.eq(self.bus.adr[:len(rdport.addr)]),
self.bus.dat_r.eq(rdport.data)
]
# write
if not self.read_only:
m.submodules.wrport = wrport = self.mem.write_port(granularity=8)
m.d.comb += [
wrport.addr.eq(self.bus.adr[:len(rdport.addr)]),
wrport.data.eq(self.bus.dat_w)
]
for i in range(4):
m.d.comb += wrport.en[i].eq(self.bus.cyc & self.bus.stb & self.bus.we & self.bus.sel[i])
# generate ack
m.d.sync += self.bus.ack.eq(0)
with m.If(self.bus.cyc & self.bus.stb & ~self.bus.ack):
m.d.sync += self.bus.ack.eq(1)
return m
class RoundRobin(Elaboratable):
def __init__(self, n):
self.n = n
self.request = Signal(n)
self.grant = Signal(max=n)
def elaborate(self, platform):
m = Module()
with m.Switch(self.grant):
for i in range(self.n):
with m.Case(i):
with m.If(~self.request[i]):
for j in reversed(range(i+1, i+self.n)):
t = j % self.n
with m.If(self.request[t]):
m.d.sync += self.grant.eq(t)
return m
class Arbiter(Elaboratable):
def __init__(self, masters, target):
self.masters = masters
self.target = target
def elaborate(self, platform):
m = Module()
m.submodules.rr = rr = RoundRobin(len(self.masters))
# mux master->target signals
for name, size, direction in wishbone_layout:
if direction == DIR_FANOUT:
choices = Array(getattr(m, name) for m in self.masters)
m.d.comb += getattr(self.target, name).eq(choices[rr.grant])
# connect target->master signals
for name, size, direction in wishbone_layout:
if direction == DIR_FANIN:
source = getattr(self.target, name)
for i, master in enumerate(self.masters):
dest = getattr(master, name)
if name == "ack" or name == "err":
m.d.comb += dest.eq(source & (rr.grant == i))
else:
m.d.comb += dest.eq(source)
# connect bus requests to round-robin selector
reqs = [m.cyc & ~m.ack for m in self.masters]
m.d.comb += rr.request.eq(Cat(*reqs))
return m
class Decoder(Elaboratable):
def __init__(self, master, targets, register=False):
self.master = master
self.targets = targets
self.register = register
def elaborate(self, platform):
m = Module()
nt = len(self.targets)
target_sel = Signal(nt)
target_sel_r = Signal(nt)
# decode target addresses
for i, (fun, bus) in enumerate(self.targets):
m.d.comb += target_sel[i].eq(fun(self.master.adr))
if self.register:
m.d.sync += target_sel_r.eq(target_sel)
else:
m.d.comb += target_sel_r.eq(target_sel)
# connect master->targets signals except cyc
for target in self.targets:
for name, size, direction in wishbone_layout:
if direction == DIR_FANOUT and name != "cyc":
m.d.comb += getattr(target[1], name).eq(getattr(self.master, name))
# combine cyc with target selection signals
for i, target in enumerate(self.targets):
m.d.comb += target[1].cyc.eq(self.master.cyc & target_sel[i])
# generate master ack (resp. err) by ORing all target acks (resp. errs)
m.d.comb += [
self.master.ack.eq(reduce(or_, [target[1].ack for target in self.targets])),
self.master.err.eq(reduce(or_, [target[1].err for target in self.targets]))
]
# mux (1-hot) target data return
masked = [Repl(target_sel_r[i], len(self.master.dat_r)) & self.targets[i][1].dat_r for i in range(nt)]
m.d.comb += self.master.dat_r.eq(reduce(or_, masked))
return m
class InterconnectShared(Module):
def __init__(self, masters, targets, register=False):
self.masters = masters
self.targets = targets
self.register = register
def elaborate(self, platform):
m = Module()
shared = Interface()
m.submodules.arbiter = Arbiter(self.masters, shared)
m.submodules.decoder = Decoder(shared, self.targets, self.register)
return m

View File

@ -4,6 +4,7 @@ let
jobs = derivations // {
helloworld_ecp5 = import ./examples/helloworld_ecp5.nix { inherit pkgs; };
helloworld_kintex7 = import ./examples/helloworld_kintex7.nix { inherit pkgs; };
simplesoc_ecp5 = import ./examples/simplesoc_ecp5.nix { inherit pkgs; };
};
in
builtins.mapAttrs (name: value: pkgs.lib.hydraJob value) jobs