refactor to use common bus layout across all modules

This commit is contained in:
Fischer Moseley 2024-01-07 18:17:09 -08:00
parent 61d6479805
commit ee4a3026af
13 changed files with 119 additions and 211 deletions

View File

@ -31,7 +31,7 @@ cores:
my_logic_analyzer:
type: logic_analyzer
sample_depth: 4096
trigger_loc: 1000
trigger_location: 1000
probes:
larry: 1

View File

@ -14,7 +14,7 @@ cores:
my_logic_analyzer:
type: logic_analyzer
sample_depth: 4096
trigger_loc: 1000
trigger_location: 1000
probes:
larry: 1

View File

@ -88,15 +88,8 @@ class IOCore(Elaboratable):
def define_signals(self):
# Bus Ports
self.addr_i = Signal(16)
self.data_i = Signal(16)
self.rw_i = Signal()
self.valid_i = Signal()
self.addr_o = Signal(16)
self.data_o = Signal(16)
self.rw_o = Signal()
self.valid_o = Signal()
self.bus_i = Signal(InternalBus())
self.bus_o = Signal(InternalBus())
# Input Probes (and buffers)
if "inputs" in self.config:
@ -187,10 +180,7 @@ class IOCore(Elaboratable):
m = Module()
# Shuffle bus transactions along
m.d.sync += self.addr_o.eq(self.addr_i)
m.d.sync += self.data_o.eq(self.data_i)
m.d.sync += self.rw_o.eq(self.rw_i)
m.d.sync += self.valid_o.eq(self.valid_i)
m.d.sync += self.bus_o.eq(self.bus_i)
# Update buffers from probes
with m.If(self.strobe):
@ -209,17 +199,17 @@ class IOCore(Elaboratable):
m.d.sync += output_probe.eq(output_probe_buf)
# Handle register reads and writes
with m.If((self.addr_i >= self.base_addr)):
with m.If((self.addr_o <= self.max_addr)):
with m.If((self.bus_i.addr >= self.base_addr)):
with m.If((self.bus_o.addr <= self.max_addr)):
for entry in self.mmap.values():
for addr, signal in zip(entry["addrs"], entry["signals"]):
with m.If(self.rw_i):
with m.If(self.addr_i == addr):
m.d.sync += signal.eq(self.data_i)
with m.If(self.bus_i.rw):
with m.If(self.bus_i.addr == addr):
m.d.sync += signal.eq(self.bus_i.data)
with m.Else():
with m.If(self.addr_i == addr):
m.d.sync += self.data_o.eq(signal)
with m.If(self.bus_i.addr == addr):
m.d.sync += self.bus_o.data.eq(signal)
return m

View File

@ -36,18 +36,11 @@ class LogicAnalyzerCore(Elaboratable):
self.config = config
self.check_config(config)
# Bus Input
self.addr_i = Signal(16)
self.data_i = Signal(16)
self.rw_i = Signal(1)
self.valid_i = Signal(1)
# Bus Output
self.addr_o = Signal(16)
self.data_o = Signal(16)
self.rw_o = Signal(1)
self.valid_o = Signal(1)
# Bus Input/Output
self.bus_i = Signal(InternalBus())
self.bus_o = Signal(InternalBus())
# Probes
self.probes = [
Signal(width, name=name) for name, width in self.config["probes"].items()
]
@ -177,22 +170,11 @@ class LogicAnalyzerCore(Elaboratable):
# Wire bus connections between internal modules
m.d.comb += [
# Bus Connections
fsm.addr_i.eq(self.addr_i),
fsm.data_i.eq(self.data_i),
fsm.rw_i.eq(self.rw_i),
fsm.valid_i.eq(self.valid_i),
trig_blk.addr_i.eq(fsm.addr_o),
trig_blk.data_i.eq(fsm.data_o),
trig_blk.rw_i.eq(fsm.rw_o),
trig_blk.valid_i.eq(fsm.valid_o),
sample_mem.addr_i.eq(trig_blk.addr_o),
sample_mem.data_i.eq(trig_blk.data_o),
sample_mem.rw_i.eq(trig_blk.rw_o),
sample_mem.valid_i.eq(trig_blk.valid_o),
self.addr_o.eq(sample_mem.addr_o),
self.data_o.eq(sample_mem.data_o),
self.rw_o.eq(sample_mem.rw_o),
self.valid_o.eq(sample_mem.valid_o),
fsm.bus_i.eq(self.bus_i),
trig_blk.bus_i.eq(fsm.bus_o),
sample_mem.bus_i.eq(trig_blk.bus_o),
self.bus_o.eq(sample_mem.bus_o),
# Non-bus Connections
fsm.trigger.eq(trig_blk.trig),
sample_mem.user_addr.eq(fsm.r.write_pointer),

View File

@ -1,11 +1,10 @@
from amaranth import *
from math import ceil, log2
from ..io_core import IOCore
from ..utils import *
class LogicAnalyzerFSM(Elaboratable):
""" """
def __init__(self, config, base_addr, interface):
self.config = config
self.states = {
@ -37,24 +36,14 @@ class LogicAnalyzerFSM(Elaboratable):
self.r = IOCore(register_config, base_addr, interface)
# Bus Input
self.addr_i = self.r.addr_i
self.data_i = self.r.data_i
self.rw_i = self.r.rw_i
self.valid_i = self.r.valid_i
# Bus Output
self.addr_o = self.r.addr_o
self.data_o = self.r.data_o
self.rw_o = self.r.rw_o
self.valid_o = self.r.valid_o
# Bus Input/Output
self.bus_i = self.r.bus_i
self.bus_o = self.r.bus_o
def get_max_addr(self):
return self.r.get_max_addr()
def increment_mod_sample_depth(self, m, signal):
# m.d.sync += signal.eq((signal + 1) % self.config["sample_depth"])
with m.If(signal == self.config["sample_depth"] - 1):
m.d.sync += signal.eq(0)

View File

@ -1,10 +1,9 @@
from amaranth import *
from ..io_core import IOCore
from ..utils import *
class LogicAnalyzerTriggerBlock(Elaboratable):
""" """
def __init__(self, probes, base_addr, interface):
# Instantiate a bunch of trigger blocks
self.probes = probes
@ -18,17 +17,9 @@ class LogicAnalyzerTriggerBlock(Elaboratable):
self.r = IOCore({"outputs": outputs}, base_addr, interface)
# Bus Input
self.addr_i = self.r.addr_i
self.data_i = self.r.data_i
self.rw_i = self.r.rw_i
self.valid_i = self.r.valid_i
# Bus Output
self.addr_o = self.r.addr_o
self.data_o = self.r.data_o
self.rw_o = self.r.rw_o
self.valid_o = self.r.valid_o
# Bus Input/Output
self.bus_i = Signal(InternalBus())
self.bus_o = Signal(InternalBus())
# Global trigger. High if any probe is triggered.
self.trig = Signal(1)

View File

@ -136,28 +136,15 @@ class Manta(Elaboratable):
core_instances = list(self.cores.values())
first_core = core_instances[0]
last_core = core_instances[-1]
m.d.comb += [
first_core.addr_i.eq(self.interface.addr_o),
first_core.data_i.eq(self.interface.data_o),
first_core.rw_i.eq(self.interface.rw_o),
first_core.valid_i.eq(self.interface.valid_o),
self.interface.addr_i.eq(last_core.addr_o),
self.interface.data_i.eq(last_core.data_o),
self.interface.rw_i.eq(last_core.rw_o),
self.interface.valid_i.eq(last_core.valid_o),
]
first_core.bus_i.eq(self.interface.bus_o)
self.interface.bus_i.eq(self.interface.bus_o)
# Connect output of ith core to input of (i+1)th core
for i in range(len(core_instances) - 1):
ith_core = core_instances[i]
i_plus_oneth_core = core_instances[i + 1]
m.d.comb += [
i_plus_oneth_core.addr_i.eq(ith_core.addr_o),
i_plus_oneth_core.data_i.eq(ith_core.data_o),
i_plus_oneth_core.rw_i.eq(ith_core.rw_o),
i_plus_oneth_core.valid_i.eq(ith_core.valid_o),
]
i_plus_oneth_core.bus_i.eq(ith_core.bus_o)
return m

View File

@ -47,23 +47,10 @@ class ReadOnlyMemoryCore(Elaboratable):
raise ValueError("Width of memory core must be positive. ")
def define_signals(self):
# Bus Input
self.addr_i = Signal(16)
self.data_i = Signal(16)
self.rw_i = Signal(1)
self.valid_i = Signal(1)
# Bus Pipelining
self.addr_pipe = [Signal(16) for _ in range(3)]
self.data_pipe = [Signal(16) for _ in range(3)]
self.rw_pipe = [Signal(1) for _ in range(3)]
self.valid_pipe = [Signal(1) for _ in range(3)]
# Bus Output
self.addr_o = Signal(16, reset=0)
self.data_o = Signal(16, reset=0)
self.rw_o = Signal(1, reset=0)
self.valid_o = Signal(1, reset=0)
# Bus Input/Pipelining/Output
self.bus_i = Signal(InternalBus())
self.bus_pipe = [Signal(InternalBus()) for _ in range(3)]
self.bus_o = Signal(InternalBus())
# User Port
self.user_addr = Signal(range(self.depth))
@ -72,21 +59,12 @@ class ReadOnlyMemoryCore(Elaboratable):
def pipeline_bus(self, m):
# Pipelining
m.d.sync += self.addr_pipe[0].eq(self.addr_i)
m.d.sync += self.data_pipe[0].eq(self.data_i)
m.d.sync += self.rw_pipe[0].eq(self.rw_i)
m.d.sync += self.valid_pipe[0].eq(self.valid_i)
m.d.sync += self.bus_pipe[0].eq(self.bus_i)
for i in range(1, 3):
m.d.sync += self.addr_pipe[i].eq(self.addr_pipe[i - 1])
m.d.sync += self.data_pipe[i].eq(self.data_pipe[i - 1])
m.d.sync += self.rw_pipe[i].eq(self.rw_pipe[i - 1])
m.d.sync += self.valid_pipe[i].eq(self.valid_pipe[i - 1])
m.d.sync += self.bus_pipe[i].eq(self.bus_pipe[i-1])
m.d.sync += self.addr_o.eq(self.addr_pipe[2])
m.d.sync += self.data_o.eq(self.data_pipe[2])
m.d.sync += self.rw_o.eq(self.rw_pipe[2])
m.d.sync += self.valid_o.eq(self.valid_pipe[2])
m.d.sync += self.bus_o.eq(self.bus_pipe[2])
def define_mems(self):
# ok there's three cases:
@ -122,21 +100,21 @@ class ReadOnlyMemoryCore(Elaboratable):
# Throw BRAM operations into the front of the pipeline
with m.If(
(self.valid_i)
& (~self.rw_i)
& (self.addr_i >= start_addr)
& (self.addr_i <= stop_addr)
(self.bus_i.valid)
& (~self.bus_i.rw)
& (self.bus_i.addr >= start_addr)
& (self.bus_i.addr <= stop_addr)
):
m.d.sync += read_port.addr.eq(self.addr_i - start_addr)
m.d.sync += read_port.addr.eq(self.bus_i.addr - start_addr)
# Pull BRAM reads from the back of the pipeline
with m.If(
(self.valid_pipe[2])
& (~self.rw_pipe[2])
& (self.addr_pipe[2] >= start_addr)
& (self.addr_pipe[2] <= stop_addr)
(self.bus_pipe[2].valid)
& (~self.bus_pipe[2].rw)
& (self.bus_pipe[2].addr >= start_addr)
& (self.bus_pipe[2].addr <= stop_addr)
):
m.d.sync += self.data_o.eq(read_port.data)
m.d.sync += self.bus_o.data.eq(read_port.data)
def handle_write_ports(self, m):
# These are given to the user

View File

@ -222,15 +222,8 @@ class UARTInterface(Elaboratable):
self.rx = Signal()
self.tx = Signal()
self.addr_o = Signal(16)
self.data_o = Signal(16)
self.rw_o = Signal()
self.valid_o = Signal()
self.addr_i = Signal(16)
self.data_i = Signal(16)
self.rw_i = Signal()
self.valid_i = Signal()
self.bus_i = Signal(InternalBus())
self.bus_o = Signal(InternalBus())
def elaborate(self, platform):
# fancy submoduling and such goes in here
@ -246,14 +239,9 @@ class UARTInterface(Elaboratable):
uart_rx.rx.eq(self.rx),
bridge_rx.data_i.eq(uart_rx.data_o),
bridge_rx.valid_i.eq(uart_rx.valid_o),
self.data_o.eq(bridge_rx.data_o),
self.addr_o.eq(bridge_rx.addr_o),
self.rw_o.eq(bridge_rx.rw_o),
self.valid_o.eq(bridge_rx.valid_o),
self.bus_o.data.eq(bridge_rx.bus_o),
# Internal Bus -> UART TX
bridge_tx.data_i.eq(self.data_i),
bridge_tx.rw_i.eq(self.rw_i),
bridge_tx.valid_i.eq(self.valid_i),
bridge_tx.bus_i.eq(self.bus_i),
uart_tx.data_i.eq(bridge_tx.data_o),
uart_tx.start_i.eq(bridge_tx.start_o),
bridge_tx.done_i.eq(uart_tx.done_o),
@ -326,10 +314,7 @@ class RecieveBridge(Elaboratable):
self.data_i = Signal(8)
self.valid_i = Signal()
self.addr_o = Signal(16, reset=0)
self.data_o = Signal(16, reset=0)
self.rw_o = Signal(1, reset=0)
self.valid_o = Signal(1, reset=0)
self.bus_o = Signal(InternalBus())
# State Machine
self.IDLE_STATE = 0
@ -369,30 +354,27 @@ class RecieveBridge(Elaboratable):
with m.If(
(self.state == self.READ_STATE) & (self.byte_num == 4) & (self.is_eol)
):
m.d.comb += self.addr_o.eq(
m.d.comb += self.bus_o.addr.eq(
Cat(self.buffer[3], self.buffer[2], self.buffer[1], self.buffer[0])
)
m.d.comb += self.data_o.eq(0)
m.d.comb += self.valid_o.eq(1)
m.d.comb += self.rw_o.eq(0)
m.d.comb += self.bus_o.data.eq(0)
m.d.comb += self.bus_o.valid.eq(1)
m.d.comb += self.bus_o.rw.eq(0)
with m.Elif(
(self.state == self.WRITE_STATE) & (self.byte_num == 8) & (self.is_eol)
):
m.d.comb += self.addr_o.eq(
m.d.comb += self.bus_o.addr.eq(
Cat(self.buffer[3], self.buffer[2], self.buffer[1], self.buffer[0])
)
m.d.comb += self.data_o.eq(
m.d.comb += self.bus_o.data.eq(
Cat(self.buffer[7], self.buffer[6], self.buffer[5], self.buffer[4])
)
m.d.comb += self.valid_o.eq(1)
m.d.comb += self.rw_o.eq(1)
m.d.comb += self.bus_o.valid.eq(1)
m.d.comb += self.bus_o.rw.eq(1)
with m.Else():
m.d.comb += self.addr_o.eq(0)
m.d.comb += self.data_o.eq(0)
m.d.comb += self.rw_o.eq(0)
m.d.comb += self.valid_o.eq(0)
m.d.comb += self.bus_o.eq(0)
def drive_fsm(self, m):
with m.If(self.valid_i):
@ -500,9 +482,7 @@ class UARTTransmitter(Elaboratable):
class TransmitBridge(Elaboratable):
def __init__(self):
# Top-Level Ports
self.data_i = Signal(16)
self.rw_i = Signal()
self.valid_i = Signal()
self.bus_i = Signal(InternalBus())
self.data_o = Signal(8, reset=0)
self.start_o = Signal(1)
@ -521,9 +501,9 @@ class TransmitBridge(Elaboratable):
m.d.comb += self.start_o.eq(self.busy)
with m.If(~self.busy):
with m.If((self.valid_i) & (~self.rw_i)):
with m.If((self.bus_i.valid) & (~self.bus_i.rw)):
m.d.sync += self.busy.eq(1)
m.d.sync += self.buffer.eq(self.data_i)
m.d.sync += self.buffer.eq(self.bus_i.data)
with m.Else():
# uart_tx is transmitting a byte:
@ -535,8 +515,8 @@ class TransmitBridge(Elaboratable):
m.d.sync += self.count.eq(0)
# Go back to idle, or transmit next message
with m.If((self.valid_i) & (~self.rw_i)):
m.d.sync += self.buffer.eq(self.data_i)
with m.If((self.bus_i.valid) & (~self.bus_i.rw)):
m.d.sync += self.buffer.eq(self.bus_i.data)
with m.Else():
m.d.sync += self.busy.eq(0)

View File

@ -1,7 +1,16 @@
from amaranth.sim import Simulator
from amaranth.lib import data, enum
from math import ceil
import os
class InternalBus(data.StructLayout):
def __init__(self):
super().__init__({
"addr": 16,
"data": 16,
"rw": 1,
"valid": 1
})
def words_to_value(data):
"""
@ -74,20 +83,20 @@ def verify_register(module, addr, expected_data):
"""
# place read transaction on the bus
yield module.addr_i.eq(addr)
yield module.data_i.eq(0)
yield module.rw_i.eq(0)
yield module.valid_i.eq(1)
yield module.bus_i.addr.eq(addr)
yield module.bus_i.data.eq(0)
yield module.bus_i.rw.eq(0)
yield module.bus_i.valid.eq(1)
yield
yield module.addr_i.eq(0)
yield module.valid_i.eq(0)
yield module.bus_i.addr.eq(0)
yield module.bus_i.valid.eq(0)
# wait for output to be valid
while not (yield module.valid_o):
while not (yield module.bus_o.valid):
yield
# compare returned value with expected
data = yield (module.data_o)
data = yield (module.bus_o.data)
if data != expected_data:
raise ValueError(f"Read from {addr} yielded {data} instead of {expected_data}")
@ -98,12 +107,12 @@ def write_register(module, addr, data):
at `addr`.
"""
yield module.addr_i.eq(addr)
yield module.data_i.eq(data)
yield module.rw_i.eq(1)
yield module.valid_i.eq(1)
yield module.bus_i.addr.eq(addr)
yield module.bus_i.data.eq(data)
yield module.bus_i.rw.eq(1)
yield module.bus_i.valid.eq(1)
yield
yield module.valid_i.eq(0)
yield module.bus_i.valid.eq(0)
yield
@ -142,3 +151,5 @@ def ice40_tools_installed():
return True
return False

View File

@ -17,15 +17,15 @@ def verify_read_decoding(bytes, addr):
for i, byte in enumerate(bytes):
yield bridge_rx.data_i.eq(byte)
if (yield bridge_rx.valid_o) and (i > 0):
if (yield bridge_rx.bus_o.valid) and (i > 0):
valid_asserted = True
if (yield bridge_rx.addr_o) != addr:
if (yield bridge_rx.bus_o.addr) != addr:
raise ValueError("wrong addr!")
if (yield bridge_rx.rw_o) != 0:
if (yield bridge_rx.bus_o.rw) != 0:
raise ValueError("wrong rw!")
if (yield bridge_rx.data_o) != 0:
if (yield bridge_rx.bus_o.data) != 0:
raise ValueError("wrong data!")
yield
@ -33,7 +33,7 @@ def verify_read_decoding(bytes, addr):
yield bridge_rx.valid_i.eq(0)
yield bridge_rx.data_i.eq(0)
if not valid_asserted and not (yield bridge_rx.valid_o):
if not valid_asserted and not (yield bridge_rx.bus_o.valid):
raise ValueError("Bridge failed to output valid message.")
@ -48,15 +48,15 @@ def verify_write_decoding(bytes, addr, data):
for i, byte in enumerate(bytes):
yield bridge_rx.data_i.eq(byte)
if (yield bridge_rx.valid_o) and (i > 0):
if (yield bridge_rx.bus_o.valid) and (i > 0):
valid_asserted = True
if (yield bridge_rx.addr_o) != addr:
if (yield bridge_rx.bus_o.addr) != addr:
raise ValueError("wrong addr!")
if (yield bridge_rx.rw_o) != 1:
if (yield bridge_rx.bus_o.rw) != 1:
raise ValueError("wrong rw!")
if (yield bridge_rx.data_o) != data:
if (yield bridge_rx.bus_o.data) != data:
raise ValueError("wrong data!")
yield
@ -64,7 +64,7 @@ def verify_write_decoding(bytes, addr, data):
yield bridge_rx.valid_i.eq(0)
yield bridge_rx.data_i.eq(0)
if not valid_asserted and not (yield bridge_rx.valid_o):
if not valid_asserted and not (yield bridge_rx.bus_o.valid):
raise ValueError("Bridge failed to output valid message.")
@ -78,7 +78,7 @@ def verify_bad_bytes(bytes):
for byte in bytes:
yield bridge_rx.data_i.eq(byte)
if (yield bridge_rx.valid_o):
if (yield bridge_rx.bus_o.valid):
raise ValueError("Bridge decoded invalid message.")
yield

View File

@ -17,16 +17,16 @@ def verify_encoding(data, bytes):
"""
# Place a read response on the internal bus
yield bridge_tx.data_i.eq(data)
yield bridge_tx.valid_i.eq(1)
yield bridge_tx.rw_i.eq(0)
yield bridge_tx.bus_i.data.eq(data)
yield bridge_tx.bus_i.valid.eq(1)
yield bridge_tx.bus_i.rw.eq(0)
yield bridge_tx.done_i.eq(1)
yield
yield bridge_tx.data_i.eq(0)
yield bridge_tx.valid_i.eq(0)
yield bridge_tx.rw_i.eq(0)
yield bridge_tx.bus_i.data.eq(0)
yield bridge_tx.bus_i.valid.eq(0)
yield bridge_tx.bus_i.rw.eq(0)
yield

View File

@ -6,7 +6,7 @@ from random import sample
config = {
"type": "logic_analyzer",
"sample_depth": 1024,
"trigger_loc": 512,
"trigger_location": 512,
"probes": {"larry": 1, "curly": 3, "moe": 9},
"triggers": ["moe RISING"],
}
@ -16,19 +16,19 @@ la = LogicAnalyzerCore(config, base_addr=0, interface=None)
def print_data_at_addr(addr):
# place read transaction on the bus
yield la.addr_i.eq(addr)
yield la.data_i.eq(0)
yield la.rw_i.eq(0)
yield la.valid_i.eq(1)
yield la.bus_i.addr.eq(addr)
yield la.bus_i.data.eq(0)
yield la.bus_i.rw.eq(0)
yield la.bus_i.valid.eq(1)
yield
yield la.addr_i.eq(0)
yield la.valid_i.eq(0)
yield la.bus_i.addr.eq(0)
yield la.bus_i.valid.eq(0)
# wait for output to be valid
while not (yield la.valid_o):
while not (yield la.bus_o.valid):
yield
print(f"addr: {hex(addr)} data: {hex((yield la.data_o))}")
print(f"addr: {hex(addr)} data: {hex((yield la.bus_o.data))}")
def set_fsm_register(name, data):