refactor IO core read/write to be less ugly
This commit is contained in:
parent
3cf5164d23
commit
edd50168e2
|
|
@ -1,6 +1,5 @@
|
||||||
## IO Core
|
## IO Core
|
||||||
- test examples that build
|
- test examples that build
|
||||||
- update IO core read/write memory handling to be less ugly
|
|
||||||
- add logic for ports >16 bits in width
|
- add logic for ports >16 bits in width
|
||||||
- clock domain crossing
|
- clock domain crossing
|
||||||
- figure out what happens for module naming - it's possible we could have two modules that have the same ports but have different names
|
- figure out what happens for module naming - it's possible we could have two modules that have the same ports but have different names
|
||||||
|
|
@ -21,6 +20,7 @@
|
||||||
- write interface
|
- write interface
|
||||||
|
|
||||||
## Python API
|
## Python API
|
||||||
|
- implement global address assignment
|
||||||
- find a better way of handling tabs
|
- find a better way of handling tabs
|
||||||
- make finding a serial port possible even if no cores are configured
|
- make finding a serial port possible even if no cores are configured
|
||||||
- make autodetecting and automatically selecting a serial device possible
|
- make autodetecting and automatically selecting a serial device possible
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
`timescale 1ns/1ps
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This manta definition was generated on 16 Mar 2023 at 12:07:39 by fischerm
|
This manta definition was generated on 17 Mar 2023 at 20:02:55 by fischerm
|
||||||
|
|
||||||
If this breaks or if you've got dank formal verification memes,
|
If this breaks or if you've got dank formal verification memes,
|
||||||
please contact fischerm [at] mit.edu
|
please contact fischerm [at] mit.edu
|
||||||
|
|
@ -418,31 +418,31 @@ always @(posedge clk) begin
|
||||||
|
|
||||||
if(!rw_i) begin // reads
|
if(!rw_i) begin // reads
|
||||||
case (addr_i)
|
case (addr_i)
|
||||||
BASE_ADDR + 0: rdata_o <= {15'b0, btnu};
|
0: rdata_o <= {15'b0, btnu};
|
||||||
BASE_ADDR + 1: rdata_o <= {15'b0, btnd};
|
1: rdata_o <= {15'b0, btnd};
|
||||||
BASE_ADDR + 2: rdata_o <= {15'b0, btnl};
|
2: rdata_o <= {15'b0, btnl};
|
||||||
BASE_ADDR + 3: rdata_o <= {15'b0, btnr};
|
3: rdata_o <= {15'b0, btnr};
|
||||||
BASE_ADDR + 4: rdata_o <= {15'b0, btnc};
|
4: rdata_o <= {15'b0, btnc};
|
||||||
BASE_ADDR + 5: rdata_o <= sw;
|
5: rdata_o <= sw;
|
||||||
BASE_ADDR + 6: rdata_o <= led;
|
6: rdata_o <= led;
|
||||||
BASE_ADDR + 7: rdata_o <= {15'b0, led16_b};
|
7: rdata_o <= {15'b0, led16_b};
|
||||||
BASE_ADDR + 8: rdata_o <= {15'b0, led16_g};
|
8: rdata_o <= {15'b0, led16_g};
|
||||||
BASE_ADDR + 9: rdata_o <= {15'b0, led16_r};
|
9: rdata_o <= {15'b0, led16_r};
|
||||||
BASE_ADDR + 10: rdata_o <= {15'b0, led17_b};
|
10: rdata_o <= {15'b0, led17_b};
|
||||||
BASE_ADDR + 11: rdata_o <= {15'b0, led17_g};
|
11: rdata_o <= {15'b0, led17_g};
|
||||||
BASE_ADDR + 12: rdata_o <= {15'b0, led17_r};
|
12: rdata_o <= {15'b0, led17_r};
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
else begin // writes
|
else begin // writes
|
||||||
case (addr_i)
|
case (addr_i)
|
||||||
BASE_ADDR + 6: led <= wdata_i;
|
6: led <= wdata_i;
|
||||||
BASE_ADDR + 7: led16_b <= wdata_i[0];
|
7: led16_b <= wdata_i[0];
|
||||||
BASE_ADDR + 8: led16_g <= wdata_i[0];
|
8: led16_g <= wdata_i[0];
|
||||||
BASE_ADDR + 9: led16_r <= wdata_i[0];
|
9: led16_r <= wdata_i[0];
|
||||||
BASE_ADDR + 10: led17_b <= wdata_i[0];
|
10: led17_b <= wdata_i[0];
|
||||||
BASE_ADDR + 11: led17_g <= wdata_i[0];
|
11: led17_g <= wdata_i[0];
|
||||||
BASE_ADDR + 12: led17_r <= wdata_i[0];
|
12: led17_r <= wdata_i[0];
|
||||||
endcase
|
endcase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -131,91 +131,80 @@ class UARTInterface:
|
||||||
|
|
||||||
.tx(tx));\n"""
|
.tx(tx));\n"""
|
||||||
|
|
||||||
|
class IOCoreProbe:
|
||||||
|
def __init__(self, name, width, direction, base_addr, interface):
|
||||||
|
self.name = name
|
||||||
|
self.width = width
|
||||||
|
self.direction = direction
|
||||||
|
self.base_addr = base_addr
|
||||||
|
self.interface = interface
|
||||||
|
|
||||||
|
def set(self, data):
|
||||||
|
# check that value is within range for the width of the probe
|
||||||
|
assert isinstance(data, int), "Data must be an integer."
|
||||||
|
if data > 0:
|
||||||
|
assert data <= (2**self.width) - 1, f"Unsigned value too large for probe of width {self.width}"
|
||||||
|
|
||||||
|
elif data < 0:
|
||||||
|
assert data >= -(2**(self.width-1))-1, f"Signed value too large for probe of width {self.width}"
|
||||||
|
assert data <= (2**(self.width-1))-1, f"Signed value too large for probe of width {self.width}"
|
||||||
|
|
||||||
|
self.interface.write_register(self.base_addr, data)
|
||||||
|
|
||||||
|
def get(self, probe):
|
||||||
|
return self.interface.read_register(self.base_addr)
|
||||||
|
|
||||||
class IOCore:
|
class IOCore:
|
||||||
def __init__(self, config, interface):
|
def __init__(self, config, name, base_addr, interface):
|
||||||
|
self.name = name
|
||||||
|
self.base_addr = base_addr
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
|
||||||
# make sure we have ports defined
|
# make sure we have ports defined
|
||||||
assert ('inputs' in config) or ('outputs' in config), "No input or output ports specified."
|
assert ('inputs' in config) or ('outputs' in config), "No input or output ports specified."
|
||||||
|
|
||||||
# add inputs to core
|
# add input probes to core
|
||||||
address = 0
|
self.probes = []
|
||||||
self.inputs = []
|
probe_base_addr = self.base_addr
|
||||||
if 'inputs' in config:
|
if 'inputs' in config:
|
||||||
for name, width in config["inputs"].items():
|
for name, width in config["inputs"].items():
|
||||||
# make sure inputs are of reasonable width
|
# make sure inputs are of reasonable width
|
||||||
assert width > 0, f"Input {name} must have width greater than zero."
|
assert isinstance(width, int), f"Probe {name} must have integer width."
|
||||||
|
assert width > 0, f"Probe {name} must have positive width."
|
||||||
|
|
||||||
self.inputs.append( {"name": name, "width": width, "address": address} )
|
probe = IOCoreProbe(name, width, "input", probe_base_addr, self.interface)
|
||||||
self.max_rel_addr = address
|
|
||||||
address += 1
|
# add friendly name, so users can do Manta.my_io_core.my_probe.set() for example
|
||||||
|
setattr(self, name, probe)
|
||||||
|
self.probes.append(probe)
|
||||||
|
|
||||||
# add outputs to core
|
self.max_probe_addr = probe_base_addr
|
||||||
self.outputs = []
|
probe_base_addr += 1
|
||||||
|
|
||||||
|
# add output probes to core
|
||||||
if 'outputs' in config:
|
if 'outputs' in config:
|
||||||
for name, width in config["outputs"].items():
|
for name, width in config["outputs"].items():
|
||||||
# make sure inputs are of reasonable width
|
# make sure inputs are of reasonable width
|
||||||
assert width > 0, f"Output {name} must have width greater than zero."
|
assert isinstance(width, int), f"Probe {name} must have integer width."
|
||||||
|
assert width > 0, f"Probe {name} must have positive width."
|
||||||
|
|
||||||
|
probe = IOCoreProbe(name, width, "output", probe_base_addr, self.interface)
|
||||||
|
|
||||||
|
# add friendly name, so users can do Manta.my_io_core.my_probe.set() for example
|
||||||
|
setattr(self, name, probe)
|
||||||
|
self.probes.append(probe)
|
||||||
|
|
||||||
self.outputs.append( {"name": name, "width": width, "address": address} )
|
self.max_probe_addr = probe_base_addr
|
||||||
self.max_rel_addr = address
|
probe_base_addr += 1
|
||||||
address += 1
|
|
||||||
|
|
||||||
def set(self, probe, data):
|
|
||||||
# check that probe actually exists
|
|
||||||
assert (probe in self.inputs) or (probe in self.outputs), "Probe {probe} not found."
|
|
||||||
|
|
||||||
if probe in self.inputs:
|
|
||||||
probe_def = self.inputs[probe]
|
|
||||||
|
|
||||||
elif probe in self.outputs:
|
|
||||||
probe_def = self.outputs[probe]
|
|
||||||
|
|
||||||
|
|
||||||
# check that value is reasonable
|
|
||||||
# (should be an integer between 0 and 2^width - 1)
|
|
||||||
|
|
||||||
# send message
|
|
||||||
addr = probe_def["address"] + self.base_addr
|
|
||||||
|
|
||||||
|
|
||||||
assert isinstance(data, int), "Data must be an integer."
|
|
||||||
if data > 0:
|
|
||||||
assert data <= (2**probe_def["width"]) - 1, f"Unsigned value too large for probe of width {probe_def['width']}"
|
|
||||||
|
|
||||||
elif data < 0:
|
|
||||||
assert data >= -(2**(probe_def["width"]-1))-1, f"Signed value too large for probe of width {probe_def['width']}"
|
|
||||||
assert data <= (2**(probe_def["width"]-1))-1, f"Signed value too large for probe of width {probe_def['width']}"
|
|
||||||
|
|
||||||
self.interface.write_register(addr, data)
|
|
||||||
|
|
||||||
|
|
||||||
def get(self, probe):
|
|
||||||
# check that probe actually exists
|
|
||||||
assert (probe in self.inputs) or (probe in self.outputs), "Probe {probe} not found."
|
|
||||||
|
|
||||||
if probe in self.inputs:
|
|
||||||
probe_def = self.inputs[probe]
|
|
||||||
|
|
||||||
elif probe in self.outputs:
|
|
||||||
probe_def = self.outputs[probe]
|
|
||||||
|
|
||||||
addr = self.base_addr + probe_def["address"]
|
|
||||||
return self.interface.read_register(addr)
|
|
||||||
|
|
||||||
def hdl_inst(self):
|
def hdl_inst(self):
|
||||||
inst_ports = ""
|
# TODO: make this a string comprehension
|
||||||
for input in self.inputs:
|
inst_ports = [f".{probe.name}({probe.name}),\n " for probe in self.probes]
|
||||||
name = input["name"]
|
inst_ports = "".join(inst_ports)
|
||||||
inst_ports += f".{name}({name}),\n "
|
|
||||||
|
|
||||||
for output in self.outputs:
|
|
||||||
name = output["name"]
|
|
||||||
inst_ports += f".{name}({name}),\n "
|
|
||||||
|
|
||||||
inst_ports = inst_ports.rstrip()
|
inst_ports = inst_ports.rstrip()
|
||||||
|
|
||||||
inst = f"""
|
return f"""
|
||||||
{self.name} {self.name}_inst(
|
{self.name} {self.name}_inst(
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
|
|
||||||
|
|
@ -237,12 +226,9 @@ class IOCore:
|
||||||
.valid_o()
|
.valid_o()
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
return inst
|
|
||||||
|
|
||||||
def hdl_def(self):
|
def hdl_def(self):
|
||||||
|
|
||||||
# generate declaration
|
# 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 += ','
|
top_level_ports += ','
|
||||||
declaration = f"""
|
declaration = f"""
|
||||||
|
|
@ -269,43 +255,29 @@ module {self.name} (
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# generate memory handling
|
# generate memory handling
|
||||||
|
|
||||||
# TODO: clean this up, should just do all the probes at once
|
|
||||||
# instead of splitting by input/output
|
|
||||||
read_case_statement_body = ""
|
read_case_statement_body = ""
|
||||||
for input in self.inputs:
|
|
||||||
name = input["name"]
|
|
||||||
width = input["width"]
|
|
||||||
address = input["address"]
|
|
||||||
|
|
||||||
if width == 16:
|
|
||||||
read_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: rdata_o <= {name};\n"
|
|
||||||
|
|
||||||
else:
|
|
||||||
read_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: rdata_o <= {{{16-width}'b0, {name}}};\n"
|
|
||||||
|
|
||||||
write_case_statement_body = ""
|
write_case_statement_body = ""
|
||||||
for output in self.outputs:
|
for probe in self.probes:
|
||||||
name = output["name"]
|
|
||||||
width = output["width"]
|
|
||||||
address = output["address"]
|
|
||||||
|
|
||||||
if width == 16:
|
# add to read block
|
||||||
read_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: rdata_o <= {name};\n"
|
if probe.width == 16:
|
||||||
|
read_case_statement_body += f"\t\t\t\t\t{probe.base_addr}: rdata_o <= {probe.name};\n"
|
||||||
else:
|
|
||||||
read_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: rdata_o <= {{{16-width}'b0, {name}}};\n"
|
|
||||||
|
|
||||||
|
|
||||||
if width == 1:
|
|
||||||
write_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: {name} <= wdata_i[0];\n"
|
|
||||||
|
|
||||||
elif width == 16:
|
|
||||||
write_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: {name} <= wdata_i;\n"
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
write_case_statement_body += f"\t\t\t\t\tBASE_ADDR + {address}: {name} <= wdata_i[{width-1}:0];\n"
|
read_case_statement_body += f"\t\t\t\t\t{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"
|
||||||
|
|
||||||
|
elif probe.width == 16:
|
||||||
|
write_case_statement_body += f"\t\t\t\t\t{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"
|
||||||
|
|
||||||
# remove trailing newline
|
# remove trailing newline
|
||||||
read_case_statement_body = read_case_statement_body.rstrip()
|
read_case_statement_body = read_case_statement_body.rstrip()
|
||||||
write_case_statement_body = write_case_statement_body.rstrip()
|
write_case_statement_body = write_case_statement_body.rstrip()
|
||||||
|
|
@ -322,7 +294,7 @@ always @(posedge clk) begin
|
||||||
|
|
||||||
|
|
||||||
// check if address is valid
|
// check if address is valid
|
||||||
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= BASE_ADDR + {self.max_rel_addr})) begin
|
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= BASE_ADDR + {self.max_probe_addr})) begin
|
||||||
|
|
||||||
if(!rw_i) begin // reads
|
if(!rw_i) begin // reads
|
||||||
case (addr_i)
|
case (addr_i)
|
||||||
|
|
@ -343,34 +315,18 @@ always @(posedge clk) begin
|
||||||
return hdl
|
return hdl
|
||||||
|
|
||||||
def hdl_top_level_ports(self):
|
def hdl_top_level_ports(self):
|
||||||
probes = []
|
ports = []
|
||||||
|
for probe in self.probes:
|
||||||
# generate inputs
|
net_type = "input wire " if probe.direction == "input" else "output reg "
|
||||||
for input in self.inputs:
|
name_def = probe.name if probe.width == 1 else f"[{probe.width-1}:0] {probe.name}"
|
||||||
name = input["name"]
|
ports.append(net_type + name_def)
|
||||||
width = input["width"]
|
|
||||||
|
return ports
|
||||||
if width == 1:
|
|
||||||
probes.append(f"input wire {name}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
probes.append(f"input wire [{width-1}:0] {name}")
|
|
||||||
|
|
||||||
# generate outputs
|
|
||||||
for output in self.outputs:
|
|
||||||
name = output["name"]
|
|
||||||
width = output["width"]
|
|
||||||
|
|
||||||
if width == 1:
|
|
||||||
probes.append(f"output reg {name}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
probes.append(f"output reg [{width-1}:0] {name}")
|
|
||||||
|
|
||||||
return probes
|
|
||||||
|
|
||||||
class LUTRAMCore:
|
class LUTRAMCore:
|
||||||
def __init__(self, config, interface):
|
def __init__(self, config, name, base_addr, interface):
|
||||||
|
self.name = name
|
||||||
|
self.base_addr = base_addr
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
|
||||||
assert "size" in config, "Size not specified for LUT RAM core."
|
assert "size" in config, "Size not specified for LUT RAM core."
|
||||||
|
|
@ -404,7 +360,9 @@ class LUTRAMCore:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
class LogicAnalyzerCore:
|
class LogicAnalyzerCore:
|
||||||
def __init__(self, config, interface):
|
def __init__(self, config, name, base_addr, interface):
|
||||||
|
self.name = name
|
||||||
|
self.base_addr = base_addr
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
|
||||||
# load config
|
# load config
|
||||||
|
|
@ -587,6 +545,7 @@ class Manta:
|
||||||
assert len(config["cores"]) > 0, "Must specify at least one core."
|
assert len(config["cores"]) > 0, "Must specify at least one core."
|
||||||
|
|
||||||
# add cores to self
|
# add cores to self
|
||||||
|
base_addr = 0 # TODO: implement address assignment
|
||||||
self.cores = []
|
self.cores = []
|
||||||
for i, core_name in enumerate(config["cores"]):
|
for i, core_name in enumerate(config["cores"]):
|
||||||
core = config["cores"][core_name]
|
core = config["cores"][core_name]
|
||||||
|
|
@ -596,20 +555,17 @@ class Manta:
|
||||||
|
|
||||||
# add the core to ourself
|
# add the core to ourself
|
||||||
if core["type"] == "logic_analyzer":
|
if core["type"] == "logic_analyzer":
|
||||||
new_core = LogicAnalyzerCore(core, self.interface)
|
new_core = LogicAnalyzerCore(core, core_name, base_addr, self.interface)
|
||||||
|
|
||||||
elif core["type"] == "io":
|
elif core["type"] == "io":
|
||||||
new_core = IOCore(core, self.interface)
|
new_core = IOCore(core, core_name, base_addr, self.interface)
|
||||||
|
|
||||||
elif core["type"] == "lut_ram":
|
elif core["type"] == "lut_ram":
|
||||||
new_core = LUTRAMCore(core, self.interface)
|
new_core = LUTRAMCore(core, core_name, base_addr, self.interface)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unrecognized core type specified for {core_name}.")
|
raise ValueError(f"Unrecognized core type specified for {core_name}.")
|
||||||
|
|
||||||
# TODO: update class defs so that we don't monkey-patch like this. this is not good. i am lazy
|
|
||||||
setattr(new_core, 'name', core_name)
|
|
||||||
|
|
||||||
# add friendly name, so users can do Manta.my_logic_analyzer.read() for example
|
# add friendly name, so users can do Manta.my_logic_analyzer.read() for example
|
||||||
setattr(self, core_name, new_core)
|
setattr(self, core_name, new_core)
|
||||||
self.cores.append(new_core)
|
self.cores.append(new_core)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue