refactor to separate verilog and python
This commit is contained in:
parent
621ca48ad3
commit
20957e5ba7
|
|
@ -5,10 +5,28 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install yosys
|
||||
|
||||
- name: Install Manta from Source
|
||||
run: |
|
||||
pip install setuptools --upgrade
|
||||
|
||||
# omitting the following commmand causes the version of setuptools
|
||||
# used by python to get confused, and it doesn't detect the name
|
||||
# or version of the package from pyproject.toml - so the following
|
||||
# workaround is used:
|
||||
# https://github.com/pypa/setuptools/issues/3269#issuecomment-1254507377
|
||||
export DEB_PYTHON_INSTALL_LAYOUT=deb_system
|
||||
|
||||
python3 -m pip install .
|
||||
|
||||
- name: Install Icestorm Tools
|
||||
run: |
|
||||
wget --no-verbose https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2023-02-23/oss-cad-suite-linux-x64-20230223.tgz
|
||||
tar -xzf oss-cad-suite-linux-x64-20230223.tgz
|
||||
tar -xzf oss-cad-suite-linux-x64-20230223.tgz
|
||||
export PATH="$(pwd)/oss-cad-suite/bin/:$PATH"
|
||||
cd examples/icestick/counter
|
||||
./build.sh
|
||||
|
||||
- name: Build io_core
|
||||
run: make icestick_io_core
|
||||
|
||||
- name: Build lut_ram
|
||||
run: make icestick_lut_ram
|
||||
|
|
@ -26,13 +26,11 @@ jobs:
|
|||
|
||||
python3 -m pip install .
|
||||
|
||||
- name: Generate Core
|
||||
run: manta gen examples/nexys_a7/counter/manta.yaml examples/nexys_a7/counter/src/debug.sv
|
||||
- name: Build io_core
|
||||
run: make nexys_a7_io_core
|
||||
|
||||
- name: Build Verilog
|
||||
working-directory: examples/nexys_a7/counter
|
||||
run: mkdir obj && python3 lab-bc.py
|
||||
- name: Build logic_analyzer
|
||||
run: make nexys_a7_logic_analyzer
|
||||
|
||||
- name: Print build.log
|
||||
working-directory: examples/nexys_a7/counter
|
||||
run: cat obj/build.log
|
||||
- name: Build lut_ram
|
||||
run: make nexys_a7_lut_ram
|
||||
|
|
@ -5,6 +5,94 @@ from datetime import datetime
|
|||
|
||||
version = "0.0.0"
|
||||
|
||||
class VerilogManipulator:
|
||||
def __init__(self, filepath=None):
|
||||
if filepath is not None:
|
||||
self.hdl = pkgutil.get_data(__name__, filepath).decode()
|
||||
|
||||
else:
|
||||
self.hdl = None
|
||||
|
||||
def sub(self, replace, find):
|
||||
# sometimes we have integer inputs, want to accomodate
|
||||
if isinstance(replace, str):
|
||||
replace_str = replace
|
||||
|
||||
elif isinstance(replace, int):
|
||||
replace_str = str(replace)
|
||||
|
||||
else:
|
||||
raise ValueError("Only string and integer arguments supported.")
|
||||
|
||||
|
||||
# if the string being subbed in isn't multiline, just
|
||||
# find-and-replace like normal:
|
||||
if "\n" not in replace_str:
|
||||
self.hdl = self.hdl.replace(find, replace_str)
|
||||
|
||||
# if the string being substituted in is multiline,
|
||||
# make sure the replace text gets put at the same
|
||||
# indentation level by adding whitespace to left
|
||||
# of the line.
|
||||
else:
|
||||
for line in self.hdl.split("\n"):
|
||||
if find in line:
|
||||
# get whitespace that's on the left side of the line
|
||||
whitespace = line.rstrip().replace(line.lstrip(), "")
|
||||
|
||||
# add it to every line, except the first
|
||||
replace_as_lines = replace_str.split("\n")
|
||||
replace_with_whitespace = f"\n{whitespace}".join(replace_as_lines)
|
||||
|
||||
# replace the first occurance in the HDL with it
|
||||
self.hdl = self.hdl.replace(find, replace_with_whitespace, 1)
|
||||
|
||||
def get_hdl(self):
|
||||
return self.hdl
|
||||
|
||||
def net_dec(self, nets, net_type, trailing_comma = False):
|
||||
"""Takes a dictonary of nets in the format {probe: width}, and generates
|
||||
the net declarations that would go in a Verilog module definition.
|
||||
|
||||
For example, calling net_dec({foo : 1, bar : 4}, "input wire") would produce:
|
||||
|
||||
input wire foo,
|
||||
input [3:0] wire bar
|
||||
|
||||
Which you'd then slap into your module declaration, along with all the other
|
||||
inputs and outputs the module needs."""
|
||||
|
||||
dec = []
|
||||
for name, width in nets.items():
|
||||
if width == 1:
|
||||
dec.append(f"{net_type} {name}")
|
||||
|
||||
else:
|
||||
dec.append(f"{net_type} [{width-1}:0] {name}")
|
||||
|
||||
dec = ",\n".join(dec)
|
||||
dec = dec + "," if trailing_comma else dec
|
||||
return dec
|
||||
|
||||
def net_conn(self, nets, trailing_comma = False):
|
||||
"""Takes a dictionary of nets in the format {probe: width}, and generates
|
||||
the net connections that would go in the Verilog module instantiation.
|
||||
|
||||
For example, calling net_conn({foo: 1, bar: 4}) would produce:
|
||||
|
||||
.foo(foo),
|
||||
.bar(bar)
|
||||
|
||||
Which you'd then slap into your module instantiation, along with all the other
|
||||
module inputs and outputs that get connected elsewhere."""
|
||||
|
||||
|
||||
conn = [f".{name}({name})" for name in nets]
|
||||
conn = ",\n".join(conn)
|
||||
conn = conn + "," if trailing_comma else conn
|
||||
|
||||
return conn
|
||||
|
||||
class UARTInterface:
|
||||
def __init__(self, config):
|
||||
# Obtain port. Try to automatically detect port if "auto" is specified
|
||||
|
|
@ -112,63 +200,24 @@ class UARTInterface:
|
|||
return ["input wire rx", "output reg tx"]
|
||||
|
||||
def rx_hdl_def(self):
|
||||
uart_rx_def = pkgutil.get_data(__name__, "rx_uart.v").decode()
|
||||
bridge_rx_def = pkgutil.get_data(__name__, "bridge_rx.v").decode()
|
||||
uart_rx_def = VerilogManipulator("rx_uart.v").get_hdl()
|
||||
bridge_rx_def = VerilogManipulator("bridge_rx.v").get_hdl()
|
||||
return uart_rx_def + '\n' + bridge_rx_def
|
||||
|
||||
def tx_hdl_def(self):
|
||||
uart_tx_def = pkgutil.get_data(__name__, "uart_tx.v").decode()
|
||||
bridge_tx_def = pkgutil.get_data(__name__, "bridge_tx.v").decode()
|
||||
uart_tx_def = VerilogManipulator("uart_tx.v").get_hdl()
|
||||
bridge_tx_def = VerilogManipulator("bridge_tx.v").get_hdl()
|
||||
return bridge_tx_def + '\n' + uart_tx_def
|
||||
|
||||
def rx_hdl_inst(self):
|
||||
return f"""
|
||||
rx_uart #(.CLOCKS_PER_BAUD({self.clocks_per_baud})) urx (
|
||||
.i_clk(clk),
|
||||
.i_uart_rx(rx),
|
||||
.o_wr(urx_brx_axiv),
|
||||
.o_data(urx_brx_axid));
|
||||
|
||||
logic [7:0] urx_brx_axid;
|
||||
logic urx_brx_axiv;
|
||||
|
||||
bridge_rx brx (
|
||||
.clk(clk),
|
||||
|
||||
.rx_data(urx_brx_axid),
|
||||
.rx_valid(urx_brx_axiv),
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());
|
||||
"""
|
||||
rx = VerilogManipulator("uart_rx_bridge_rx_inst_templ.v")
|
||||
rx.sub(self.clocks_per_baud, "/* CLOCKS_PER_BAUD */")
|
||||
return rx.get_hdl()
|
||||
|
||||
def tx_hdl_inst(self):
|
||||
return f"""
|
||||
bridge_tx btx (
|
||||
.clk(clk),
|
||||
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
.ready_i(utx_btx_ready),
|
||||
.data_o(btx_utx_data),
|
||||
.valid_o(btx_utx_valid));
|
||||
|
||||
logic utx_btx_ready;
|
||||
logic btx_utx_valid;
|
||||
logic [7:0] btx_utx_data;
|
||||
|
||||
uart_tx #(.CLOCKS_PER_BAUD({self.clocks_per_baud})) utx (
|
||||
.clk(clk),
|
||||
|
||||
.data(btx_utx_data),
|
||||
.valid(btx_utx_valid),
|
||||
.ready(utx_btx_ready),
|
||||
|
||||
.tx(tx));\n"""
|
||||
tx = VerilogManipulator("uart_tx_bridge_tx_inst_templ.v")
|
||||
tx.sub(self.clocks_per_baud, "/* CLOCKS_PER_BAUD */")
|
||||
return tx.get_hdl()
|
||||
|
||||
class IOCoreProbe:
|
||||
def __init__(self, name, width, direction, base_addr, interface):
|
||||
|
|
@ -241,120 +290,62 @@ class IOCore:
|
|||
|
||||
|
||||
def hdl_inst(self):
|
||||
# TODO: make this a string comprehension
|
||||
inst_ports = [f".{probe.name}({probe.name}),\n " for probe in self.probes]
|
||||
inst_ports = "".join(inst_ports)
|
||||
inst_ports = inst_ports.rstrip()
|
||||
inst = VerilogManipulator("io_core_inst_tmpl.v")
|
||||
inst.sub(self.name, "/* MODULE_NAME */")
|
||||
inst.sub(self.name + "_inst", "/* INST_NAME */")
|
||||
|
||||
return f"""
|
||||
{self.name} {self.name}_inst(
|
||||
.clk(clk),
|
||||
probes = {probe.name:probe.width for probe in self.probes}
|
||||
|
||||
// ports
|
||||
{inst_ports}
|
||||
inst_ports = inst.net_conn(probes, trailing_comma=True)
|
||||
inst.sub(inst_ports, "/* INST_PORTS */")
|
||||
|
||||
// input port
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
return inst.get_hdl()
|
||||
|
||||
// output port
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o()
|
||||
);
|
||||
"""
|
||||
|
||||
def hdl_def(self):
|
||||
io_core = VerilogManipulator("io_core_def_tmpl.v")
|
||||
io_core.sub(self.name, "/* MODULE_NAME */")
|
||||
io_core.sub(self.max_addr, "/* MAX_ADDR */")
|
||||
|
||||
# generate declaration
|
||||
top_level_ports = ',\n '.join(self.hdl_top_level_ports())
|
||||
top_level_ports = ',\n'.join(self.hdl_top_level_ports())
|
||||
top_level_ports += ','
|
||||
declaration = f"""
|
||||
module {self.name} (
|
||||
input wire clk,
|
||||
|
||||
// ports
|
||||
{top_level_ports}
|
||||
|
||||
// input port
|
||||
input wire [15:0] addr_i,
|
||||
input wire [15:0] wdata_i,
|
||||
input wire [15:0] rdata_i,
|
||||
input wire rw_i,
|
||||
input wire valid_i,
|
||||
|
||||
// output port
|
||||
output reg [15:0] addr_o,
|
||||
output reg [15:0] wdata_o,
|
||||
output reg [15:0] rdata_o,
|
||||
output reg rw_o,
|
||||
output reg valid_o
|
||||
);
|
||||
"""
|
||||
io_core.sub(top_level_ports, "/* TOP_LEVEL_PORTS */")
|
||||
|
||||
# generate memory handling
|
||||
read_case_statement_body = ""
|
||||
write_case_statement_body = ""
|
||||
rcsb = "" # read case statement body
|
||||
wcsb = "" # write case statement body
|
||||
for probe in self.probes:
|
||||
|
||||
# add to read block
|
||||
if probe.width == 16:
|
||||
read_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: rdata_o <= {probe.name};\n"
|
||||
rcsb += f"{probe.base_addr}: rdata_o <= {probe.name};\n"
|
||||
|
||||
else:
|
||||
read_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: rdata_o <= {{{16-probe.width}'b0, {probe.name}}};\n"
|
||||
rcsb += f"{probe.base_addr}: rdata_o <= {{{16-probe.width}'b0, {probe.name}}};\n"
|
||||
|
||||
|
||||
# if output, add to write block
|
||||
if probe.direction == "output":
|
||||
if probe.width == 1:
|
||||
write_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: {probe.name} <= wdata_i[0];\n"
|
||||
wcsb += f"{probe.base_addr}: {probe.name} <= wdata_i[0];\n"
|
||||
|
||||
elif probe.width == 16:
|
||||
write_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: {probe.name} <= wdata_i;\n"
|
||||
wcsb += f"{probe.base_addr}: {probe.name} <= wdata_i;\n"
|
||||
|
||||
else:
|
||||
write_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: {probe.name} <= wdata_i[{probe.width-1}:0];\n"
|
||||
wcsb += f"{probe.base_addr}: {probe.name} <= wdata_i[{probe.width-1}:0];\n"
|
||||
|
||||
# remove trailing newline
|
||||
read_case_statement_body = read_case_statement_body.rstrip()
|
||||
write_case_statement_body = write_case_statement_body.rstrip()
|
||||
rcsb = rcsb.rstrip()
|
||||
wcsb = wcsb.rstrip()
|
||||
|
||||
memory_handler_hdl = f"""
|
||||
parameter BASE_ADDR = 0;
|
||||
always @(posedge clk) begin
|
||||
addr_o <= addr_i;
|
||||
wdata_o <= wdata_i;
|
||||
rdata_o <= rdata_i;
|
||||
rw_o <= rw_i;
|
||||
valid_o <= valid_i;
|
||||
rdata_o <= rdata_i;
|
||||
io_core.sub(rcsb, "/* READ_CASE_STATEMENT_BODY */")
|
||||
io_core.sub(wcsb, "/* WRITE_CASE_STATEMENT_BODY */")
|
||||
|
||||
return io_core.get_hdl()
|
||||
|
||||
|
||||
// check if address is valid
|
||||
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= BASE_ADDR + {self.max_addr})) begin
|
||||
|
||||
if(!rw_i) begin // reads
|
||||
case (addr_i)
|
||||
{read_case_statement_body}
|
||||
endcase
|
||||
end
|
||||
|
||||
else begin // writes
|
||||
case (addr_i)
|
||||
{write_case_statement_body}
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
||||
|
||||
hdl = declaration + memory_handler_hdl + "endmodule"
|
||||
return hdl
|
||||
|
||||
def hdl_top_level_ports(self):
|
||||
ports = []
|
||||
|
|
@ -379,31 +370,17 @@ class LUTRAMCore:
|
|||
self.max_addr = self.base_addr + self.size - 1
|
||||
|
||||
def hdl_inst(self):
|
||||
hdl = f"""
|
||||
lut_ram #(.DEPTH({self.size})) {self.name} (
|
||||
.clk(clk),
|
||||
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());\n"""
|
||||
|
||||
return hdl
|
||||
inst = VerilogManipulator("lut_ram_inst_tmpl.v")
|
||||
inst.sub(self.size, "/* DEPTH */")
|
||||
inst.sub(self.name, "/* INST_NAME */")
|
||||
return inst.get_hdl()
|
||||
|
||||
def hdl_def(self):
|
||||
hdl = pkgutil.get_data(__name__, "lut_ram.v").decode()
|
||||
return hdl
|
||||
return VerilogManipulator("lut_ram.v").get_hdl()
|
||||
|
||||
def hdl_top_level_ports(self):
|
||||
# no top_level connections since this core just lives on the bus
|
||||
return []
|
||||
return ""
|
||||
|
||||
def read(self, addr):
|
||||
return self.interface.read_register(addr + self.base_addr)
|
||||
|
|
@ -448,83 +425,59 @@ class LogicAnalyzerCore:
|
|||
self.max_addr = self.sample_mem_base_addr + self.sample_depth
|
||||
|
||||
def hdl_inst(self):
|
||||
ports = []
|
||||
la_inst = VerilogManipulator("logic_analyzer_inst_tmpl.v")
|
||||
|
||||
ports = [f".{name}({name})," for name in self.probes.keys()]
|
||||
ports = "\n\t\t".join(ports)
|
||||
# add module name to instantiation
|
||||
la_inst.sub(self.name, "/* INST_NAME */")
|
||||
|
||||
hdl = f"""
|
||||
logic_analyzer {self.name} (
|
||||
.clk(clk),
|
||||
# add net connections to instantiation
|
||||
conns = la_inst.net_conn(self.probes, trailing_comma=True)
|
||||
la_inst.sub(conns, "/* NET_CONNS */")
|
||||
return la_inst.get_hdl()
|
||||
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
{ports}
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());\n"""
|
||||
|
||||
return hdl
|
||||
|
||||
def generate_trigger_block(self):
|
||||
trigger_block_hdl = pkgutil.get_data(__name__, "trigger_block_template.v").decode()
|
||||
def gen_trigger_block_def(self):
|
||||
trigger_block = VerilogManipulator("trigger_block_template.v")
|
||||
|
||||
# add probe ports to module declaration
|
||||
# - these are the ports that belong to the logic analyzer, but
|
||||
# need to be included in the trigger_block module declaration
|
||||
probe_ports = []
|
||||
for name, width in self.probes.items():
|
||||
if width == 1:
|
||||
probe_ports.append(f"input wire {name}")
|
||||
else:
|
||||
probe_ports.append(f"input wire [{width-1}:0] {name}")
|
||||
# these ports belong to the logic analyzer, but
|
||||
# need to be included in the trigger_block module declaration
|
||||
probe_ports = trigger_block.net_dec(self.probes, "input wire", trailing_comma=True)
|
||||
trigger_block.sub(probe_ports, "/* PROBE_PORTS */")
|
||||
|
||||
probe_ports = ",\n\t".join(probe_ports)
|
||||
probe_ports = probe_ports + ","
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @PROBE_PORTS", probe_ports)
|
||||
|
||||
# add trigger cores to module definition
|
||||
# - these are instances of the trigger module, of which one gets wired
|
||||
# into each probe
|
||||
# these are instances of the trigger module, of which one gets wired
|
||||
# into each probe
|
||||
trigger_module_insts = []
|
||||
for name, width in self.probes.items():
|
||||
trigger_module_inst = f"reg [3:0] {name}_trigger_op = 0;\n\t"
|
||||
trig_inst = VerilogManipulator("trigger_block_inst_tmpl.v")
|
||||
trig_inst.sub(width, "/* INPUT_WIDTH */")
|
||||
trig_inst.sub(f"{name}_trigger", "/* NAME */")
|
||||
|
||||
trig_inst.sub(f"reg [3:0] {name}_op = 0;", "/* OP_DEC */")
|
||||
trig_inst.sub(f"reg {name}_trig;", "/* TRIG_DEC */")
|
||||
|
||||
if width == 1:
|
||||
trigger_module_inst += f"reg {name}_trigger_arg = 0;\n\t"
|
||||
trig_inst.sub(f"reg {name}_arg = 0;", "/* ARG_DEC */")
|
||||
|
||||
else:
|
||||
trigger_module_inst += f"reg [{width-1}:0] {name}_trigger_arg = 0;\n\t"
|
||||
trig_inst.sub(f"reg [{width-1}:0] {name}_arg = 0;", "/* ARG_DEC */")
|
||||
|
||||
trigger_module_inst += f"reg {name}_trig;\n\t"
|
||||
trigger_module_inst += f"""
|
||||
trigger #(.INPUT_WIDTH({width})) {name}_trigger (
|
||||
.clk(clk),
|
||||
trig_inst.sub(name, "/* PROBE */")
|
||||
trig_inst.sub(f"{name}_op", "/* OP */")
|
||||
trig_inst.sub(f"{name}_arg", "/* ARG */")
|
||||
trig_inst.sub(f"{name}_trig", "/* TRIG */")
|
||||
|
||||
.probe({name}),
|
||||
.op({name}_trigger_op),
|
||||
.arg({name}_trigger_arg),
|
||||
.trig({name}_trig)
|
||||
);
|
||||
"""
|
||||
trigger_module_insts.append(trigger_module_inst)
|
||||
trigger_module_insts.append(trig_inst.get_hdl())
|
||||
|
||||
trigger_module_insts = "".join(trigger_module_insts)
|
||||
trigger_module_insts = trigger_module_insts.rstrip()
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @TRIGGER_MODULE_INSTS", trigger_module_insts)
|
||||
trigger_module_insts = "\n".join(trigger_module_insts)
|
||||
trigger_block.sub(trigger_module_insts, "/* TRIGGER_MODULE_INSTS */")
|
||||
|
||||
# add combined individual triggers
|
||||
combined_individual_triggers = [f"{name}_trig" for name in self.probes]
|
||||
combined_individual_triggers = " || ".join(combined_individual_triggers)
|
||||
combined_individual_triggers = f"assign trig = {combined_individual_triggers};"
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @COMBINE_INDIV_TRIGGERS", combined_individual_triggers)
|
||||
cit = [f"{name}_trig" for name in self.probes]
|
||||
cit = " || ".join(cit)
|
||||
cit = f"assign trig = {cit};"
|
||||
trigger_block.sub(cit, " /* COMBINE_INDIV_TRIGGERS */")
|
||||
|
||||
# add read and write block case statement bodies
|
||||
rcsb = "" # read case statement body
|
||||
|
|
@ -532,38 +485,30 @@ class LogicAnalyzerCore:
|
|||
addr = 0
|
||||
for i, name in enumerate(self.probes):
|
||||
addr = 2 * i
|
||||
rcsb += f"\t\t\t\t\tBASE_ADDR + {addr}: rdata_o <= {name}_trigger_op;\n"
|
||||
wcsb += f"\t\t\t\t\tBASE_ADDR + {addr}: {name}_trigger_op <= wdata_i;\n"
|
||||
rcsb += f"BASE_ADDR + {addr}: rdata_o <= {name}_op;\n"
|
||||
wcsb += f"BASE_ADDR + {addr}: {name}_op <= wdata_i;\n"
|
||||
|
||||
addr = (2 * i) + 1
|
||||
rcsb += f"\t\t\t\t\tBASE_ADDR + {addr}: rdata_o <= {name}_trigger_arg;\n"
|
||||
wcsb += f"\t\t\t\t\tBASE_ADDR + {addr}: {name}_trigger_arg <= wdata_i;\n"
|
||||
rcsb += f"BASE_ADDR + {addr}: rdata_o <= {name}_arg;\n"
|
||||
wcsb += f"BASE_ADDR + {addr}: {name}_arg <= wdata_i;\n"
|
||||
|
||||
rcsb = rcsb.strip()
|
||||
wcsb = wcsb.strip()
|
||||
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @READ_CASE_STATEMENT_BODY", rcsb)
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @WRITE_CASE_STATEMENT_BODY", wcsb)
|
||||
trigger_block_hdl = trigger_block_hdl.replace("// @MAX_ADDR", str(addr))
|
||||
return trigger_block_hdl
|
||||
trigger_block.sub(rcsb, "/* READ_CASE_STATEMENT_BODY */")
|
||||
trigger_block.sub(wcsb, "/* WRITE_CASE_STATEMENT_BODY */")
|
||||
trigger_block.sub(addr, "/* MAX_ADDR */")
|
||||
|
||||
def generate_sample_mem(self):
|
||||
sample_mem_hdl = pkgutil.get_data(__name__, "sample_mem_template.v").decode()
|
||||
return trigger_block.get_hdl()
|
||||
|
||||
def gen_sample_mem_def(self):
|
||||
sample_mem = VerilogManipulator("sample_mem_tmpl.v")
|
||||
|
||||
# add probe ports to module declaration
|
||||
# - these are the ports that belong to the logic analyzer, but
|
||||
# need to be included in the trigger_block module declaration
|
||||
probe_ports = []
|
||||
for name, width in self.probes.items():
|
||||
if width == 1:
|
||||
probe_ports.append(f"input wire {name}")
|
||||
else:
|
||||
probe_ports.append(f"input wire [{width-1}:0] {name}")
|
||||
|
||||
probe_ports = ",\n\t".join(probe_ports)
|
||||
probe_ports = probe_ports + ","
|
||||
sample_mem_hdl = sample_mem_hdl.replace("/* PROBE_PORTS */", probe_ports)
|
||||
|
||||
probe_ports = sample_mem.net_dec(self.probes, "input wire", trailing_comma=True)
|
||||
sample_mem.sub(probe_ports, "/* PROBE_PORTS */")
|
||||
|
||||
# concatenate probes to BRAM input
|
||||
total_probe_width = sum([width for name, width in self.probes.items()])
|
||||
|
|
@ -576,76 +521,54 @@ class LogicAnalyzerCore:
|
|||
concat = ", ".join([name for name in self.probes])
|
||||
concat = f"{{{zero_pad_width}'b0, {concat}}}"
|
||||
|
||||
sample_mem_hdl = sample_mem_hdl.replace("/* CONCAT */", concat)
|
||||
sample_mem.sub(concat, "/* CONCAT */")
|
||||
return sample_mem.get_hdl()
|
||||
|
||||
return sample_mem_hdl
|
||||
|
||||
def generate_logic_analyzer(self):
|
||||
logic_analyzer_hdl = pkgutil.get_data(__name__, "logic_analyzer_template.v").decode()
|
||||
def gen_logic_analyzer_def(self):
|
||||
la = VerilogManipulator("logic_analyzer_tmpl.v")
|
||||
|
||||
# add top level probe ports to module declaration
|
||||
# - these are the ports that belong to the logic analyzer, but
|
||||
# need to be included in the trigger_block module declaration
|
||||
|
||||
tlpp = [] # top level probe ports
|
||||
for name, width in self.probes.items():
|
||||
if width == 1:
|
||||
tlpp.append(f"input wire {name}")
|
||||
else:
|
||||
tlpp.append(f"input wire [{width-1}:0] {name}")
|
||||
|
||||
tlpp = ",\n\t".join(tlpp)
|
||||
tlpp = tlpp + ","
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* TOP_LEVEL_PROBE_PORTS */", tlpp)
|
||||
ports = la.net_dec(self.probes, "input wire", trailing_comma=True)
|
||||
la.sub(ports, "/* TOP_LEVEL_PROBE_PORTS */")
|
||||
|
||||
# assign base addresses to the FSM, trigger block, and sample mem
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* FSM_BASE_ADDR */", str(self.fsm_base_addr))
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* TRIGGER_BLOCK_BASE_ADDR */", str(self.trigger_block_base_addr))
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* SAMPLE_MEM_BASE_ADDR */", str(self.sample_mem_base_addr))
|
||||
la.sub(self.fsm_base_addr, "/* FSM_BASE_ADDR */")
|
||||
la.sub(self.trigger_block_base_addr, "/* TRIGGER_BLOCK_BASE_ADDR */")
|
||||
la.sub(self.sample_mem_base_addr, "/* SAMPLE_MEM_BASE_ADDR */")
|
||||
|
||||
# set sample depth
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* SAMPLE_DEPTH */", str(self.sample_depth))
|
||||
la.sub(self.sample_depth, "/* SAMPLE_DEPTH */")
|
||||
|
||||
# set probe ports for the trigger block and sample mem
|
||||
pp = [] # probe ports
|
||||
pp = [f".{name}({name})" for name in self.probes]
|
||||
pp = ",\n\t\t".join(pp)
|
||||
pp = pp + ","
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* TRIGGER_BLOCK_PROBE_PORTS */", pp)
|
||||
logic_analyzer_hdl = logic_analyzer_hdl.replace("/* SAMPLE_MEM_PROBE_PORTS */", pp)
|
||||
|
||||
return logic_analyzer_hdl
|
||||
probe_ports = la.net_conn(self.probes, trailing_comma=True)
|
||||
la.sub(probe_ports, "/* TRIGGER_BLOCK_PROBE_PORTS */")
|
||||
la.sub(probe_ports, "/* SAMPLE_MEM_PROBE_PORTS */")
|
||||
|
||||
return la.get_hdl()
|
||||
|
||||
def hdl_def(self):
|
||||
# Return an autogenerated verilog module definition for the core.
|
||||
# load source files=
|
||||
la_fsm_hdl = pkgutil.get_data(__name__, "la_fsm.v").decode()
|
||||
dual_port_bram_hdl = pkgutil.get_data(__name__, "dual_port_bram.v").decode()
|
||||
trigger_hdl = pkgutil.get_data(__name__, "trigger.v").decode()
|
||||
# load source files
|
||||
la_fsm = VerilogManipulator("la_fsm.v").get_hdl()
|
||||
dual_port_bram = VerilogManipulator("dual_port_bram.v").get_hdl()
|
||||
trigger = VerilogManipulator("trigger.v").get_hdl()
|
||||
trigger_block = self.gen_trigger_block_def()
|
||||
sample_mem = self.gen_sample_mem_def()
|
||||
logic_analyzer = self.gen_logic_analyzer_def()
|
||||
|
||||
# generate trigger block
|
||||
trigger_block_hdl = self.generate_trigger_block()
|
||||
|
||||
# generate sample memory
|
||||
sample_mem_hdl = self.generate_sample_mem()
|
||||
|
||||
# generate logic analyzer
|
||||
logic_analyzer_hdl = self.generate_logic_analyzer()
|
||||
|
||||
return logic_analyzer_hdl + la_fsm_hdl + sample_mem_hdl + dual_port_bram_hdl + trigger_block_hdl + trigger_hdl
|
||||
return logic_analyzer + la_fsm + sample_mem + dual_port_bram + trigger_block + trigger
|
||||
|
||||
def hdl_top_level_ports(self):
|
||||
# this should return the probes that we want to connect to top-level, but as a list of verilog ports
|
||||
|
||||
# the probes that we want as ports on the top-level manta module
|
||||
ports = []
|
||||
for name, width in self.probes.items():
|
||||
if width == 1:
|
||||
ports.append(f"input wire {name}")
|
||||
|
||||
else:
|
||||
ports.append(f"input wire [{width-1}:0] {name}")
|
||||
|
||||
return ports
|
||||
#return VerilogManipulator().net_dec(self.probes, "input wire")
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
|
@ -780,7 +703,7 @@ class Manta:
|
|||
|
||||
return config
|
||||
|
||||
def generate_connections(self):
|
||||
def gen_connections(self):
|
||||
# generates hdl for registers that connect two modules together
|
||||
|
||||
# make pairwise cores
|
||||
|
|
@ -800,7 +723,7 @@ class Manta:
|
|||
|
||||
return conns
|
||||
|
||||
def generate_instances(self):
|
||||
def gen_instances(self):
|
||||
# generates hdl for modules that need to be connected together
|
||||
|
||||
insts = []
|
||||
|
|
@ -841,9 +764,9 @@ class Manta:
|
|||
|
||||
return insts
|
||||
|
||||
def generate_core_chain(self):
|
||||
insts = self.generate_instances()
|
||||
conns = self.generate_connections()
|
||||
def gen_core_chain(self):
|
||||
insts = self.gen_instances()
|
||||
conns = self.gen_connections()
|
||||
core_chain = []
|
||||
for i, inst in enumerate(insts):
|
||||
core_chain.append(inst)
|
||||
|
|
@ -853,7 +776,7 @@ class Manta:
|
|||
|
||||
return '\n'.join(core_chain)
|
||||
|
||||
def generate_header(self):
|
||||
def gen_header(self):
|
||||
# generate header
|
||||
user = os.environ.get("USER", os.environ.get("USERNAME"))
|
||||
timestamp = datetime.now().strftime("%d %b %Y at %H:%M:%S")
|
||||
|
|
@ -869,7 +792,7 @@ Provided under a GNU GPLv3 license. Go wild.
|
|||
"""
|
||||
return header
|
||||
|
||||
def generate_ex_inst(self):
|
||||
def gen_example_inst(self):
|
||||
# this is a C-style block comment that contains an instantiation
|
||||
# of the configured manta instance - the idea is that a user
|
||||
# can copy-paste that into their design instead of trying to spot
|
||||
|
|
@ -918,7 +841,7 @@ manta manta_inst (
|
|||
*/
|
||||
"""
|
||||
|
||||
def generate_declaration(self):
|
||||
def gen_declaration(self):
|
||||
# get all the top level connections for each module.
|
||||
|
||||
interface_ports = self.interface.hdl_top_level_ports()
|
||||
|
|
@ -947,7 +870,7 @@ module manta (
|
|||
{ports});
|
||||
"""
|
||||
|
||||
def generate_interface_rx(self):
|
||||
def gen_interface_rx(self):
|
||||
# instantiate interface_rx, substitute in register names
|
||||
interface_rx_inst = self.interface.rx_hdl_inst()
|
||||
|
||||
|
|
@ -965,7 +888,7 @@ module manta (
|
|||
|
||||
return interface_rx_inst + interface_rx_conn
|
||||
|
||||
def generate_interface_tx(self):
|
||||
def gen_interface_tx(self):
|
||||
|
||||
# connect core_chain to interface_tx
|
||||
interface_tx_conn = f"""
|
||||
|
|
@ -983,45 +906,28 @@ module manta (
|
|||
|
||||
return interface_tx_conn + interface_tx_inst
|
||||
|
||||
def generate_footer(self):
|
||||
def gen_footer(self):
|
||||
return """endmodule\n"""
|
||||
|
||||
def generate_module_defs(self):
|
||||
def gen_module_defs(self):
|
||||
# aggregate module definitions and remove duplicates
|
||||
module_defs_with_dups = [self.interface.rx_hdl_def()] + [core.hdl_def() for core in self.cores] + [self.interface.tx_hdl_def()]
|
||||
module_defs = []
|
||||
module_defs = [m_def for m_def in module_defs_with_dups if m_def not in module_defs]
|
||||
return '\n'.join(module_defs)
|
||||
|
||||
|
||||
|
||||
def generate_hdl(self, output_filepath):
|
||||
# generate header
|
||||
header = self.generate_header()
|
||||
|
||||
# generate example instantiation
|
||||
ex_inst = self.generate_ex_inst()
|
||||
|
||||
# generate module declaration
|
||||
declar = self.generate_declaration()
|
||||
|
||||
# generate interface_rx
|
||||
interface_rx = self.generate_interface_rx()
|
||||
|
||||
# generate core chain
|
||||
core_chain = self.generate_core_chain()
|
||||
|
||||
# generate interface_tx
|
||||
interface_tx = self.generate_interface_tx()
|
||||
|
||||
# generate footer
|
||||
footer = self.generate_footer()
|
||||
|
||||
# generate module definitions
|
||||
module_defs = self.generate_module_defs()
|
||||
header = self.gen_header()
|
||||
ex_inst = self.gen_example_inst()
|
||||
declaration = self.gen_declaration() + "\n"
|
||||
interface_rx = self.gen_interface_rx()
|
||||
core_chain = self.gen_core_chain()
|
||||
interface_tx = self.gen_interface_tx()
|
||||
footer = self.gen_footer()
|
||||
module_defs = self.gen_module_defs()
|
||||
|
||||
# assemble all the parts
|
||||
hdl = header + ex_inst + declar + interface_rx + core_chain + interface_tx + footer
|
||||
hdl = header + ex_inst + declaration + interface_rx + core_chain + interface_tx + footer
|
||||
hdl += "\n /* ---- Module Definitions ---- */\n"
|
||||
hdl += module_defs
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
module /* MODULE_NAME */ (
|
||||
input wire clk,
|
||||
|
||||
// ports
|
||||
/* TOP_LEVEL_PORTS */
|
||||
|
||||
// input port
|
||||
input wire [15:0] addr_i,
|
||||
input wire [15:0] wdata_i,
|
||||
input wire [15:0] rdata_i,
|
||||
input wire rw_i,
|
||||
input wire valid_i,
|
||||
|
||||
// output port
|
||||
output reg [15:0] addr_o,
|
||||
output reg [15:0] wdata_o,
|
||||
output reg [15:0] rdata_o,
|
||||
output reg rw_o,
|
||||
output reg valid_o
|
||||
);
|
||||
|
||||
parameter BASE_ADDR = 0;
|
||||
always @(posedge clk) begin
|
||||
addr_o <= addr_i;
|
||||
wdata_o <= wdata_i;
|
||||
rdata_o <= rdata_i;
|
||||
rw_o <= rw_i;
|
||||
valid_o <= valid_i;
|
||||
rdata_o <= rdata_i;
|
||||
|
||||
|
||||
// check if address is valid
|
||||
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= BASE_ADDR + /* MAX_ADDR */)) begin
|
||||
|
||||
// reads
|
||||
if(!rw_i) begin
|
||||
case (addr_i)
|
||||
/* READ_CASE_STATEMENT_BODY */
|
||||
endcase
|
||||
end
|
||||
|
||||
// writes
|
||||
else begin
|
||||
case (addr_i)
|
||||
/* WRITE_CASE_STATEMENT_BODY */
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* MODULE_NAME */ /* INST_NAME */ (
|
||||
.clk(clk),
|
||||
|
||||
// ports
|
||||
/* INST_PORTS */
|
||||
|
||||
// input port
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
// output port
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o()
|
||||
);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
logic_analyzer /* INST_NAME */ (
|
||||
.clk(clk),
|
||||
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
/* NET_CONNS */
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
lut_ram #(.DEPTH(/* DEPTH */)) /* INST_NAME */ (
|
||||
.clk(clk),
|
||||
|
||||
.addr_i(),
|
||||
.wdata_i(),
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/* OP_DEC */
|
||||
/* ARG_DEC */
|
||||
/* TRIG_DEC */
|
||||
|
||||
trigger #(.INPUT_WIDTH(/* INPUT_WIDTH */)) /* NAME */ (
|
||||
.clk(clk),
|
||||
|
||||
.probe(/* PROBE */),
|
||||
.op(/* OP */),
|
||||
.arg(/* ARG */),
|
||||
.trig(/* TRIG */)
|
||||
);
|
||||
|
|
@ -5,7 +5,7 @@ module trigger_block (
|
|||
input wire clk,
|
||||
|
||||
// probes
|
||||
// @PROBE_PORTS
|
||||
/* PROBE_PORTS */
|
||||
|
||||
// trigger
|
||||
output reg trig,
|
||||
|
|
@ -25,15 +25,15 @@ module trigger_block (
|
|||
output reg valid_o);
|
||||
|
||||
parameter BASE_ADDR = 0;
|
||||
localparam MAX_ADDR = // @MAX_ADDR;
|
||||
localparam MAX_ADDR = /* MAX_ADDR */;
|
||||
|
||||
// trigger configuration registers
|
||||
// - each probe gets an operation and a compare register
|
||||
// - at the end we OR them all together. along with any custom probes the user specs
|
||||
|
||||
// @TRIGGER_MODULE_INSTS
|
||||
/* TRIGGER_MODULE_INSTS */
|
||||
|
||||
// @COMBINE_INDIV_TRIGGERS
|
||||
/* COMBINE_INDIV_TRIGGERS */
|
||||
|
||||
// perform register operations
|
||||
always @(posedge clk) begin
|
||||
|
|
@ -49,14 +49,14 @@ module trigger_block (
|
|||
// reads
|
||||
if(valid_i && !rw_i) begin
|
||||
case (addr_i)
|
||||
// @READ_CASE_STATEMENT_BODY
|
||||
/* READ_CASE_STATEMENT_BODY */
|
||||
endcase
|
||||
end
|
||||
|
||||
// writes
|
||||
else if(valid_i && rw_i) begin
|
||||
case (addr_i)
|
||||
// @WRITE_CASE_STATEMENT_BODY
|
||||
/* WRITE_CASE_STATEMENT_BODY */
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
rx_uart #(.CLOCKS_PER_BAUD(/* CLOCKS_PER_BAUD */)) urx (
|
||||
.i_clk(clk),
|
||||
.i_uart_rx(rx),
|
||||
.o_wr(urx_brx_axiv),
|
||||
.o_data(urx_brx_axid));
|
||||
|
||||
logic [7:0] urx_brx_axid;
|
||||
logic urx_brx_axiv;
|
||||
|
||||
bridge_rx brx (
|
||||
.clk(clk),
|
||||
|
||||
.rx_data(urx_brx_axid),
|
||||
.rx_valid(urx_brx_axiv),
|
||||
|
||||
.addr_o(),
|
||||
.wdata_o(),
|
||||
.rw_o(),
|
||||
.valid_o());
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
bridge_tx btx (
|
||||
.clk(clk),
|
||||
|
||||
.rdata_i(),
|
||||
.rw_i(),
|
||||
.valid_i(),
|
||||
|
||||
.ready_i(utx_btx_ready),
|
||||
.data_o(btx_utx_data),
|
||||
.valid_o(btx_utx_valid));
|
||||
|
||||
logic utx_btx_ready;
|
||||
logic btx_utx_valid;
|
||||
logic [7:0] btx_utx_data;
|
||||
|
||||
uart_tx #(.CLOCKS_PER_BAUD(/* CLOCKS_PER_BAUD */)) utx (
|
||||
.clk(clk),
|
||||
|
||||
.data(btx_utx_data),
|
||||
.valid(btx_utx_valid),
|
||||
.ready(utx_btx_ready),
|
||||
|
||||
.tx(tx));
|
||||
Loading…
Reference in New Issue