|
| 1 | +# Ref: SH7254R Group User's Manual: Hardware §21.1 §21.4 |
| 2 | +# Document Number: R01UH0480EJ0400 |
| 3 | +# Accession: G00106 |
| 4 | +# |
| 5 | +# This applet implements part of the SuperH AUD-II protocol. This protocol |
| 6 | +# supports rich features such as branch and data tracing. However, it also |
| 7 | +# supports a "RAM Monitoring Mode" wich has simple read and write command. This |
| 8 | +# applet implements the RAM Monitoring Mode read command to be able to dump a |
| 9 | +# target's memory. |
| 10 | + |
| 11 | +import logging |
| 12 | +import sys |
| 13 | + |
| 14 | +from amaranth import * |
| 15 | +from amaranth.lib import io, wiring, stream, cdc, enum |
| 16 | +from amaranth.lib.wiring import In, Out |
| 17 | + |
| 18 | +from glasgow.abstract import AbstractAssembly, GlasgowPin |
| 19 | +from glasgow.applet import GlasgowAppletV2 |
| 20 | + |
| 21 | +class AUDCommand(enum.Enum, shape=8): |
| 22 | + Reset = 0x00 |
| 23 | + Run = 0x01 |
| 24 | + Sync = 0x02 |
| 25 | + Out = 0x03 |
| 26 | + Inp = 0x04 |
| 27 | + |
| 28 | +class AUDComponent(wiring.Component): |
| 29 | + i_stream: In(stream.Signature(8)) |
| 30 | + o_stream: Out(stream.Signature(8)) |
| 31 | + |
| 32 | + def __init__(self, ports, period_cyc): |
| 33 | + self._ports = ports |
| 34 | + self._period_cyc = period_cyc |
| 35 | + |
| 36 | + super().__init__() |
| 37 | + |
| 38 | + def elaborate(self, platform): |
| 39 | + m = Module() |
| 40 | + |
| 41 | + # Add IO buffers |
| 42 | + m.submodules.audsync_buffer = io.Buffer("o", self._ports.audsync) |
| 43 | + m.submodules.audmd_buffer = io.Buffer("o", self._ports.audmd) |
| 44 | + m.submodules.audrst_buffer = io.Buffer("o", self._ports.audrst) |
| 45 | + m.submodules.audck_buffer = io.Buffer("o", self._ports.audck) |
| 46 | + data_pins = [] |
| 47 | + for i, pin in enumerate(self._ports.audata): |
| 48 | + m.submodules[f"audata{i}_buffer"] = pin = io.Buffer("io", pin) |
| 49 | + data_pins.append(pin) |
| 50 | + |
| 51 | + # Create signals for the IO |
| 52 | + audata_o = Signal(4) |
| 53 | + audata_oe = Signal(4) |
| 54 | + audata_i = Signal(4) |
| 55 | + audsync = Signal() |
| 56 | + audrst = Signal() |
| 57 | + audck = Signal() |
| 58 | + |
| 59 | + # Connect the IO buffers to the signals |
| 60 | + m.d.comb += m.submodules.audmd_buffer.o.eq(1) # Always in RAM monitoring mode |
| 61 | + m.d.comb += m.submodules.audrst_buffer.o.eq(audrst) |
| 62 | + m.d.comb += m.submodules.audck_buffer.o.eq(audck) |
| 63 | + m.d.comb += m.submodules.audsync_buffer.o.eq(audsync) |
| 64 | + m.d.comb += [ |
| 65 | + Cat(pin.oe for pin in data_pins).eq(audata_oe), |
| 66 | + Cat(pin.o for pin in data_pins).eq(audata_o), |
| 67 | + ] |
| 68 | + m.submodules += cdc.FFSynchronizer(Cat(pin.i for pin in data_pins), audata_i) |
| 69 | + |
| 70 | + # FSM related signals |
| 71 | + timer = Signal(range(self._period_cyc)) |
| 72 | + data = Signal(4) |
| 73 | + |
| 74 | + # Main State Machine |
| 75 | + with m.FSM(): |
| 76 | + # Receive command and switch to the appropriate handler state |
| 77 | + with m.State("RECV-COMMAND"): |
| 78 | + with m.If(self.i_stream.valid): |
| 79 | + m.d.comb += self.i_stream.ready.eq(1) |
| 80 | + m.d.sync += data.eq(self.i_stream.payload >> 4) |
| 81 | + with m.If((self.i_stream.payload & 0xF) == AUDCommand.Reset): |
| 82 | + m.next = "RESET" |
| 83 | + with m.If((self.i_stream.payload & 0xF) == AUDCommand.Run): |
| 84 | + m.next = "RUN" |
| 85 | + with m.If((self.i_stream.payload & 0xF) == AUDCommand.Sync): |
| 86 | + m.next = "SYNC" |
| 87 | + with m.Elif((self.i_stream.payload & 0xF) == AUDCommand.Out): |
| 88 | + m.next = "OUT" |
| 89 | + with m.Elif((self.i_stream.payload & 0xF) == AUDCommand.Inp): |
| 90 | + m.next = "INP" |
| 91 | + |
| 92 | + # Assert Reset and put pins into a known state |
| 93 | + with m.State("RESET"): |
| 94 | + # Put pins into known state |
| 95 | + m.d.sync += audck.eq(0) |
| 96 | + m.d.sync += audata_oe.eq(0b1111) |
| 97 | + m.d.sync += audata_o.eq(0b0000) |
| 98 | + m.d.sync += audsync.eq(1) |
| 99 | + |
| 100 | + # Put into reset |
| 101 | + m.d.sync += audrst.eq(0) |
| 102 | + m.next = "RECV-COMMAND" |
| 103 | + |
| 104 | + # Release Reset |
| 105 | + with m.State("RUN"): |
| 106 | + # Release reset |
| 107 | + m.d.sync += audrst.eq(1) |
| 108 | + m.next = "RECV-COMMAND" |
| 109 | + |
| 110 | + # Set Sync pin to provided value |
| 111 | + with m.State("SYNC"): |
| 112 | + m.d.sync += audsync.eq(data != 0b0000) |
| 113 | + m.next = "RECV-COMMAND" |
| 114 | + |
| 115 | + # Send 1 nibble of data on the bus, then strobe clock |
| 116 | + with m.State("OUT"): |
| 117 | + m.d.sync += audata_oe.eq(0b1111) # Switch AUDATA pins to output |
| 118 | + m.d.sync += audata_o.eq(data) |
| 119 | + |
| 120 | + # Strobe clock |
| 121 | + m.d.sync += audck.eq(0) |
| 122 | + m.d.sync += timer.eq(self._period_cyc - 1) |
| 123 | + m.next = "OUT-CLOCK-0" |
| 124 | + |
| 125 | + # Wait for clock period to pass, then set clock high |
| 126 | + with m.State("OUT-CLOCK-0"): |
| 127 | + with m.If(timer == 0): |
| 128 | + m.d.sync += audck.eq(1) |
| 129 | + m.d.sync += timer.eq(self._period_cyc - 1) |
| 130 | + m.next = "OUT-CLOCK-1" |
| 131 | + with m.Else(): |
| 132 | + m.d.sync += timer.eq(timer - 1) |
| 133 | + |
| 134 | + # Wait for clock period to pass, then return to command reception |
| 135 | + with m.State("OUT-CLOCK-1"): |
| 136 | + with m.If(timer == 0): |
| 137 | + m.next = "RECV-COMMAND" |
| 138 | + with m.Else(): |
| 139 | + m.d.sync += timer.eq(timer - 1) |
| 140 | + |
| 141 | + # Strobe clock, then read data on the rising edge. Send to PC |
| 142 | + with m.State("INP"): |
| 143 | + m.d.sync += audata_oe.eq(0b0000) |
| 144 | + |
| 145 | + # Strobe clock |
| 146 | + m.d.sync += audck.eq(0) |
| 147 | + m.d.sync += timer.eq(self._period_cyc - 1) |
| 148 | + m.next = "INP-CLOCK-0" |
| 149 | + |
| 150 | + # Wait for clock period to pass, then sample data and set clock high |
| 151 | + with m.State("INP-CLOCK-0"): |
| 152 | + with m.If(timer == 0): |
| 153 | + m.d.sync += audck.eq(1) |
| 154 | + m.d.sync += timer.eq(self._period_cyc - 1) |
| 155 | + |
| 156 | + # Sample data on rising edge |
| 157 | + m.d.sync += data.eq(audata_i) |
| 158 | + |
| 159 | + m.next = "INP-CLOCK-1" |
| 160 | + with m.Else(): |
| 161 | + m.d.sync += timer.eq(timer - 1) |
| 162 | + |
| 163 | + # Wait for clock period to pass, then send data to PC |
| 164 | + with m.State("INP-CLOCK-1"): |
| 165 | + with m.If(timer == 0): |
| 166 | + m.next = "SEND-DATA" |
| 167 | + with m.Else(): |
| 168 | + m.d.sync += timer.eq(timer - 1) |
| 169 | + |
| 170 | + # Send the sampled data to the output stream, return to command reception |
| 171 | + with m.State("SEND-DATA"): |
| 172 | + m.d.comb += self.o_stream.valid.eq(1) |
| 173 | + m.d.comb += self.o_stream.payload.eq(data) |
| 174 | + |
| 175 | + with m.If(self.o_stream.ready): |
| 176 | + m.next = "RECV-COMMAND" |
| 177 | + |
| 178 | + return m |
| 179 | + |
| 180 | +class AUDInterface: |
| 181 | + def __init__(self, logger: logging.Logger, assembly: AbstractAssembly, *, |
| 182 | + audata: GlasgowPin, audsync: GlasgowPin, audck: GlasgowPin, audmd: GlasgowPin, audrst: GlasgowPin, frequency: int): |
| 183 | + self._logger = logger |
| 184 | + self._level = logging.TRACE |
| 185 | + |
| 186 | + ports = assembly.add_port_group(audata=audata, audsync=audsync, audck=audck, audmd=audmd, audrst=audrst) |
| 187 | + component = assembly.add_submodule(AUDComponent( |
| 188 | + ports, |
| 189 | + period_cyc=round(1 / (assembly.sys_clk_period * frequency)), |
| 190 | + )) |
| 191 | + self._pipe = assembly.add_inout_pipe(component.o_stream, component.i_stream) |
| 192 | + |
| 193 | + async def _cmd(self, cmd: AUDCommand, val=0): |
| 194 | + assert val <= 0xF, "Value must be less than 0xF" |
| 195 | + await self._pipe.send([cmd.value | (val << 4)]) |
| 196 | + |
| 197 | + async def reset(self): |
| 198 | + await self._cmd(AUDCommand.Reset) |
| 199 | + |
| 200 | + async def run(self): |
| 201 | + await self._cmd(AUDCommand.Run) |
| 202 | + |
| 203 | + async def out(self, val): |
| 204 | + await self._cmd(AUDCommand.Out, val) |
| 205 | + |
| 206 | + async def inp(self): |
| 207 | + await self._cmd(AUDCommand.Inp) |
| 208 | + |
| 209 | + # This is the only place we need to flush, as we're going to wait for a response |
| 210 | + await self._pipe.flush() |
| 211 | + |
| 212 | + data = await self._pipe.recv(1) |
| 213 | + return data[0] |
| 214 | + |
| 215 | + async def sync(self, val): |
| 216 | + await self._cmd(AUDCommand.Sync, val) |
| 217 | + |
| 218 | + async def init(self): |
| 219 | + await self.reset() |
| 220 | + |
| 221 | + # Strobe clock a couple times |
| 222 | + for i in range(10): |
| 223 | + await self.out(0) |
| 224 | + |
| 225 | + await self.run() |
| 226 | + |
| 227 | + # Strobe clock a couple times |
| 228 | + for i in range(10): |
| 229 | + await self.out(0) |
| 230 | + |
| 231 | + async def read(self, addr, sz=4, timeout=100): |
| 232 | + await self.sync(0) |
| 233 | + await self.out(0) |
| 234 | + |
| 235 | + match sz: |
| 236 | + case 1: |
| 237 | + await self.out(0b1000) # Read byte |
| 238 | + case 2: |
| 239 | + await self.out(0b1001) # Read word |
| 240 | + case 4: |
| 241 | + await self.out(0b1010) # Read longword |
| 242 | + case _: |
| 243 | + raise ValueError("Invalid size, must be 1, 2, or 4 bytes") |
| 244 | + |
| 245 | + # Clock out Addr |
| 246 | + for i in range(8): |
| 247 | + await self.out((addr >> (i * 4)) & 0b1111) |
| 248 | + |
| 249 | + # Wait for data ready |
| 250 | + for _ in range(timeout): |
| 251 | + data = await self.inp() |
| 252 | + if data == 1: |
| 253 | + break |
| 254 | + else: |
| 255 | + raise RuntimeError(f"Timeout waiting for data ready. Got {data:#x}") |
| 256 | + |
| 257 | + # Set AUDSYNC high to indicate we're ready to read |
| 258 | + await self.sync(1) |
| 259 | + await self.inp() |
| 260 | + |
| 261 | + # Clock in the data |
| 262 | + out = 0 |
| 263 | + for i in range(2*sz): |
| 264 | + out |= (await self.inp() << (i * 4)) |
| 265 | + return out.to_bytes(sz, byteorder='big') |
| 266 | + |
| 267 | + |
| 268 | +class AUDApplet(GlasgowAppletV2): |
| 269 | + logger = logging.getLogger(__name__) |
| 270 | + help = "SuperH AUD-II Applet" |
| 271 | + description = """ |
| 272 | + Read memory using the SuperH AUD-II protocol. |
| 273 | + """ |
| 274 | + @classmethod |
| 275 | + def add_build_arguments(cls, parser, access): |
| 276 | + def auto_int(x): |
| 277 | + return int(x, 0) |
| 278 | + |
| 279 | + access.add_voltage_argument(parser) |
| 280 | + |
| 281 | + access.add_pins_argument(parser, "audata", width=4, required=True) |
| 282 | + access.add_pins_argument(parser, "audsync", required=True) |
| 283 | + access.add_pins_argument(parser, "audck", required=True) |
| 284 | + access.add_pins_argument(parser, "audrst", required=True) |
| 285 | + access.add_pins_argument(parser, "audmd", required=True) |
| 286 | + |
| 287 | + parser.add_argument( |
| 288 | + "-f", "--frequency", metavar="FREQ", type=int, default=100, |
| 289 | + help="set clock period to FREQ kHz (default: %(default)s)") |
| 290 | + |
| 291 | + parser.add_argument( |
| 292 | + "-a", "--address", type=auto_int, required=True, |
| 293 | + help="Starting address to read from, e.g. 0x0" |
| 294 | + ) |
| 295 | + parser.add_argument( |
| 296 | + "-s", "--size", type=auto_int, required=True, |
| 297 | + help="Size of the data to read in bytes, e.g. 0x80000" |
| 298 | + ) |
| 299 | + parser.add_argument( |
| 300 | + "-o", "--output", required=True, |
| 301 | + help="Filename to write the output to" |
| 302 | + ) |
| 303 | + |
| 304 | + |
| 305 | + def build(self, args): |
| 306 | + with self.assembly.add_applet(self): |
| 307 | + self.assembly.use_voltage(args.voltage) |
| 308 | + self.aud_iface = AUDInterface( |
| 309 | + self.logger, |
| 310 | + self.assembly, |
| 311 | + audata=args.audata, |
| 312 | + audsync=args.audsync, |
| 313 | + audck=args.audck, |
| 314 | + audmd=args.audmd, |
| 315 | + audrst=args.audrst, |
| 316 | + frequency=args.frequency * 1000) |
| 317 | + |
| 318 | + @staticmethod |
| 319 | + def _show_progress(done, total, status): |
| 320 | + if sys.stdout.isatty(): |
| 321 | + sys.stdout.write("\r\033[0K") |
| 322 | + if done < total: |
| 323 | + sys.stdout.write(f"{done}/{total} bytes done ({done / total * 100:.2f}%)") |
| 324 | + if status: |
| 325 | + sys.stdout.write(f"; {status}") |
| 326 | + sys.stdout.flush() |
| 327 | + |
| 328 | + async def run(self, args): |
| 329 | + self.logger.info("Initializing AUD-II interface") |
| 330 | + await self.aud_iface.init() |
| 331 | + |
| 332 | + self.logger.info("Reading data") |
| 333 | + bs = 4 |
| 334 | + with open(args.output, 'wb') as f: |
| 335 | + for i in range(args.address, args.address + args.size, bs): |
| 336 | + data = await self.aud_iface.read(i, sz=bs) |
| 337 | + f.write(data) |
| 338 | + self._show_progress(i - args.address + bs, args.size, f"Read {data.hex()}") |
| 339 | + |
| 340 | + self.logger.info("Done") |
0 commit comments