mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			drivetypes
			...
			qd
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c8fd984c5c | ||
|  | 14ed696993 | ||
|  | 047521475f | ||
|  | d906d92016 | 
							
								
								
									
										41
									
								
								.appveyor.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								.appveyor.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| version: '{branch}.{build}' | ||||
| clone_depth: 1 | ||||
| skip_tags: true | ||||
|  | ||||
| environment: | ||||
|   MSYSTEM: MINGW32 | ||||
|  | ||||
| init: | ||||
|   - git config --global core.autocrlf input | ||||
|  | ||||
| install: | ||||
|   - set PATH=c:\msys64\mingw32\bin;c:\msys64\usr\bin;c:\msys64\bin;%PATH% | ||||
|   - echo %PATH% | ||||
|   - pacman -S --noconfirm --needed make ninja mingw-w64-i686-libusb mingw-w64-i686-sqlite3 mingw-w64-i686-zlib mingw-w64-i686-gcc zip | ||||
|  | ||||
| build_script: | ||||
|   - make | ||||
|   - zip -9 fluxengine.zip fluxengine.exe brother120tool.exe | ||||
|  | ||||
| artifacts: | ||||
|   - path: fluxengine.zip | ||||
|     name: fluxengine.zip | ||||
|  | ||||
| deploy: | ||||
|   release: FluxEngine Windows client version $(APPVEYOR_BUILD_NUMBER) | ||||
|   description: > | ||||
|     This is an automatically built version of the FluxEngine Windows client | ||||
|     which is generated whenever a significant checkin has happened. It's had | ||||
|     no testing whatsoever. | ||||
|  | ||||
|     To use, download it, put it somewhere, and then run it from a cmd window | ||||
|     or other command line shell. | ||||
|   provider: GitHub | ||||
|   auth_token: | ||||
|     secure: dfJjj7fWCoDUz+Ni11OcNPB/U3TNJFwNA2AsL++ChFjniUsZLlC6SDWHiL/t4FZo | ||||
|   artifact: fluxengine.zip | ||||
|   draft: false | ||||
|   prerelease: false | ||||
|   on: | ||||
|     branch: master | ||||
|  | ||||
| @@ -1,44 +0,0 @@ | ||||
| --- | ||||
| AccessModifierOffset: -4 | ||||
| AlignAfterOpenBracket: DontAlign | ||||
| AlignArrayOfStructures: Left | ||||
| AlignEscapedNewlines: Left | ||||
| AllowAllArgumentsOnNextLine: 'true' | ||||
| AllowAllConstructorInitializersOnNextLine: 'false' | ||||
| AllowAllParametersOfDeclarationOnNextLine: 'true' | ||||
| AllowShortBlocksOnASingleLine: 'true' | ||||
| AllowShortCaseLabelsOnASingleLine: 'false' | ||||
| AllowShortFunctionsOnASingleLine: Empty | ||||
| AllowShortIfStatementsOnASingleLine: Never | ||||
| AllowShortLambdasOnASingleLine: None | ||||
| AllowShortLoopsOnASingleLine: 'false' | ||||
| AlwaysBreakAfterDefinitionReturnType: None | ||||
| AlwaysBreakAfterReturnType: None | ||||
| AlwaysBreakBeforeMultilineStrings: 'true' | ||||
| AlwaysBreakTemplateDeclarations: 'Yes' | ||||
| BinPackArguments: 'false' | ||||
| BinPackParameters: 'false' | ||||
| BreakBeforeBraces: Allman | ||||
| BreakConstructorInitializers: 'AfterColon' | ||||
| BreakInheritanceList: AfterColon | ||||
| BreakStringLiterals: 'true' | ||||
| ColumnLimit: '80' | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' | ||||
| FixNamespaceComments: 'false' | ||||
| IncludeBlocks: Preserve | ||||
| IndentCaseLabels: 'true' | ||||
| IndentWidth: '4' | ||||
| IndentWrappedFunctionNames: 'false' | ||||
| KeepEmptyLinesAtTheStartOfBlocks: 'true' | ||||
| NamespaceIndentation: All | ||||
| PointerAlignment: Left | ||||
| ReflowComments: 'true' | ||||
| SortIncludes: 'false' | ||||
| SortUsingDeclarations: 'true' | ||||
| SpaceAfterTemplateKeyword: 'true' | ||||
| SpaceBeforeAssignmentOperators: 'true' | ||||
| SpaceBeforeCtorInitializerColon: 'false' | ||||
| SpaceBeforeInheritanceColon: 'true' | ||||
| SpaceBeforeParens: ControlStatements | ||||
| SpaceInEmptyParentheses: 'false' | ||||
| ... | ||||
							
								
								
									
										95
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										95
									
								
								.github/workflows/ccpp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,95 +0,0 @@ | ||||
| name: C/C++ CI | ||||
|  | ||||
| on: [push] | ||||
|  | ||||
| jobs: | ||||
|   build-linux: | ||||
|     runs-on: ubuntu-20.04 | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: apt | ||||
|       run: sudo apt update && sudo apt install libudev-dev libsqlite3-dev protobuf-compiler libwxgtk3.0-gtk3-dev libfmt-dev | ||||
|     - name: make | ||||
|       run: CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS" make -j2 -C fluxengine | ||||
|  | ||||
|   build-macos-current: | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake -j2 -C fluxengine | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine.FluxEngine.pkg | ||||
|  | ||||
|   build-windows: | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: msys2 {0} | ||||
|     steps: | ||||
|     - uses: msys2/setup-msys2@v2 | ||||
|       with: | ||||
|         update: true | ||||
|         msystem: MINGW32 | ||||
|         install: >- | ||||
|           diffutils | ||||
|           make | ||||
|           mingw-w64-i686-fmt | ||||
|           mingw-w64-i686-gcc | ||||
|           mingw-w64-i686-libusb | ||||
|           mingw-w64-i686-pkg-config | ||||
|           mingw-w64-i686-protobuf | ||||
|           mingw-w64-i686-sqlite3 | ||||
|           mingw-w64-i686-wxWidgets | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine' | ||||
|         path: 'fluxengine' | ||||
|     - uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: 'davidgiven/fluxengine-testdata' | ||||
|         path: 'fluxengine-testdata' | ||||
|     - name: build | ||||
|       run: make -j2 -C fluxengine | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
|         cd fluxengine | ||||
|         strip fluxengine.exe -o fluxengine-stripped.exe | ||||
|         strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe | ||||
|         makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         cd fluxengine | ||||
|         zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe | ||||
|  | ||||
|     - name: Upload build artifacts | ||||
|       uses: actions/upload-artifact@v2 | ||||
|       with: | ||||
|         name: ${{ github.event.repository.name }}.${{ github.sha }} | ||||
|         path: fluxengine/fluxengine-windows.zip | ||||
							
								
								
									
										119
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										119
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,119 +0,0 @@ | ||||
| name: Autorelease | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - "master" | ||||
|  | ||||
| jobs: | ||||
|   dev-release: | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: msys2 {0} | ||||
|     steps: | ||||
|     - uses: msys2/setup-msys2@v2 | ||||
|       with: | ||||
|         update: true | ||||
|         msystem: MINGW32 | ||||
|         install: >- | ||||
|           diffutils | ||||
|           make | ||||
|           mingw-w64-i686-fmt | ||||
|           mingw-w64-i686-gcc | ||||
|           mingw-w64-i686-libusb | ||||
|           mingw-w64-i686-pkg-config | ||||
|           mingw-w64-i686-protobuf | ||||
|           mingw-w64-i686-sqlite3 | ||||
|           mingw-w64-i686-wxWidgets | ||||
|           mingw-w64-i686-zlib | ||||
|           mingw-w64-i686-nsis | ||||
|           zip | ||||
|           vim | ||||
|     - uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: build | ||||
|       run: | | ||||
|         make -j2 | ||||
|  | ||||
|     - name: nsis | ||||
|       run: | | ||||
|         strip fluxengine.exe -o fluxengine-stripped.exe | ||||
|         strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe | ||||
|         makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi | ||||
|  | ||||
|     - name: zip | ||||
|       run: | | ||||
|         zip -9 fluxengine.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex | ||||
|  | ||||
|     - name: date | ||||
|       run: | | ||||
|         echo "RELEASE_DATE=$(date --rfc-3339=date)" >> ${GITHUB_ENV} | ||||
|  | ||||
|     - name: tag | ||||
|       uses: EndBug/latest-tag@latest | ||||
|       with: | ||||
|         tag-name: dev | ||||
|         force-branch: false | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|     - name: delete-old-assets | ||||
|       uses: mknejp/delete-release-assets@v1 | ||||
|       with: | ||||
|         token: ${{ github.token }} | ||||
|         tag: dev | ||||
|         assets: |  | ||||
|           fluxengine.zip | ||||
|           fluxengine-installer.exe | ||||
|         fail-if-no-assets: false | ||||
|  | ||||
|     - name: release | ||||
|       uses: softprops/action-gh-release@v1 | ||||
|       with: | ||||
|         name: Development build ${{ env.RELEASE_DATE }} | ||||
|         files: | | ||||
|           fluxengine.zip | ||||
|           fluxengine-installer.exe | ||||
|         tag_name: dev | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|   build-macos: | ||||
|     runs-on: macos-latest | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - name: brew | ||||
|       run: brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg | ||||
|     - name: make | ||||
|       run: gmake | ||||
|  | ||||
|     - name: tag | ||||
|       uses: EndBug/latest-tag@latest | ||||
|       with: | ||||
|         tag-name: dev | ||||
|         force-branch: false | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|     - name: delete-old-assets | ||||
|       uses: mknejp/delete-release-assets@v1 | ||||
|       with: | ||||
|         token: ${{ github.token }} | ||||
|         tag: dev | ||||
|         assets: |  | ||||
|           FluxEngine.pkg | ||||
|         fail-if-no-assets: false | ||||
|  | ||||
|     - name: release | ||||
|       uses: softprops/action-gh-release@v1 | ||||
|       with: | ||||
|         name: Development build ${{ env.RELEASE_DATE }} | ||||
|         files: | | ||||
|           FluxEngine.pkg | ||||
|         tag_name: dev | ||||
|       env: | ||||
|         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,2 @@ | ||||
| .obj | ||||
| .project | ||||
| /.ninja* | ||||
| /brother120tool | ||||
| /brother120tool-* | ||||
| /brother240tool | ||||
| /brother240tool-* | ||||
| /fluxengine | ||||
| /fluxengine-* | ||||
| /upgrade-flux-file | ||||
|   | ||||
							
								
								
									
										39
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| language: shell | ||||
| git: | ||||
|     depth: 1 | ||||
|  | ||||
| matrix: | ||||
|     include: | ||||
|         - | ||||
|             os: linux | ||||
|             sudo: false | ||||
|             dist: xenial | ||||
|             compiler: gcc | ||||
|             env: CXX=g++-8 | ||||
|             script: | ||||
|             - make | ||||
|         - | ||||
|             os: osx | ||||
|             osx_image: xcode10.2 | ||||
|             compiler: clang | ||||
|             env: | ||||
|             - HOMEBREW_NO_INSTALL_CLEANUP=1 | ||||
|  | ||||
| addons: | ||||
|     apt: | ||||
|         sources: | ||||
|         - llvm-toolchain-precise-3.8 | ||||
|         - ubuntu-toolchain-r-test | ||||
|         packages: | ||||
|         - ninja-build | ||||
|         - libusb-1.0-0-dev | ||||
|         - libsqlite3-dev | ||||
|         - g++-8 | ||||
|     homebrew: | ||||
|         packages: | ||||
|         - ninja | ||||
|  | ||||
| script: | ||||
| - make | ||||
|  | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,27 +0,0 @@ | ||||
| #include "cyfitter_cfg.h" | ||||
| #include "cydevice_trm.h" | ||||
| #include "cyfitter.h" | ||||
| #include "`$INSTANCE_NAME`_h.h" | ||||
|  | ||||
| void `$INSTANCE_NAME`_Start() | ||||
| { | ||||
|    `$INSTANCE_NAME`_Init(); | ||||
| }     | ||||
|  | ||||
| void `$INSTANCE_NAME`_Stop() | ||||
| { | ||||
|     `$INSTANCE_NAME`_Disable(); | ||||
| } | ||||
|  | ||||
| void `$INSTANCE_NAME`_Init() | ||||
| {     | ||||
|     `$INSTANCE_NAME`_Enable(); | ||||
|      | ||||
| } | ||||
| void `$INSTANCE_NAME`_Enable() | ||||
| { | ||||
| } | ||||
|  | ||||
| void `$INSTANCE_NAME`_Disable() | ||||
| { | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| #if !defined(`$INSTANCE_NAME`_H) | ||||
| #define `$INSTANCE_NAME`_H | ||||
|  | ||||
| #include "cytypes.h" | ||||
| #include "cyfitter.h" | ||||
| #include "CyLib.h"  | ||||
|  | ||||
| #define `$INSTANCE_NAME`_FIFO_PTR	         ((reg8 *) `$INSTANCE_NAME`_dp__F0_REG) | ||||
|  | ||||
|     /* Macros to clear DP FIFOs.*/ | ||||
| #define `$INSTANCE_NAME`_CLEAR do { \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x01u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG)));\ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfeu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG)));\ | ||||
|     } while(0) | ||||
|  | ||||
| /* Macros to set FIFO level mode. See the TRM for details */ | ||||
| #define `$INSTANCE_NAME`_SET_LEVEL_NORMAL \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfbu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
| #define `$INSTANCE_NAME`_SET_LEVEL_MID \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x04u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|  | ||||
| /* Macros to set FIFO to single-buffer mode. */ | ||||
| #define `$INSTANCE_NAME`_SINGLE_BUFFER_SET \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x01u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|  | ||||
| /* Macros to return the FIFO to normal mode. */ | ||||
| #define `$INSTANCE_NAME`_SINGLE_BUFFER_UNSET \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfeu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|      | ||||
| void `$INSTANCE_NAME`_Enable(); | ||||
| void `$INSTANCE_NAME`_Disable(); | ||||
| void `$INSTANCE_NAME`_Start(); | ||||
| void `$INSTANCE_NAME`_Stop(); | ||||
| void `$INSTANCE_NAME`_Init(); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* [] END OF FILE */ | ||||
										
											Binary file not shown.
										
									
								
							| @@ -1,128 +0,0 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|  | ||||
| /* Ultra-simple FIFO in component: a byte is shifted in every clock when req | ||||
|  * is high. */ | ||||
|   | ||||
| module FIFOin (drq, clk, d, req); | ||||
| 	output  drq; | ||||
| 	input   clk; | ||||
| 	input  [7:0] d; | ||||
| 	input  req; | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| wire [7:0] pi; | ||||
| assign pi = d; | ||||
|  | ||||
| wire load; | ||||
| assign load = req; | ||||
|  | ||||
| cy_psoc3_dp #(.cy_dpconfig( | ||||
| { | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM0:    */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM1:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM2:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM3:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM4:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM5:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM6:     */ | ||||
| 	`CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
| 	`CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
| 	`CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
| 	`CS_CMP_SEL_CFGA, /*CFGRAM7:     */ | ||||
| 	8'hFF, 8'h00,	/*CFG9:     */ | ||||
| 	8'hFF, 8'hFF,	/*CFG11-10:     */ | ||||
| 	`SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH, | ||||
| 	`SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL, | ||||
| 	`SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI, | ||||
| 	`SC_SI_A_DEFSI, /*CFG13-12:     */ | ||||
| 	`SC_A0_SRC_PIN, `SC_SHIFT_SL, 1'h0, | ||||
| 	1'h0, `SC_FIFO1_BUS, `SC_FIFO0_ALU, | ||||
| 	`SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN, | ||||
| 	`SC_FB_NOCHN, `SC_CMP1_NOCHN, | ||||
| 	`SC_CMP0_NOCHN, /*CFG15-14:     */ | ||||
| 	10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX, | ||||
| 	`SC_FIFO_LEVEL,`SC_FIFO__SYNC,`SC_EXTCRC_DSBL, | ||||
| 	`SC_WRK16CAT_DSBL /*CFG17-16:     */ | ||||
| } | ||||
| )) dp( | ||||
| 	/* input          */ .clk(clk), | ||||
| 	/* input [02:00]  */ .cs_addr(3'b0),    // Program counter | ||||
| 	/* input          */ .route_si(1'b0),   // Shift in | ||||
| 	/* input          */ .route_ci(1'b0),   // Carry in | ||||
| 	/* input          */ .f0_load(load),    // Load FIFO 0 | ||||
| 	/* input          */ .f1_load(1'b0), 	// Load FIFO 1 | ||||
| 	/* input          */ .d0_load(1'b0), 	// Load Data Register 0 | ||||
| 	/* input          */ .d1_load(1'b0), 	// Load Data Register 1 | ||||
| 	/* output         */ .ce0(), 			// Accumulator 0 = Data register 0 | ||||
| 	/* output         */ .cl0(), 			// Accumulator 0 < Data register 0 | ||||
| 	/* output         */ .z0(), 			// Accumulator 0 = 0 | ||||
| 	/* output         */ .ff0(), 			// Accumulator 0 = FF | ||||
| 	/* output         */ .ce1(), 			// Accumulator [0|1] = Data register 1 | ||||
| 	/* output         */ .cl1(), 			// Accumulator [0|1] < Data register 1 | ||||
| 	/* output         */ .z1(), 			// Accumulator 1 = 0 | ||||
| 	/* output         */ .ff1(), 			// Accumulator 1 = FF | ||||
| 	/* output         */ .ov_msb(), 		// Operation over flow | ||||
| 	/* output         */ .co_msb(), 		// Carry out | ||||
| 	/* output         */ .cmsb(), 			// Carry out | ||||
| 	/* output         */ .so(), 			// Shift out | ||||
|     /* output         */ .f0_bus_stat(drq), // not empty | ||||
| 	/* output         */ .f0_blk_stat(full),// full | ||||
| 	/* output         */ .f1_bus_stat(), 	// FIFO 1 status to uP | ||||
| 	/* output         */ .f1_blk_stat(), 	// FIFO 1 status to DP | ||||
| 	/* input          */ .ci(1'b0), 		// Carry in from previous stage | ||||
| 	/* output         */ .co(), 			// Carry out to next stage | ||||
| 	/* input          */ .sir(1'b0), 		// Shift in from right side | ||||
| 	/* output         */ .sor(), 			// Shift out to right side | ||||
| 	/* input          */ .sil(1'b0), 		// Shift in from left side | ||||
| 	/* output         */ .sol(), 			// Shift out to left side | ||||
| 	/* input          */ .msbi(1'b0), 		// MSB chain in | ||||
| 	/* output         */ .msbo(), 			// MSB chain out | ||||
| 	/* input [01:00]  */ .cei(2'b0),        // Compare equal in from prev stage | ||||
| 	/* output [01:00] */ .ceo(),            // Compare equal out to next stage | ||||
| 	/* input [01:00]  */ .cli(2'b0), 	    // Compare less than in from prv stage | ||||
| 	/* output [01:00] */ .clo(),            // Compare less than out to next stage | ||||
| 	/* input [01:00]  */ .zi(2'b0),         // Zero detect in from previous stage | ||||
| 	/* output [01:00] */ .zo(),             // Zero detect out to next stage | ||||
| 	/* input [01:00]  */ .fi(2'b0), 		// 0xFF detect in from previous stage | ||||
| 	/* output [01:00] */ .fo(), 	        // 0xFF detect out to next stage | ||||
| 	/* input [01:00]  */ .capi(2'b0),	    // Capture in from previous stage | ||||
| 	/* output [01:00] */ .capo(),		    // Capture out to next stage | ||||
| 	/* input          */ .cfbi(1'b0), 		// CRC Feedback in from previous stage | ||||
| 	/* output         */ .cfbo(), 			// CRC Feedback out to next stage | ||||
| 	/* input [07:00]  */ .pi(pi), 		    // Parallel data port | ||||
| 	/* output [07:00] */ .po()              // Parallel data port | ||||
| ); | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| endmodule | ||||
| //`#start footer` -- edit after this line, do not edit this line | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,27 +0,0 @@ | ||||
| #include "cyfitter_cfg.h" | ||||
| #include "cydevice_trm.h" | ||||
| #include "cyfitter.h" | ||||
| #include "`$INSTANCE_NAME`_h.h" | ||||
|  | ||||
| void `$INSTANCE_NAME`_Start() | ||||
| { | ||||
|    `$INSTANCE_NAME`_Init(); | ||||
| }     | ||||
|  | ||||
| void `$INSTANCE_NAME`_Stop() | ||||
| { | ||||
|     `$INSTANCE_NAME`_Disable(); | ||||
| } | ||||
|  | ||||
| void `$INSTANCE_NAME`_Init() | ||||
| {     | ||||
|     `$INSTANCE_NAME`_Enable(); | ||||
|      | ||||
| } | ||||
| void `$INSTANCE_NAME`_Enable() | ||||
| { | ||||
| } | ||||
|  | ||||
| void `$INSTANCE_NAME`_Disable() | ||||
| { | ||||
| } | ||||
| @@ -1,50 +0,0 @@ | ||||
| #if !defined(`$INSTANCE_NAME`_H) | ||||
| #define `$INSTANCE_NAME`_H | ||||
|  | ||||
| #include "cytypes.h" | ||||
| #include "cyfitter.h" | ||||
| #include "CyLib.h"  | ||||
|  | ||||
| #define `$INSTANCE_NAME`_FIFO_PTR	         ((reg8 *) `$INSTANCE_NAME`_dp__F0_REG) | ||||
|  | ||||
|     /* Macros to clear DP FIFOs.*/ | ||||
| #define `$INSTANCE_NAME`_CLEAR do { \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x01u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG)));\ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfeu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG)));\ | ||||
|     } while(0) | ||||
|  | ||||
| /* Macros to set FIFO level mode. See the TRM for details */ | ||||
| #define `$INSTANCE_NAME`_SET_LEVEL_NORMAL \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfbu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
| #define `$INSTANCE_NAME`_SET_LEVEL_MID \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x04u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|  | ||||
| /* Macros to set FIFO to single-buffer mode. */ | ||||
| #define `$INSTANCE_NAME`_SINGLE_BUFFER_SET \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0x01u | \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|  | ||||
| /* Macros to return the FIFO to normal mode. */ | ||||
| #define `$INSTANCE_NAME`_SINGLE_BUFFER_UNSET \ | ||||
|     CY_SET_XTND_REG8(\ | ||||
|         ((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG), 0xfeu & \ | ||||
|         CY_GET_XTND_REG8(((reg8 *) `$INSTANCE_NAME`_dp__DP_AUX_CTL_REG))) | ||||
|      | ||||
| void `$INSTANCE_NAME`_Enable(); | ||||
| void `$INSTANCE_NAME`_Disable(); | ||||
| void `$INSTANCE_NAME`_Start(); | ||||
| void `$INSTANCE_NAME`_Stop(); | ||||
| void `$INSTANCE_NAME`_Init(); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* [] END OF FILE */ | ||||
										
											Binary file not shown.
										
									
								
							| @@ -1,169 +0,0 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| // Generated on 11/16/2017 at 15:44 | ||||
| // Component: FIFOout | ||||
| module FIFOout ( | ||||
|     input req, | ||||
| 	input clk, | ||||
|     output [7:0] d, | ||||
| 	output drq, | ||||
| 	output empty, | ||||
|     output ack | ||||
| ); | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| /* Reads from the FIFO are done based on the FIFO being not empty. */ | ||||
|   | ||||
| wire [7:0] po; | ||||
| assign d = po; | ||||
|  | ||||
| localparam STATE_WAITFORREQ = 0; | ||||
| localparam STATE_READFROMFIFO = 1; | ||||
| localparam STATE_WAITFORNREQ = 2; | ||||
|  | ||||
| reg [1:0] state; | ||||
| wire readfromfifo; | ||||
|  | ||||
| assign ack = (state == STATE_WAITFORNREQ); | ||||
| assign readfromfifo = (state == STATE_READFROMFIFO); | ||||
|  | ||||
| always @(posedge clk) | ||||
| begin | ||||
|     case (state) | ||||
|         /* opcode is not valid; req is low; wait for req to go high. */ | ||||
|         STATE_WAITFORREQ: | ||||
|         begin | ||||
|             if (!empty && req) | ||||
|                 state <= STATE_READFROMFIFO; | ||||
|         end | ||||
|          | ||||
|         /* Fetch a single value from the FIFO. */ | ||||
|         STATE_READFROMFIFO: | ||||
|             state <= STATE_WAITFORNREQ; | ||||
|              | ||||
|         /* opcode is valid; ack is high. Wait for req to go low. */ | ||||
|         STATE_WAITFORNREQ: | ||||
|             if (!req) | ||||
|                 state <= STATE_WAITFORREQ; | ||||
|     endcase | ||||
| end | ||||
|              | ||||
| cy_psoc3_dp #(.cy_dpconfig( | ||||
| { | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM0: idle */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC___F0, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM1: read from fifo */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM2:           */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM3:           */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM4:           */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM5:           */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM6:           */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM7:           */ | ||||
|     8'hFF, 8'h00,  /*CFG9:           */ | ||||
|     8'hFF, 8'hFF,  /*CFG11-10:           */ | ||||
|     `SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH, | ||||
|     `SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL, | ||||
|     `SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI, | ||||
|     `SC_SI_A_DEFSI, /*CFG13-12:           */ | ||||
|     `SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0, | ||||
|     1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS, | ||||
|     `SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN, | ||||
|     `SC_FB_NOCHN, `SC_CMP1_NOCHN, | ||||
|     `SC_CMP0_NOCHN, /*CFG15-14:           */ | ||||
|     10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX, | ||||
|     `SC_FIFO_LEVEL,`SC_FIFO_ASYNC,`SC_EXTCRC_DSBL, | ||||
|     `SC_WRK16CAT_DSBL /*CFG17-16:           */ | ||||
| } | ||||
| )) dp( | ||||
|         /*  input                   */  .reset(1'b0), | ||||
|         /*  input                   */  .clk(clk), | ||||
|         /*  input   [02:00]         */  .cs_addr({2'b0, readfromfifo}), | ||||
|         /*  input                   */  .route_si(1'b0), | ||||
|         /*  input                   */  .route_ci(1'b0), | ||||
|         /*  input                   */  .f0_load(1'b0), | ||||
|         /*  input                   */  .f1_load(1'b0), | ||||
|         /*  input                   */  .d0_load(1'b0), | ||||
|         /*  input                   */  .d1_load(1'b0), | ||||
|         /*  output                  */  .ce0(), | ||||
|         /*  output                  */  .cl0(), | ||||
|         /*  output                  */  .z0(), | ||||
|         /*  output                  */  .ff0(), | ||||
|         /*  output                  */  .ce1(), | ||||
|         /*  output                  */  .cl1(), | ||||
|         /*  output                  */  .z1(), | ||||
|         /*  output                  */  .ff1(), | ||||
|         /*  output                  */  .ov_msb(), | ||||
|         /*  output                  */  .co_msb(), | ||||
|         /*  output                  */  .cmsb(), | ||||
|         /*  output                  */  .so(), | ||||
|         /*  output                  */  .f0_bus_stat(drq), // not full | ||||
|         /*  output                  */  .f0_blk_stat(empty), // empty | ||||
|         /*  output                  */  .f1_bus_stat(), | ||||
|         /*  output                  */  .f1_blk_stat(), | ||||
|          | ||||
|         /* input                    */  .ci(1'b0),     // Carry in from previous stage | ||||
|         /* output                   */  .co(),// Carry out to next stage | ||||
|         /* input                    */  .sir(1'b0),    // Shift in from right side | ||||
|         /* output                   */  .sor(),        // Shift out to right side | ||||
|         /* input                    */  .sil(1'b0),    // Shift in from left side | ||||
|         /* output                   */  .sol(),        // Shift out to left side | ||||
|         /* input                    */  .msbi(1'b0),   // MSB chain in | ||||
|         /* output                   */  .msbo(),       // MSB chain out | ||||
|         /* input [01:00]            */  .cei(2'b0),    // Compare equal in from prev stage | ||||
|         /* output [01:00]           */  .ceo(),        // Compare equal out to next stage | ||||
|         /* input [01:00]            */  .cli(2'b0),    // Compare less than in from prv stage | ||||
|         /* output [01:00]           */  .clo(),        // Compare less than out to next stage | ||||
|         /* input [01:00]            */  .zi(2'b0),     // Zero detect in from previous stage | ||||
|         /* output [01:00]           */  .zo(),         // Zero detect out to next stage | ||||
|         /* input [01:00]            */  .fi(2'b0),     // 0xFF detect in from previous stage | ||||
|         /* output [01:00]           */  .fo(),         // 0xFF detect out to next stage | ||||
|         /* input [01:00]            */  .capi(2'b0),   // Software capture from previous stage | ||||
|         /* output [01:00]           */  .capo(),       // Software capture to next stage | ||||
|         /* input                    */  .cfbi(1'b0),   // CRC Feedback in from previous stage | ||||
|         /* output                   */  .cfbo(),       // CRC Feedback out to next stage | ||||
|         /* input [07:00]            */  .pi(8'b0),     // Parallel data port | ||||
|         /* output [07:00]           */  .po(po)       // Parallel data port | ||||
| ); | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| endmodule | ||||
| //`#start footer` -- edit after this line, do not edit this line | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -28,54 +28,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="1a7e8637-3b6b-4e84-839c-0dfc18fdaf5b"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_DIVIDER" /> | ||||
|         <Data key="desired_freq" value="0" /> | ||||
|         <Data key="desired_unit" value="15" /> | ||||
|         <Data key="divider" value="0" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="Clock_5" /> | ||||
|         <Data key="named_src_direct_connect" value="True" /> | ||||
|         <Data key="netlist_name" value="Clock_5" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="75C2148C-3656-4d8a-846D-0CAE99AB6FF7" /> | ||||
|         <Data key="src_clk_name" value="BUS_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="3f3708ae-fb62-4012-919b-9a3b9a1dfbc2"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_DIVIDER" /> | ||||
|         <Data key="desired_freq" value="0" /> | ||||
|         <Data key="desired_unit" value="15" /> | ||||
|         <Data key="divider" value="0" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="Clock_8" /> | ||||
|         <Data key="named_src_direct_connect" value="True" /> | ||||
|         <Data key="netlist_name" value="Clock_8" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="75C2148C-3656-4d8a-846D-0CAE99AB6FF7" /> | ||||
|         <Data key="src_clk_name" value="BUS_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="4eef02b9-8ad1-43c4-85f1-b3335faa5fc4"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -195,54 +147,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="71bc291d-84a7-40a8-b7b2-1c8a34326a31"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_FREQ" /> | ||||
|         <Data key="desired_freq" value="300" /> | ||||
|         <Data key="desired_unit" value="0" /> | ||||
|         <Data key="divider" value="65536" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="CLOCK300" /> | ||||
|         <Data key="named_src_direct_connect" value="False" /> | ||||
|         <Data key="netlist_name" value="CLOCK300" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="CEF43CFB-0213-49b9-B980-2FFAB81C5B47" /> | ||||
|         <Data key="src_clk_name" value="IMO" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="90ce0c72-9f10-44ef-a049-f0f525d59bea"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_FREQ" /> | ||||
|         <Data key="desired_freq" value="128" /> | ||||
|         <Data key="desired_unit" value="0" /> | ||||
|         <Data key="divider" value="65536" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="CLOCK8" /> | ||||
|         <Data key="named_src_direct_connect" value="False" /> | ||||
|         <Data key="netlist_name" value="CLOCK8" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="CEF43CFB-0213-49b9-B980-2FFAB81C5B47" /> | ||||
|         <Data key="src_clk_name" value="IMO" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="349ffa20-8576-4ac3-9a6f-34ef606de6cf"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -266,29 +170,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="4033c29d-f4bc-4e94-ac95-aa587e869f88/696a0979-21fc-4185-bf38-6c79febcde7a"> | ||||
|         <Data key="check_tolerance" value="False" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="AUTO" /> | ||||
|         <Data key="desired_freq" value="1600000" /> | ||||
|         <Data key="desired_unit" value="0" /> | ||||
|         <Data key="divider" value="40" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="OUTPUT_VOLTAGE_ADC_theACLK" /> | ||||
|         <Data key="netlist_name" value="\OUTPUT_VOLTAGE_ADC:theACLK\" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="61737EF6-3B74-48f9-8B91-F7473A442AE7" /> | ||||
|         <Data key="src_clk_name" value="MASTER_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="6616e828-6611-4893-a674-66c861d79d6c"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -360,53 +241,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="09974428-e912-491f-8d2f-361ba50e7599"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_DIVIDER" /> | ||||
|         <Data key="desired_freq" value="0" /> | ||||
|         <Data key="desired_unit" value="15" /> | ||||
|         <Data key="divider" value="0" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="Clock_6" /> | ||||
|         <Data key="named_src_direct_connect" value="True" /> | ||||
|         <Data key="netlist_name" value="Clock_6" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="75C2148C-3656-4d8a-846D-0CAE99AB6FF7" /> | ||||
|         <Data key="src_clk_name" value="BUS_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="a5825a94-fa18-4e4f-a843-bc687cacbd56/696a0979-21fc-4185-bf38-6c79febcde7a"> | ||||
|         <Data key="check_tolerance" value="False" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="AUTO" /> | ||||
|         <Data key="desired_freq" value="1600000" /> | ||||
|         <Data key="desired_unit" value="0" /> | ||||
|         <Data key="divider" value="40" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="INPUT_VOLTAGE_ADC_theACLK" /> | ||||
|         <Data key="netlist_name" value="\INPUT_VOLTAGE_ADC:theACLK\" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="61737EF6-3B74-48f9-8B91-F7473A442AE7" /> | ||||
|         <Data key="src_clk_name" value="MASTER_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="b762c287-7f87-4b21-982e-84be01dc5115"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -454,30 +288,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="b722443b-8f81-46dc-bf9b-c95eb62bc181"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_DIVIDER" /> | ||||
|         <Data key="desired_freq" value="0" /> | ||||
|         <Data key="desired_unit" value="15" /> | ||||
|         <Data key="divider" value="0" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="Clock_1" /> | ||||
|         <Data key="named_src_direct_connect" value="True" /> | ||||
|         <Data key="netlist_name" value="Clock_1" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="75C2148C-3656-4d8a-846D-0CAE99AB6FF7" /> | ||||
|         <Data key="src_clk_name" value="BUS_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="cb7e877c-9fb4-4fc1-a708-f1e48eb5a68c"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -502,30 +312,6 @@ | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="d3075dc6-05c8-4dc9-9959-cf7014c0e66f"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
|         <Data key="derive_type" value="NAMED_DIVIDER" /> | ||||
|         <Data key="desired_freq" value="0" /> | ||||
|         <Data key="desired_unit" value="15" /> | ||||
|         <Data key="divider" value="0" /> | ||||
|         <Data key="domain" value="DIGITAL" /> | ||||
|         <Data key="enabled" value="True" /> | ||||
|         <Data key="minus_accuracy" value="0.25" /> | ||||
|         <Data key="minus_tolerance" value="5" /> | ||||
|         <Data key="name" value="Clock_7" /> | ||||
|         <Data key="named_src_direct_connect" value="True" /> | ||||
|         <Data key="netlist_name" value="Clock_7" /> | ||||
|         <Data key="placement" value="AUTO" /> | ||||
|         <Data key="plus_accuracy" value="0.25" /> | ||||
|         <Data key="plus_tolerance" value="5" /> | ||||
|         <Data key="scope" value="LOCAL" /> | ||||
|         <Data key="src_clk_id" value="75C2148C-3656-4d8a-846D-0CAE99AB6FF7" /> | ||||
|         <Data key="src_clk_name" value="BUS_CLK" /> | ||||
|         <Data key="start_on_reset" value="True" /> | ||||
|         <Data key="sync_with_bus_clk" value="True" /> | ||||
|         <Data key="user_set_domain" value="False" /> | ||||
|       </Group> | ||||
|       <Group key="e4a53a4c-40e1-4747-a72a-10193ffdf31c"> | ||||
|         <Data key="check_tolerance" value="True" /> | ||||
|         <Data key="clock_version" value="v1" /> | ||||
| @@ -814,69 +600,54 @@ | ||||
|   </Group> | ||||
|   <Group key="Component"> | ||||
|     <Group key="v1"> | ||||
|       <Data key="cy_boot" value="cy_boot_v6_10" /> | ||||
|       <Data key="cy_boot" value="cy_boot_v5_80" /> | ||||
|       <Data key="Em_EEPROM_Dynamic" value="Em_EEPROM_Dynamic_v2_20" /> | ||||
|       <Data key="LIN_Dynamic" value="LIN_Dynamic_v6_0" /> | ||||
|       <Data key="LIN_Dynamic" value="LIN_Dynamic_v5_0" /> | ||||
|     </Group> | ||||
|   </Group> | ||||
|   <Data key="DataVersionKey" value="2" /> | ||||
|   <Group key="DWRInstGuidMapping"> | ||||
|     <Group key="Clock"> | ||||
|       <Data key="0b2f9bbb-00ce-4115-a788-ffb9d046a9e5" value="Clock_4" /> | ||||
|       <Data key="1a7e8637-3b6b-4e84-839c-0dfc18fdaf5b" value="Clock_5" /> | ||||
|       <Data key="3f3708ae-fb62-4012-919b-9a3b9a1dfbc2" value="Clock_8" /> | ||||
|       <Data key="4eef02b9-8ad1-43c4-85f1-b3335faa5fc4" value="Clock_3" /> | ||||
|       <Data key="06c4d5d4-f15f-4b29-a1d0-c24b2e38b1ec" value="CounterClock" /> | ||||
|       <Data key="24cd38f7-f472-4403-837f-86807c8f5333" value="PULSE_CLOCK" /> | ||||
|       <Data key="63ed4137-0b09-4256-8a27-35c9a2653f1a" value="Clock_2" /> | ||||
|       <Data key="66f14071-bddd-4b4d-a9aa-a129cceaa7b6" value="Clock_3" /> | ||||
|       <Data key="71bc291d-84a7-40a8-b7b2-1c8a34326a31" value="CLOCK300" /> | ||||
|       <Data key="90ce0c72-9f10-44ef-a049-f0f525d59bea" value="CLOCK8" /> | ||||
|       <Data key="349ffa20-8576-4ac3-9a6f-34ef606de6cf" value="Clock_1" /> | ||||
|       <Data key="4033c29d-f4bc-4e94-ac95-aa587e869f88/696a0979-21fc-4185-bf38-6c79febcde7a" value="OUTPUT_VOLTAGE_ADC_theACLK" /> | ||||
|       <Data key="6616e828-6611-4893-a674-66c861d79d6c" value="SignalSamplingClock" /> | ||||
|       <Data key="12664fc6-9d70-44b1-8a49-887a292e1b7f" value="Clock_3" /> | ||||
|       <Data key="75187c05-9501-4450-b306-6ccdd3bb77db" value="Clock_5" /> | ||||
|       <Data key="09974428-e912-491f-8d2f-361ba50e7599" value="Clock_6" /> | ||||
|       <Data key="a5825a94-fa18-4e4f-a843-bc687cacbd56/696a0979-21fc-4185-bf38-6c79febcde7a" value="INPUT_VOLTAGE_ADC_theACLK" /> | ||||
|       <Data key="b762c287-7f87-4b21-982e-84be01dc5115" value="Clock_2" /> | ||||
|       <Data key="b0162966-0060-4af5-82d1-fcb491ad7619/be0a0e37-ad17-42ca-b5a1-1a654d736358" value="UART_IntClock" /> | ||||
|       <Data key="b722443b-8f81-46dc-bf9b-c95eb62bc181" value="Clock_1" /> | ||||
|       <Data key="cb7e877c-9fb4-4fc1-a708-f1e48eb5a68c" value="CounterClock" /> | ||||
|       <Data key="d3075dc6-05c8-4dc9-9959-cf7014c0e66f" value="Clock_7" /> | ||||
|       <Data key="e4a53a4c-40e1-4747-a72a-10193ffdf31c" value="Clock_1" /> | ||||
|       <Data key="efd5f185-0c32-4824-ba72-3ceb5356f5a7" value="Clock_1" /> | ||||
|     </Group> | ||||
|     <Group key="Pin"> | ||||
|       <Data key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b" value="Pin_2" /> | ||||
|       <Data key="4a398466-709f-4228-9500-96178658e13e" value="RDATA" /> | ||||
|       <Data key="5a3407c1-b434-4438-a7b4-b9dfd2280495" value="MOTEA" /> | ||||
|       <Data key="8d318d8b-cf7b-4b6b-b02c-ab1c5c49d0ba" value="SW1" /> | ||||
|       <Data key="8fc20a4f-e4d1-44b3-a5d4-546e8628d61e" value="LED" /> | ||||
|       <Data key="12e00eac-69b5-4717-85c8-25ef6b224d4c" value="DEBUG_PINS" /> | ||||
|       <Data key="41e2d8ed-5494-4d8c-8ff7-f4f789cece51" value="REDWC" /> | ||||
|       <Data key="264be2d3-9481-494b-8d9c-c1905a45e9cc" value="FDD" /> | ||||
|       <Data key="472f8fdb-f772-44fb-8897-cc690694237b" value="WDATA" /> | ||||
|       <Data key="736cb12b-c863-43d4-a8f0-42f06023f8b5" value="SIDE1" /> | ||||
|       <Data key="4249c923-fcff-453b-8629-bec6fddd00c1" value="STEP" /> | ||||
|       <Data key="27315b0e-6a8c-4b7f-be77-73ab434fa803" value="Pin_1" /> | ||||
|       <Data key="1425177d-0d0e-4468-8bcc-e638e5509a9b" value="UartRx" /> | ||||
|       <Data key="a5d987c6-e45b-45b9-ad93-656fab06d720" value="TRK00" /> | ||||
|       <Data key="a93ef5b3-00f4-42c0-8fad-0e275a7e2537" value="MOTEB" /> | ||||
|       <Data key="b8380fb7-fdb8-449f-bd8d-c4ca96cdf55a" value="DEBUG_PINS" /> | ||||
|       <Data key="bc2e8987-db82-469c-bf6f-22fd3464cc70" value="DEBUG_PINS" /> | ||||
|       <Data key="bc5d52a1-1b25-4aa0-9ba9-3f81d122772f" value="DEBUG_PINS" /> | ||||
|       <Data key="beca5e2d-f70f-4900-a4db-7eca1ed3126e/8b77a6c4-10a0-4390-971c-672353e2a49c" value="USBFS_Dm" /> | ||||
|       <Data key="beca5e2d-f70f-4900-a4db-7eca1ed3126e/618a72fc-5ddd-4df5-958f-a3d55102db42" value="USBFS_Dp" /> | ||||
|       <Data key="c5367cde-21d5-4866-9a32-d16abfea0c61" value="WPT" /> | ||||
|       <Data key="d19368c5-6855-41bb-a9ff-808938abef00" value="INDEX" /> | ||||
|       <Data key="e9f14b5a-b2bf-49b8-98f3-d7b5a43ace8d" value="DRVSB" /> | ||||
|       <Data key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d" value="LOW_CURRENT" /> | ||||
|       <Data key="e851a3b9-efb8-48be-bbb8-b303b216c393" value="INDEX300" /> | ||||
|       <Data key="e851a3b9-efb8-48be-bbb8-b303b216c393" value="LED" /> | ||||
|       <Data key="e51063a9-4fad-40c7-a06b-7cc4b137dc18" value="DSKCHG" /> | ||||
|       <Data key="ea7ee228-8b3f-426c-8bb8-cd7a81937769" value="DIR" /> | ||||
|       <Data key="ed092b9b-d398-4703-be89-cebf998501f6" value="UartTx" /> | ||||
|       <Data key="f9a7371a-8a7d-4144-8b08-69e3d2a3a663" value="INDEX360" /> | ||||
|       <Data key="fbd1f839-40f9-498e-a48b-5f3048ea5c3d/52f31aa9-2f0a-497d-9a1f-1424095e13e6" value="UART_tx" /> | ||||
|       <Data key="fede1767-f3fd-4021-b3d7-8f9d88f36f9b" value="DRVSA" /> | ||||
|       <Data key="fff78075-035e-43d7-8577-bc5be4d21926" value="WGATE" /> | ||||
| @@ -3966,11 +3737,6 @@ | ||||
|     </Group> | ||||
|   </Group> | ||||
|   <Group key="Pin2"> | ||||
|     <Group key="3e1862bb-be82-47b0-9549-7ebfe76b6f7b"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,6" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="4a398466-709f-4228-9500-96178658e13e"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="1,5" /> | ||||
| @@ -3986,11 +3752,6 @@ | ||||
|         <Data key="Port Format" value="2,2" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="8fc20a4f-e4d1-44b3-a5d4-546e8628d61e"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="2,1" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="12e00eac-69b5-4717-85c8-25ef6b224d4c"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="2,2" /> | ||||
| @@ -4072,11 +3833,6 @@ | ||||
|         <Data key="Port Format" value="1,0" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="27315b0e-6a8c-4b7f-be77-73ab434fa803"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,7" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="1425177d-0d0e-4468-8bcc-e638e5509a9b"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="12,6" /> | ||||
| @@ -4103,32 +3859,6 @@ | ||||
|         <Data key="Port Format" value="2,3" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="bc2e8987-db82-469c-bf6f-22fd3464cc70"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,0" /> | ||||
|       </Group> | ||||
|       <Group key="1"> | ||||
|         <Data key="Port Format" value="0,1" /> | ||||
|       </Group> | ||||
|       <Group key="2"> | ||||
|         <Data key="Port Format" value="0,2" /> | ||||
|       </Group> | ||||
|       <Group key="3"> | ||||
|         <Data key="Port Format" value="0,3" /> | ||||
|       </Group> | ||||
|       <Group key="4"> | ||||
|         <Data key="Port Format" value="0,4" /> | ||||
|       </Group> | ||||
|       <Group key="5"> | ||||
|         <Data key="Port Format" value="0,5" /> | ||||
|       </Group> | ||||
|       <Group key="6"> | ||||
|         <Data key="Port Format" value="0,6" /> | ||||
|       </Group> | ||||
|       <Group key="7"> | ||||
|         <Data key="Port Format" value="0,7" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="bc5d52a1-1b25-4aa0-9ba9-3f81d122772f"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="0,5" /> | ||||
| @@ -4162,14 +3892,9 @@ | ||||
|         <Data key="Port Format" value="12,3" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="e16b5ef8-00d3-40a4-bc1c-194983c8eb3d"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="3,2" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="e851a3b9-efb8-48be-bbb8-b303b216c393"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="3,0" /> | ||||
|         <Data key="Port Format" value="2,1" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="e51063a9-4fad-40c7-a06b-7cc4b137dc18"> | ||||
| @@ -4187,14 +3912,9 @@ | ||||
|         <Data key="Port Format" value="12,7" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="f9a7371a-8a7d-4144-8b08-69e3d2a3a663"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="3,1" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="fbd1f839-40f9-498e-a48b-5f3048ea5c3d/52f31aa9-2f0a-497d-9a1f-1424095e13e6"> | ||||
|       <Group key="0"> | ||||
|         <Data key="Port Format" value="12,7" /> | ||||
|         <Data key="Port Format" value="2,5" /> | ||||
|       </Group> | ||||
|     </Group> | ||||
|     <Group key="fede1767-f3fd-4021-b3d7-8f9d88f36f9b"> | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,96 +0,0 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| // Generated on 12/11/2019 at 21:18 | ||||
| // Component: Sampler | ||||
| module Sampler ( | ||||
| 	output [2:0] debug_state, | ||||
| 	output reg [7:0] opcode, | ||||
| 	output  reg       req, | ||||
| 	input   clock, | ||||
| 	input   index, | ||||
| 	input   rdata, | ||||
| 	input   reset, | ||||
| 	input   sampleclock | ||||
| ); | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| // NOTE: Reset pulse is used in both clock domains, and must be long enough | ||||
| // to be detected in both. | ||||
|  | ||||
| reg [5:0] counter; | ||||
|  | ||||
| reg index_edge; | ||||
| reg rdata_edge; | ||||
|  | ||||
| reg req_toggle; | ||||
|  | ||||
| reg rdata_toggle; | ||||
| reg old_rdata_toggle; | ||||
|  | ||||
| reg index_toggle; | ||||
| reg old_index_toggle; | ||||
|  | ||||
| always @(posedge rdata) | ||||
| begin | ||||
|     rdata_toggle <= ~rdata_toggle; | ||||
| end | ||||
|  | ||||
| always @(posedge index) | ||||
| begin | ||||
|     index_toggle <= ~index_toggle; | ||||
| end | ||||
|  | ||||
| always @(posedge sampleclock) | ||||
| begin | ||||
|     if (reset) | ||||
|     begin | ||||
|         old_rdata_toggle <= 0; | ||||
|         old_index_toggle <= 0; | ||||
|          | ||||
|         index_edge <= 0; | ||||
|         rdata_edge <= 0; | ||||
|         counter <= 0; | ||||
|         req_toggle <= 0; | ||||
|     end | ||||
|     else | ||||
|     begin | ||||
|         /* If data_toggle or index_toggle have changed state, this means that they've | ||||
|          * gone high since the last sampleclock. */ | ||||
|           | ||||
|         index_edge <= index_toggle != old_index_toggle; | ||||
|         old_index_toggle <= index_toggle; | ||||
|          | ||||
|         rdata_edge <= rdata_toggle != old_rdata_toggle; | ||||
|         old_rdata_toggle <= rdata_toggle; | ||||
|          | ||||
|         if (rdata_edge || index_edge || (counter == 6'h3f)) begin | ||||
|             opcode <= { rdata_edge, index_edge, counter }; | ||||
|             req_toggle <= ~req_toggle; | ||||
|             counter <= 1;   /* remember to count this tick */ | ||||
|         end else begin | ||||
|             counter <= counter + 1; | ||||
|         end | ||||
|     end | ||||
| end | ||||
|  | ||||
| reg req_toggle_q; | ||||
|  | ||||
| always @(posedge clock) | ||||
| begin | ||||
|     if (reset) begin | ||||
|         req_toggle_q <= 0; | ||||
|         req <= 0; | ||||
|     end else begin | ||||
|         req_toggle_q <= req_toggle; | ||||
|         req <= (req_toggle != req_toggle_q); | ||||
|     end | ||||
| end | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| endmodule | ||||
| //`#start footer` -- edit after this line, do not edit this line | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| @@ -1,95 +0,0 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| // Generated on 11/24/2019 at 17:25 | ||||
| // Component: Sequencer | ||||
| module Sequencer ( | ||||
| 	output req, /* request new data on leading edge */ | ||||
| 	output wdata, | ||||
|     output [2:0] debug_state, | ||||
| 	input clock, | ||||
| 	input dataclock, /* incoming data on leading edge */ | ||||
| 	input [7:0] opcode, | ||||
|     input index, | ||||
|     input sampleclock, | ||||
|     input reset | ||||
| ); | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| localparam STATE_LOAD = 0; | ||||
| localparam STATE_WRITING = 1; | ||||
|  | ||||
| reg state; | ||||
| reg [5:0] countdown; | ||||
| reg pulsepending; | ||||
|  | ||||
| assign req = (!reset && (state == STATE_LOAD)); | ||||
| assign wdata = (!reset && (state == STATE_WRITING) && (countdown == 0) && pulsepending); | ||||
| assign debug_state = 0; | ||||
|  | ||||
| reg olddataclock; | ||||
| wire dataclocked; | ||||
| always @(posedge clock) olddataclock <= dataclock; | ||||
| assign dataclocked = !olddataclock && dataclock; | ||||
|  | ||||
| reg oldsampleclock; | ||||
| reg sampleclocked; | ||||
|  | ||||
| reg oldindex; | ||||
| wire indexed; | ||||
| always @(posedge clock) oldindex <= index; | ||||
| assign indexed = !oldindex && index; | ||||
|  | ||||
| always @(posedge clock) | ||||
| begin | ||||
|     if (reset) | ||||
|     begin | ||||
|         state <= STATE_LOAD; | ||||
|         countdown <= 0; | ||||
|         pulsepending <= 0; | ||||
|         oldsampleclock <= 0; | ||||
|     end | ||||
|     else | ||||
|     begin | ||||
|         if (!oldsampleclock && sampleclock) | ||||
|             sampleclocked <= 1; | ||||
|         oldsampleclock <= sampleclock; | ||||
|              | ||||
|         case (state) | ||||
|             STATE_LOAD: | ||||
|             begin | ||||
|                 /* A posedge on dataclocked indicates that another opcode has | ||||
|                  * arrived. */ | ||||
|                 if (dataclocked) | ||||
|                 begin | ||||
|                     pulsepending <= opcode[7]; | ||||
|                     if (opcode[5:0] == 0) | ||||
|                         countdown <= 0; | ||||
|                     else | ||||
|                         countdown <= opcode[5:0] - 1; /* compensate for extra tick in state machine */ | ||||
|                      | ||||
|                     state <= STATE_WRITING; | ||||
|                 end | ||||
|             end | ||||
|              | ||||
|             STATE_WRITING: | ||||
|             begin | ||||
|                 if (sampleclocked) | ||||
|                 begin | ||||
|                     if (countdown == 0) | ||||
|                         state <= STATE_LOAD; | ||||
|                     else | ||||
|                         countdown <= countdown - 1; | ||||
|                     sampleclocked <= 0; | ||||
|                 end | ||||
|             end | ||||
|         endcase | ||||
|     end | ||||
| end | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| endmodule | ||||
| //`#start footer` -- edit after this line, do not edit this line | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
										
											Binary file not shown.
										
									
								
							| @@ -1,156 +0,0 @@ | ||||
|  | ||||
| //`#start header` -- edit after this line, do not edit this line | ||||
| `include "cypress.v" | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| // Generated on 11/16/2017 at 15:44 | ||||
| // Component: FIFOout | ||||
| module SuperCounter ( | ||||
| 	input clk, | ||||
|     input reset, | ||||
|     input count, | ||||
|     output [7:0] d, | ||||
| 	output drq, | ||||
| 	output empty, | ||||
|     output ack | ||||
| ); | ||||
|  | ||||
| //`#start body` -- edit after this line, do not edit this line | ||||
|  | ||||
| parameter ResetValue = 0; | ||||
| parameter Delta = 1; | ||||
|      | ||||
| wire [7:0] po; | ||||
| assign d = po; | ||||
|  | ||||
| localparam STATE_RESET = 0; | ||||
| localparam STATE_WAIT = 1; | ||||
| localparam STATE_ADD = 2; | ||||
|  | ||||
| reg oldcount; | ||||
| wire counted; | ||||
| assign counted = count && !oldcount; | ||||
|  | ||||
| always @(posedge clk) oldcount <= count; | ||||
|  | ||||
| wire [2:0] cs; | ||||
| assign cs = reset ? STATE_RESET : (counted ? STATE_ADD : STATE_WAIT); | ||||
|              | ||||
| cy_psoc3_dp #(.d0_init(ResetValue), .d1_init(Delta),  | ||||
| .cy_dpconfig( | ||||
| { | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC___D0, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM0:  STATE_RESET*/ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM1:  STATE_WAIT*/ | ||||
|     `CS_ALU_OP__ADD, `CS_SRCA_A0, `CS_SRCB_D1, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM2:  STATE_ADD*/ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM3:             */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM4:             */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM5:             */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM6:             */ | ||||
|     `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, | ||||
|     `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, | ||||
|     `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, | ||||
|     `CS_CMP_SEL_CFGA, /*CFGRAM7:             */ | ||||
|     8'hFF, 8'h00,  /*CFG9:             */ | ||||
|     8'hFF, 8'hFF,  /*CFG11-10:             */ | ||||
|     `SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH, | ||||
|     `SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL, | ||||
|     `SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI, | ||||
|     `SC_SI_A_DEFSI, /*CFG13-12:             */ | ||||
|     `SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0, | ||||
|     1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS, | ||||
|     `SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN, | ||||
|     `SC_FB_NOCHN, `SC_CMP1_NOCHN, | ||||
|     `SC_CMP0_NOCHN, /*CFG15-14:             */ | ||||
|     10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX, | ||||
|     `SC_FIFO_LEVEL,`SC_FIFO_ASYNC,`SC_EXTCRC_DSBL, | ||||
|     `SC_WRK16CAT_DSBL /*CFG17-16:             */ | ||||
| } | ||||
| )) dp( | ||||
|         /*  input                   */  .reset(1'b0), | ||||
|         /*  input                   */  .clk(clk), | ||||
|         /*  input   [02:00]         */  .cs_addr(cs), | ||||
|         /*  input                   */  .route_si(1'b0), | ||||
|         /*  input                   */  .route_ci(1'b0), | ||||
|         /*  input                   */  .f0_load(1'b0), | ||||
|         /*  input                   */  .f1_load(1'b0), | ||||
|         /*  input                   */  .d0_load(1'b0), | ||||
|         /*  input                   */  .d1_load(1'b0), | ||||
|         /*  output                  */  .ce0(), | ||||
|         /*  output                  */  .cl0(), | ||||
|         /*  output                  */  .z0(), | ||||
|         /*  output                  */  .ff0(), | ||||
|         /*  output                  */  .ce1(), | ||||
|         /*  output                  */  .cl1(), | ||||
|         /*  output                  */  .z1(), | ||||
|         /*  output                  */  .ff1(), | ||||
|         /*  output                  */  .ov_msb(), | ||||
|         /*  output                  */  .co_msb(), | ||||
|         /*  output                  */  .cmsb(), | ||||
|         /*  output                  */  .so(), | ||||
|         /*  output                  */  .f0_bus_stat(), | ||||
|         /*  output                  */  .f0_blk_stat(), | ||||
|         /*  output                  */  .f1_bus_stat(), | ||||
|         /*  output                  */  .f1_blk_stat(), | ||||
|          | ||||
|         /* input                    */  .ci(1'b0),     // Carry in from previous stage | ||||
|         /* output                   */  .co(),// Carry out to next stage | ||||
|         /* input                    */  .sir(1'b0),    // Shift in from right side | ||||
|         /* output                   */  .sor(),        // Shift out to right side | ||||
|         /* input                    */  .sil(1'b0),    // Shift in from left side | ||||
|         /* output                   */  .sol(),        // Shift out to left side | ||||
|         /* input                    */  .msbi(1'b0),   // MSB chain in | ||||
|         /* output                   */  .msbo(),       // MSB chain out | ||||
|         /* input [01:00]            */  .cei(2'b0),    // Compare equal in from prev stage | ||||
|         /* output [01:00]           */  .ceo(),        // Compare equal out to next stage | ||||
|         /* input [01:00]            */  .cli(2'b0),    // Compare less than in from prv stage | ||||
|         /* output [01:00]           */  .clo(),        // Compare less than out to next stage | ||||
|         /* input [01:00]            */  .zi(2'b0),     // Zero detect in from previous stage | ||||
|         /* output [01:00]           */  .zo(),         // Zero detect out to next stage | ||||
|         /* input [01:00]            */  .fi(2'b0),     // 0xFF detect in from previous stage | ||||
|         /* output [01:00]           */  .fo(),         // 0xFF detect out to next stage | ||||
|         /* input [01:00]            */  .capi(2'b0),   // Software capture from previous stage | ||||
|         /* output [01:00]           */  .capo(),       // Software capture to next stage | ||||
|         /* input                    */  .cfbi(1'b0),   // CRC Feedback in from previous stage | ||||
|         /* output                   */  .cfbo(),       // CRC Feedback out to next stage | ||||
|         /* input [07:00]            */  .pi(8'b0),     // Parallel data port | ||||
|         /* output [07:00]           */  .po(po)       // Parallel data port | ||||
| ); | ||||
|  | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
| endmodule | ||||
| //`#start footer` -- edit after this line, do not edit this line | ||||
| //`#end` -- edit above this line, do not edit this line | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								FluxEngine.cydsn/UdbSampler/UdbSampler.cyudb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								FluxEngine.cydsn/UdbSampler/UdbSampler.cyudb
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								FluxEngine.cydsn/UdbSequencer/UdbSequencer.cyudb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								FluxEngine.cydsn/UdbSequencer/UdbSequencer.cyudb
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										18
									
								
								FluxEngine.cydsn/patcher.vbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								FluxEngine.cydsn/patcher.vbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| const READ = 1 | ||||
| const WRITE = 2 | ||||
|  | ||||
| filename = "Generated_Source\PSoC5\USBFS_descr.c" | ||||
| set fso = CreateObject("Scripting.FileSystemObject") | ||||
|  | ||||
| set file = fso.OpenTextFile(filename, READ) | ||||
| text = file.ReadAll | ||||
| file.Close | ||||
|  | ||||
| set r = New RegExp | ||||
| r.MultiLine = True | ||||
| r.Pattern = "/\* +compatibleID.*\n.*" | ||||
| text = r.replace(text, "'W', 'I', 'N', 'U', 'S', 'B', 0, 0,") | ||||
|  | ||||
| set file = fso.CreateTextFile(filename, True) | ||||
| file.Write text | ||||
| file.Close | ||||
							
								
								
									
										317
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										317
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,297 +1,40 @@ | ||||
| # Special Windows settings. | ||||
| PACKAGES = zlib sqlite3 libusb-1.0 | ||||
|  | ||||
| export CFLAGS = -O3 -g --std=c++14 \ | ||||
| 	-ffunction-sections -fdata-sections | ||||
| export LDFLAGS = -O3 | ||||
|  | ||||
| ifeq ($(OS), Windows_NT) | ||||
| 	MINGWBIN = /mingw32/bin | ||||
| 	CCPREFIX = $(MINGWBIN)/ | ||||
| 	LUA = $(MINGWBIN)/lua | ||||
| 	PKG_CONFIG = $(MINGWBIN)/pkg-config | ||||
| 	WX_CONFIG = /usr/bin/sh $(MINGWBIN)/wx-config --static=yes | ||||
| 	PROTOC = $(MINGWBIN)/protoc | ||||
| 	PLATFORM = WINDOWS | ||||
| 	LDFLAGS += \ | ||||
| 		-static | ||||
| 	CXXFLAGS += \ | ||||
| 		-std=c++17 \ | ||||
| 		-fext-numeric-literals \ | ||||
| 		-Wno-deprecated-enum-float-conversion \ | ||||
| 		-Wno-deprecated-enum-enum-conversion | ||||
|  | ||||
| 	# Required to get the gcc run-time libraries on the path. | ||||
| 	export PATH := $(PATH):$(MINGWBIN) | ||||
| 	EXT ?= .exe | ||||
| export CXX = /mingw32/bin/g++ | ||||
| export AR = /mingw32/bin/ar rcs | ||||
| export STRIP = /mingw32/bin/strip | ||||
| export CFLAGS += -I/mingw32/include/libusb-1.0 | ||||
| export LDFLAGS += | ||||
| export LIBS = -static -lz -lsqlite3 -lusb-1.0 | ||||
| export EXTENSION = .exe | ||||
| else | ||||
| export CXX = g++ | ||||
| export AR = ar rcs | ||||
| export STRIP = strip | ||||
| export CFLAGS += $(shell pkg-config --cflags $(PACKAGES)) | ||||
| export LDFLAGS += | ||||
| export LIBS = $(shell pkg-config --libs $(PACKAGES)) | ||||
| export EXTENSION = | ||||
| endif | ||||
|  | ||||
| # Special OSX settings. | ||||
| CFLAGS += -Ilib -Idep/fmt -Iarch | ||||
|  | ||||
| ifeq ($(shell uname),Darwin) | ||||
| 	PLATFORM = OSX | ||||
| 	LDFLAGS += \ | ||||
| 		-framework IOKit \ | ||||
| 		-framework Foundation | ||||
| endif | ||||
| export OBJDIR = .obj | ||||
|  | ||||
| # Check the Make version. | ||||
|  | ||||
|  | ||||
| ifeq ($(findstring 4.,$(MAKE_VERSION)),) | ||||
| $(error You need GNU Make 4.x for this (if you're on OSX, use gmake).) | ||||
| endif | ||||
|  | ||||
| # Normal settings. | ||||
|  | ||||
| OBJDIR ?= .obj | ||||
| CCPREFIX ?= | ||||
| LUA ?= lua | ||||
| CC ?= $(CCPREFIX)gcc | ||||
| CXX ?= $(CCPREFIX)g++ | ||||
| AR ?= $(CCPREFIX)ar | ||||
| PKG_CONFIG ?= pkg-config | ||||
| WX_CONFIG ?= wx-config | ||||
| PROTOC ?= protoc | ||||
| CFLAGS ?= -g -O3 | ||||
| CXXFLAGS += -std=c++17 | ||||
| LDFLAGS ?= | ||||
| PLATFORM ?= UNIX | ||||
| TESTS ?= yes | ||||
| EXT ?= | ||||
| DESTDIR ?= | ||||
| PREFIX ?= /usr/local | ||||
| BINDIR ?= $(PREFIX)/bin | ||||
|  | ||||
| CFLAGS += \ | ||||
| 	-Iarch \ | ||||
| 	-Ilib \ | ||||
| 	-I. \ | ||||
| 	-I$(OBJDIR)/arch \ | ||||
| 	-I$(OBJDIR)/lib \ | ||||
| 	-I$(OBJDIR) \ | ||||
|  | ||||
| LDFLAGS += \ | ||||
| 	-lz \ | ||||
| 	-lfmt | ||||
|  | ||||
| .SUFFIXES: | ||||
| .DELETE_ON_ERROR: | ||||
|  | ||||
| define nl | ||||
|  | ||||
| endef | ||||
|  | ||||
| empty := | ||||
| space := $(empty) $(empty) | ||||
|  | ||||
| use-library = $(eval $(use-library-impl)) | ||||
| define use-library-impl | ||||
| $1: $(call $3_LIB) | ||||
| $1: private LDFLAGS += $(call $3_LDFLAGS) | ||||
| $2: private CFLAGS += $(call $3_CFLAGS) | ||||
| endef | ||||
|  | ||||
| use-pkgconfig = $(eval $(use-pkgconfig-impl)) | ||||
| define use-pkgconfig-impl | ||||
| ifneq ($(strip $(shell $(PKG_CONFIG) $3; echo $$?)),0) | ||||
| $$(error Missing required pkg-config dependency: $3) | ||||
| endif | ||||
|  | ||||
| $(1): private LDFLAGS += $(shell $(PKG_CONFIG) --libs $(3)) | ||||
| $(2): private CFLAGS += $(shell $(PKG_CONFIG) --cflags $(3)) | ||||
| endef | ||||
|  | ||||
| .PHONY: all binaries tests clean install install-bin | ||||
| all: binaries tests docs | ||||
|  | ||||
| PROTOS = \ | ||||
| 	arch/aeslanier/aeslanier.proto \ | ||||
| 	arch/agat/agat.proto \ | ||||
| 	arch/amiga/amiga.proto \ | ||||
| 	arch/apple2/apple2.proto \ | ||||
| 	arch/brother/brother.proto \ | ||||
| 	arch/c64/c64.proto \ | ||||
| 	arch/f85/f85.proto \ | ||||
| 	arch/fb100/fb100.proto \ | ||||
| 	arch/ibm/ibm.proto \ | ||||
| 	arch/macintosh/macintosh.proto \ | ||||
| 	arch/micropolis/micropolis.proto \ | ||||
| 	arch/mx/mx.proto \ | ||||
| 	arch/northstar/northstar.proto \ | ||||
| 	arch/rolandd20/rolandd20.proto \ | ||||
| 	arch/smaky6/smaky6.proto \ | ||||
| 	arch/tids990/tids990.proto \ | ||||
| 	arch/victor9k/victor9k.proto \ | ||||
| 	arch/zilogmcz/zilogmcz.proto \ | ||||
| 	lib/common.proto \ | ||||
| 	lib/config.proto \ | ||||
| 	lib/decoders/decoders.proto \ | ||||
| 	lib/drive.proto \ | ||||
| 	lib/encoders/encoders.proto \ | ||||
| 	lib/fl2.proto \ | ||||
| 	lib/fluxsink/fluxsink.proto \ | ||||
| 	lib/fluxsource/fluxsource.proto \ | ||||
| 	lib/imagereader/imagereader.proto \ | ||||
| 	lib/imagewriter/imagewriter.proto \ | ||||
| 	lib/layout.proto \ | ||||
| 	lib/usb/usb.proto \ | ||||
| 	lib/vfs/vfs.proto \ | ||||
| 	tests/testproto.proto \ | ||||
|  | ||||
| PROTO_HDRS = $(patsubst %.proto, $(OBJDIR)/%.pb.h, $(PROTOS)) | ||||
| PROTO_SRCS = $(patsubst %.proto, $(OBJDIR)/%.pb.cc, $(PROTOS)) | ||||
| PROTO_OBJS = $(patsubst %.cc, %.o, $(PROTO_SRCS)) | ||||
| PROTO_CFLAGS = $(shell $(PKG_CONFIG) --cflags protobuf) | ||||
| $(PROTO_SRCS): | $(PROTO_HDRS) | ||||
| $(PROTO_OBJS): CFLAGS += $(PROTO_CFLAGS) | ||||
| PROTO_LIB = $(OBJDIR)/libproto.a | ||||
| $(PROTO_LIB): $(PROTO_OBJS) | ||||
| PROTO_LDFLAGS = $(shell $(PKG_CONFIG) --libs protobuf) -pthread $(PROTO_LIB) | ||||
| .PRECIOUS: $(PROTO_HDRS) $(PROTO_SRCS) | ||||
|  | ||||
| include dep/agg/build.mk | ||||
| include dep/libusbp/build.mk | ||||
| include dep/stb/build.mk | ||||
| include dep/emu/build.mk | ||||
| include dep/fatfs/build.mk | ||||
| include dep/adflib/build.mk | ||||
| include dep/hfsutils/build.mk | ||||
| include scripts/build.mk | ||||
|  | ||||
| include lib/build.mk | ||||
| include arch/build.mk | ||||
| include src/build.mk | ||||
| include src/gui/build.mk | ||||
| include tools/build.mk | ||||
| include tests/build.mk | ||||
|  | ||||
| do-encodedecodetest = $(eval $(do-encodedecodetest-impl)) | ||||
| define do-encodedecodetest-impl | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.flux.encodedecode | ||||
| $(OBJDIR)/$1$3.flux.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST $1 flux $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 flux $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| tests: $(OBJDIR)/$1$3.scp.encodedecode | ||||
| $(OBJDIR)/$1$3.scp.encodedecode: scripts/encodedecodetest.sh $(FLUXENGINE_BIN) $2 | ||||
| 	@mkdir -p $(dir $$@) | ||||
| 	@echo ENCODEDECODETEST $1 scp $(FLUXENGINE_BIN) $2 $3 | ||||
| 	@scripts/encodedecodetest.sh $1 scp $(FLUXENGINE_BIN) $2 $3 > $$@ | ||||
|  | ||||
| endef | ||||
|  | ||||
| $(call do-encodedecodetest,agat) | ||||
| $(call do-encodedecodetest,amiga) | ||||
| $(call do-encodedecodetest,apple2,,--140) | ||||
| $(call do-encodedecodetest,atarist,,--360) | ||||
| $(call do-encodedecodetest,atarist,,--370) | ||||
| $(call do-encodedecodetest,atarist,,--400) | ||||
| $(call do-encodedecodetest,atarist,,--410) | ||||
| $(call do-encodedecodetest,atarist,,--720) | ||||
| $(call do-encodedecodetest,atarist,,--740) | ||||
| $(call do-encodedecodetest,atarist,,--800) | ||||
| $(call do-encodedecodetest,atarist,,--820) | ||||
| $(call do-encodedecodetest,bk) | ||||
| $(call do-encodedecodetest,brother,,--120) | ||||
| $(call do-encodedecodetest,brother,,--240) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--171) | ||||
| $(call do-encodedecodetest,commodore,scripts/commodore1541_test.textpb,--192) | ||||
| $(call do-encodedecodetest,commodore,,--800) | ||||
| $(call do-encodedecodetest,commodore,,--1620) | ||||
| $(call do-encodedecodetest,hplif,,--264) | ||||
| $(call do-encodedecodetest,hplif,,--616) | ||||
| $(call do-encodedecodetest,hplif,,--770) | ||||
| $(call do-encodedecodetest,ibm,,--1200) | ||||
| $(call do-encodedecodetest,ibm,,--1232) | ||||
| $(call do-encodedecodetest,ibm,,--1440) | ||||
| $(call do-encodedecodetest,ibm,,--1680) | ||||
| $(call do-encodedecodetest,ibm,,--180) | ||||
| $(call do-encodedecodetest,ibm,,--160) | ||||
| $(call do-encodedecodetest,ibm,,--320) | ||||
| $(call do-encodedecodetest,ibm,,--360) | ||||
| $(call do-encodedecodetest,ibm,,--720) | ||||
| $(call do-encodedecodetest,mac,scripts/mac400_test.textpb,--400) | ||||
| $(call do-encodedecodetest,mac,scripts/mac800_test.textpb,--800) | ||||
| $(call do-encodedecodetest,n88basic) | ||||
| $(call do-encodedecodetest,rx50) | ||||
| $(call do-encodedecodetest,tids990) | ||||
| $(call do-encodedecodetest,victor9k,,--612) | ||||
| $(call do-encodedecodetest,victor9k,,--1224) | ||||
|  | ||||
| do-corpustest = $(eval $(do-corpustest-impl)) | ||||
| define do-corpustest-impl | ||||
|  | ||||
| tests: $(OBJDIR)/corpustest/$2 | ||||
| $(OBJDIR)/corpustest/$2: $(FLUXENGINE_BIN) \ | ||||
| 		../fluxengine-testdata/data/$1 ../fluxengine-testdata/data/$2 | ||||
| 	@mkdir -p $(OBJDIR)/corpustest | ||||
| 	@echo CORPUSTEST $1 $2 $3 | ||||
| 	@$(FLUXENGINE_BIN) read $3 -s ../fluxengine-testdata/data/$1 -o $$@ > $$@.log | ||||
| 	@cmp $$@ ../fluxengine-testdata/data/$2 | ||||
|  | ||||
| endef | ||||
|  | ||||
| ifneq ($(wildcard ../fluxengine-testdata/data),) | ||||
|  | ||||
| $(call do-corpustest,amiga.flux,amiga.adf,amiga) | ||||
| $(call do-corpustest,atarist360.flux,atarist360.st,atarist --360) | ||||
| $(call do-corpustest,atarist720.flux,atarist720.st,atarist --720) | ||||
| $(call do-corpustest,brother120.flux,brother120.img,brother --120) | ||||
| $(call do-corpustest,cmd-fd2000.flux,cmd-fd2000.img,commodore --1620) | ||||
| $(call do-corpustest,ibm1232.flux,ibm1232.img,ibm --1232) | ||||
| $(call do-corpustest,ibm1440.flux,ibm1440.img,ibm --1440) | ||||
| $(call do-corpustest,mac800.flux,mac800.dsk,mac --800) | ||||
| $(call do-corpustest,micropolis315.flux,micropolis315.img,micropolis --315) | ||||
| $(call do-corpustest,northstar87-synthetic.flux,northstar87-synthetic.nsi,northstar --87 --drive.tpi=48) | ||||
| $(call do-corpustest,northstar175-synthetic.flux,northstar175-synthetic.nsi,northstar --175 --drive.tpi=48) | ||||
| $(call do-corpustest,northstar350-synthetic.flux,northstar350-synthetic.nsi,northstar --350 --drive.tpi=48) | ||||
| $(call do-corpustest,victor9k_ss.flux,victor9k_ss.img,victor9k --612) | ||||
| $(call do-corpustest,victor9k_ds.flux,victor9k_ds.img,victor9k --1224) | ||||
|  | ||||
| endif | ||||
|  | ||||
| $(OBJDIR)/%.a: | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo AR $@ | ||||
| 	@$(AR) rc $@ $^ | ||||
|  | ||||
| %.exe: | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo LINK $@ | ||||
| 	@$(CXX) -o $@ $^ $(LDFLAGS) $(LDFLAGS) | ||||
|  | ||||
| $(OBJDIR)/%.o: %.cpp | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo CXX $< | ||||
| 	@$(CXX) $(CFLAGS) $(CXXFLAGS) -MMD -MP -MF $(@:.o=.d) -c -o $@ $< | ||||
|  | ||||
| $(OBJDIR)/%.o: %.cc | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo CXX $< | ||||
| 	@$(CXX) $(CFLAGS) $(CXXFLAGS) -MMD -MP -MF $(@:.o=.d) -c -o $@ $< | ||||
|  | ||||
| $(OBJDIR)/%.o: $(OBJDIR)/%.cc | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo CXX $< | ||||
| 	@$(CXX) $(CFLAGS) $(CXXFLAGS) -MMD -MP -MF $(@:.o=.d) -c -o $@ $< | ||||
|  | ||||
| $(OBJDIR)/%.o: %.c | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo CC $< | ||||
| 	@$(CC) $(CFLAGS) $(CFLAGS) -MMD -MP -MF $(@:.o=.d) -c -o $@ $< | ||||
|  | ||||
| $(OBJDIR)/%.pb.h: %.proto | ||||
| 	@mkdir -p $(dir $@) | ||||
| 	@echo PROTOC $@ | ||||
| 	@$(PROTOC) -I. --cpp_out=$(OBJDIR) $< | ||||
| all: .obj/build.ninja | ||||
| 	@ninja -f .obj/build.ninja -v | ||||
|  | ||||
| clean: | ||||
| 	rm -rf $(OBJDIR) | ||||
| 	@echo CLEAN | ||||
| 	@rm -rf $(OBJDIR) | ||||
|  | ||||
| install: install-bin # install-man install-docs ... | ||||
| .obj/build.ninja: mkninja.sh Makefile | ||||
| 	@echo MKNINJA $@ | ||||
| 	@mkdir -p $(OBJDIR) | ||||
| 	@sh $< > $@ | ||||
|  | ||||
| install-bin: fluxengine$(EXT) fluxengine-gui$(EXT) brother120tool$(EXT) brother240tool$(EXT) upgrade-flux-file$(EXT) | ||||
| 	install -d "$(DESTDIR)$(BINDIR)" | ||||
| 	for target in $^; do \ | ||||
| 		install $$target "$(DESTDIR)$(BINDIR)/$$target"; \ | ||||
| 	done | ||||
|  | ||||
| -include $(OBJS:%.o=%.d) | ||||
|   | ||||
							
								
								
									
										190
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										190
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,17 +2,7 @@ FluxEngine | ||||
| ========== | ||||
|  | ||||
| (If you're reading this on GitHub, the formatting's a bit messed up. [Try the | ||||
| version on cowlark.com instead.](http://cowlark.com/fluxengine/)) | ||||
|  | ||||
| **Breaking news!** As of 2022-09-09, there's new [filesystem | ||||
| support](doc/filesystem.md). Read (and sometimes write) files directly from | ||||
| (and to) your disks, with eight different file systems! It works in the GUI, | ||||
| too, which is available for Linux (and other Unix clones), Windows and OSX. See | ||||
| the details below. | ||||
|  | ||||
| <div style="text-align: center"> | ||||
| <a href="doc/screenshot.jpg"><img src="doc/screenshot.jpg" style="width:60%" alt="screenshot of the GUI in action"></a> | ||||
| </div> | ||||
| version on cowlark.com instead.](http://cowlark.com/fluxengine/) | ||||
|  | ||||
| What? | ||||
| ----- | ||||
| @@ -34,13 +24,17 @@ Don't believe me? Watch the demo reel! | ||||
| <iframe width="373" height="210" src="https://www.youtube.com/embed/m_s1iw8eW7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> | ||||
| </div> | ||||
|  | ||||
| **New!** The FluxEngine client software now works with | ||||
| [GreaseWeazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you | ||||
| can't find a PSoC5 development kit, or don't want to use the Cypress Windows | ||||
| tools for programming it, you can use one of these instead. Very nearly all | ||||
| FluxEngine features are available with the GreaseWeazle and it works out-of-the | ||||
| box. See the [dedicated GreaseWeazle documentation page](doc/greaseweazle.md) | ||||
| for more information. | ||||
| **Important note.** On 2019-02-09 I did a hardware redesign and moved the pins on | ||||
| the board. Sorry for the inconvenience, but it means you don't have to modify | ||||
| the board any more to make it work. If you built the hardware prior to then, | ||||
| you'll need to adjust it. | ||||
|  | ||||
| **Another important note.** On 2019-07-03 I've revamped the build process and | ||||
| the (command line) user interface. It should be much nicer now, not least in | ||||
| that there's a single client binary with all the functionality in it. The | ||||
| interface is a little different, but not much. The build process is now | ||||
| better (simpler). See [the building](doc/building.md) and | ||||
| [using](doc/using.md) pages for more details. | ||||
|  | ||||
| Where? | ||||
| ------ | ||||
| @@ -65,83 +59,56 @@ following friendly articles: | ||||
|   - [Using a FluxEngine](doc/using.md) ∾ what to do with your new hardware ∾ | ||||
|     flux files and image files ∾ knowing what you're doing | ||||
|  | ||||
|   - [Using GreaseWeazle hardware with the FluxEngine client | ||||
|     software](doc/greaseweazle.md) ∾ what works ∾ what doesn't work ∾ where to | ||||
|     go for help | ||||
|  | ||||
|   - [Configuring for your drive](doc/drives.md) ∾ but I don't have a 80 track | ||||
|     drive! ∾ reading and writing 40 track disks ∾ Shugart and Apple II | ||||
|  | ||||
|   - [Direct filesystem access](doc/filesystem.md) ∾ imaging files is a pain | ||||
|     ∾ accessing files directly ∾ features and limitation ∾ it works on disk | ||||
|     images too, you say? | ||||
|      | ||||
|   - [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact | ||||
|     science ∾ the sector map ∾ clock detection and the histogram | ||||
|  | ||||
|   - [Disk densities](doc/driveresponse.md) ∾ what's the difference between an HD | ||||
|     and DD disk? ∾ you can't do that with that ∾ measuring your drive's ability to | ||||
|     work with exotic formats ∾ I think my drive is broken | ||||
|   - [Troubleshooting dubious disks](doc/problems.md) ∾ it's not an exact science ∾ | ||||
|     the sector map ∾ clock detection and the histogram | ||||
|  | ||||
| Which? | ||||
| ------ | ||||
|  | ||||
| The current support state is as follows. | ||||
|  | ||||
| Dinosaurs (🦖) have yet to be observed in real life --- I've written the encoder | ||||
| and/or decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have | ||||
| real, physical disks in my hand to test the capture process, or hardware to | ||||
| verify that written disks work. | ||||
| Dinosaurs (🦖) have yet to be observed in real life --- I've written the | ||||
| decoder based on Kryoflux (or other) dumps I've found. I don't (yet) have | ||||
| real, physical disks in my hand to test the capture process. | ||||
|  | ||||
| Unicorns (🦄) are completely real --- this means that I've read actual, physical | ||||
| disks with these formats and/or written real, physical disks and then used them | ||||
| on real hardware, and so know they work (or had reports from people who've had | ||||
| it work). | ||||
| Unicorns (🦄) are completely real --- this means that I've read actual, | ||||
| physical disks with these formats and so know they work (or had reports from | ||||
| people who've had it work). | ||||
|  | ||||
| If a filesystem is listed, this means that FluxEngine natively supports that | ||||
| particular filesystem and can read (and sometimes write, support varies) files | ||||
| directly from disks, flux files or disk images. Some formats have multiple | ||||
| choices because they can store multiple types of file system. | ||||
| ### Old disk formats | ||||
|  | ||||
| <!-- FORMATSSTART --> | ||||
| <!-- This section is automatically generated. Do not edit. --> | ||||
|  | ||||
| | Profile | Format | Read? | Write? | Filesystem? | | ||||
| |:--------|:-------|:-----:|:------:|:------------| | ||||
| | [`acornadfs`](doc/disk-acornadfs.md) | Acorn ADFS: BBC Micro, Archimedes | 🦖 |  |  | | ||||
| | [`acorndfs`](doc/disk-acorndfs.md) | Acorn DFS: Acorn Atom, BBC Micro series | 🦄 |  | ACORNDFS  | | ||||
| | [`aeslanier`](doc/disk-aeslanier.md) | AES Lanier "No Problem": 616kB 5.25" 77-track SSDD hard sectored | 🦖 |  |  | | ||||
| | [`agat`](doc/disk-agat.md) | Agat: 840kB 5.25" 80-track DS | 🦖 | 🦖 |  | | ||||
| | [`amiga`](doc/disk-amiga.md) | Amiga: 880kB 3.5" DSDD | 🦄 | 🦄 | AMIGAFFS  | | ||||
| | [`ampro`](doc/disk-ampro.md) | Ampro Little Board: CP/M | 🦖 |  | CPMFS  | | ||||
| | [`apple2`](doc/disk-apple2.md) | Apple II: Prodos, Appledos, and CP/M | 🦄 | 🦄 | APPLEDOS CPMFS PRODOS  | | ||||
| | [`atarist`](doc/disk-atarist.md) | Atari ST: Almost PC compatible | 🦄 | 🦄 |  | | ||||
| | [`bk`](doc/disk-bk.md) | BK: 800kB 5.25"/3.5" 80-track 10-sector DSDD | 🦖 | 🦖 |  | | ||||
| | [`brother`](doc/disk-brother.md) | Brother word processors: GCR family | 🦄 | 🦄 | BROTHER120 FATFS  | | ||||
| | [`commodore`](doc/disk-commodore.md) | Commodore: 1541, 1581, 8050 and variations | 🦄 | 🦄 | CBMFS  | | ||||
| | [`eco1`](doc/disk-eco1.md) | VDS Eco1: CP/M; 1210kB 77-track mixed format DSHD | 🦖 |  | CPMFS  | | ||||
| | [`epsonpf10`](doc/disk-epsonpf10.md) | Epson PF-10: CP/M; 3.5" 40-track DSDD | 🦖 |  | CPMFS  | | ||||
| | [`f85`](doc/disk-f85.md) | Durango F85: 461kB 5.25" 77-track SS | 🦖 |  |  | | ||||
| | [`fb100`](doc/disk-fb100.md) | Brother FB-100: 100kB 3.5" 40-track SSSD | 🦖 |  |  | | ||||
| | [`hplif`](doc/disk-hplif.md) | Hewlett-Packard LIF: a variety of disk formats used by HP | 🦄 | 🦄 | LIF  | | ||||
| | [`ibm`](doc/disk-ibm.md) | IBM PC: Generic PC 3.5"/5.25" disks | 🦄 | 🦄 | FATFS  | | ||||
| | [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 |  | CPMFS  | | ||||
| | [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS  | | ||||
| | [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 |  | | ||||
| | [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 |  |  | | ||||
| | [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25"/3.5" 77-track 26-sector DSHD | 🦄 | 🦄 |  | | ||||
| | [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 |  | | ||||
| | [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE  | | ||||
| | [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦖 |  |  | | ||||
| | [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 |  | | ||||
| | [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 |  | SMAKY6  | | ||||
| | [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 |  | | ||||
| | [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M |  |  | CPMFS  | | ||||
| | [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 |  | | ||||
| | [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 |  |  | | ||||
| | Format                                   | Read? | Write? | Notes | | ||||
| |:-----------------------------------------|:-----:|:------:|-------| | ||||
| | IBM PC compatible                        |  🦄   |        | and compatibles (like the Atari ST) | | ||||
| | [Acorn ADFS](doc/disk-acornadfs.md)      |  🦄   |        | single- and double- sided           | | ||||
| | [Acorn DFS](doc/disk-acorndfs.md)        |  🦄   |        |                                     | | ||||
| | [Ampro Little Board](doc/disk-ampro.md)  |  🦖   |        |                                     | | ||||
| | [Apple II DOS 3.3](doc/disk-apple2.md)   |  🦄   |        | doesn't do logical sector remapping | | ||||
| | [Amiga](doc/disk-amiga.md)               |  🦄   |        |                                     | | ||||
| | [Commodore 64 1541](doc/disk-c64.md)     |  🦖   |        | and probably the other GCR formats  | | ||||
| | [Brother 120kB](doc/disk-brother.md)     |  🦄   |        |                                     | | ||||
| | [Brother 240kB](doc/disk-brother.md)     |  🦄   |   🦄   |                                     | | ||||
| | [Brother FB-100](doc/disk-fb100.md)      |  🦖   |        | Tandy Model 100, Husky Hunter, knitting machines | | ||||
| | [Macintosh 800kB](doc/disk-macintosh.md) |  🦄   |        | and probably the 400kB too          | | ||||
| | [TRS-80](doc/disk-trs80.md)              |  🦖   |        | a minor variation of the IBM scheme | | ||||
| {: .datatable } | ||||
|  | ||||
| <!-- FORMATSEND --> | ||||
| ### Even older disk formats | ||||
|  | ||||
| These formats are for particularly old, weird architectures, even by the | ||||
| standards of floppy disks. They've largely been implemented from single flux | ||||
| files with no access to physical hardware. Typically the reads were pretty | ||||
| bad and I've had to make a number of guesses as to how things work. They do, | ||||
| at least, check the CRC so what data's there is probably good. | ||||
|  | ||||
| | Format                                   | Read? | Write? | Notes | | ||||
| |:-----------------------------------------|:-----:|:------:|-------| | ||||
| | [AES Superplus / No Problem](doc/disk-aeslanier.md) |  🦖   | | hard sectors! | | ||||
| | [Durango F85](doc/disk-durangof85.md)    |  🦖   |        | 5.25" | | ||||
| | [DVK MX](doc/disk-mx.md)                 |  🦖   |        | Soviet PDP-11 clone | | ||||
| | [Victor 9000](doc/disk-victor9k.md)      |  🦖   |        | 8-inch        | | ||||
| | [Zilog MCZ](doc/disk-zilogmcz.md)        |  🦖   |        | 8-inch _and_ hard sectors | | ||||
| {: .datatable } | ||||
|  | ||||
| ### Notes | ||||
|  | ||||
| @@ -158,7 +125,7 @@ choices because they can store multiple types of file system. | ||||
|     There hasn't been a lot of demand for this yet; if you have a pressing | ||||
|     need to write weird disks, [please | ||||
|     ask](https://github.com/davidgiven/fluxengine/issues/new). I haven't | ||||
|     implemented write support for PC disks because they're boring and I'm lazy, | ||||
|     implement write support for PC disks because they're boring and I'm lazy, | ||||
|     and also because they vary so much that figuring out how to specify them | ||||
|     is hard. | ||||
|  | ||||
| @@ -212,53 +179,18 @@ There may or may not be anything interesting there. | ||||
| License | ||||
| ------- | ||||
|  | ||||
| Everything here _except the contents of the `dep` directory_ is © 2022 The | ||||
| FluxEngine Authors (mostly me, David Given; see the VCS history for the other | ||||
| people) and is licensed under the MIT open source license. Please see | ||||
| Everything here _except the contents of the `dep` directory_ is © 2019 David | ||||
| Given and is licensed under the MIT open source license. Please see | ||||
| [COPYING](COPYING) for the full text. The tl;dr is: you can do what you like | ||||
| with it provided you don't claim you wrote it. | ||||
|  | ||||
| As an exception, `dep/fmt` contains a copy of [fmt](http://fmtlib.net), | ||||
| maintained by Victor Zverovich (`vitaut <https://github.com/vitaut>`) and | ||||
| Jonathan Müller (`foonathan <https://github.com/foonathan>`) with | ||||
| contributions from many other people. It is licensed under the terms of the | ||||
| BSD license. Please see the contents of the directory for the full text. | ||||
|  | ||||
| As an exception, `dep/emu` contains parts of the OpenBSD C library | ||||
| code, maintained by Todd Miller and William A. Rowe (and probably others). It is licensed | ||||
| code, Todd Miller and William A. Rowe (and probably others). It is licensed | ||||
| under the terms of the 3-clause BSD license. Please see the contents of the | ||||
| directory for the full text. It's been lightly modified by me. | ||||
|  | ||||
| As an exception, `dep/agg` contains parts of the Anti-Grain Antialiasing | ||||
| library, written by Maxim Semanarev (and others). It is licensed under the | ||||
| terms of the 3-clause BSD license. Please see the contents of the directory for | ||||
| the full text. It's been lightly modified by me. | ||||
|  | ||||
| As an exception, `dep/stb` contains parts of the libstb utility library, | ||||
| written by Sean T Barett (and others). It is public domain/Unlicense/MIT | ||||
| licensed, at your choice. Please see the contents of the directory for the full | ||||
| text. | ||||
|  | ||||
| As an exception, `dep/snowhouse` contains the snowhouse assertion library, | ||||
| taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License | ||||
| 1.0 licensed. Please see the contents of the directory for the full text. Note | ||||
| that this is only used during the build and no code ends up in the output | ||||
| binaries. | ||||
|  | ||||
| As an exception, `dep/libusbp` contains the libusbp library, taken from | ||||
| https://github.com/pololu/libusbp. It is MIT licensed. Please see the contents | ||||
| of the directory for the full text. | ||||
|  | ||||
| As an exception, `dep/fatfs` contains the fatfs library, taken from | ||||
| http://elm-chan.org/fsw/ff/00index_e.html. It is single-clause BSD licensed. | ||||
| Please see the contents of the directory for the full text. | ||||
|  | ||||
| As an exception, `dep/adflib` contains the adflib library, written by Laurent | ||||
| Clevy et al, taken from https://github.com/lclevy/ADFlib. It is GPL 2.0 | ||||
| licensed. Please see the contents of the directory for the full text. | ||||
|  | ||||
| As an exception, `dep/hfsutils` contains a partial copy of the hfsutils | ||||
| package, written by Robert Leslie et al, taken from | ||||
| https://www.mars.org/home/rob/proj/hfs. It is GPL 2.0 licensed. Please see the | ||||
| contents of the directory for the full text. | ||||
|  | ||||
| __Important:__ Because of all these exceptions, if you distribute the | ||||
| FluxEngine package as a whole, you must comply with the terms of _all_ of the | ||||
| licensing terms. This means that __effectively the FluxEngine package is | ||||
| distributable under the terms of the GPL 2.0__. | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,16 @@ | ||||
| #define AESLANIER_SECTOR_LENGTH    256 | ||||
| #define AESLANIER_RECORD_SIZE      (AESLANIER_SECTOR_LENGTH + 5) | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class AesLanierDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~AesLanierDecoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message AesLanierDecoderProto {} | ||||
|  | ||||
| @@ -6,6 +6,7 @@ | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include "bytes.h" | ||||
| #include "record.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
|  | ||||
| @@ -13,54 +14,52 @@ static const FluxPattern SECTOR_PATTERN(32, AESLANIER_RECORD_SEPARATOR); | ||||
|  | ||||
| /* This is actually M2FM, rather than MFM, but it our MFM/FM decoder copes fine with it. */ | ||||
|  | ||||
| class AesLanierDecoder : public Decoder | ||||
| static Bytes reverse_bits(const Bytes& input) | ||||
| { | ||||
| public: | ||||
| 	AesLanierDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */ | ||||
|  | ||||
| 		readRawBits(16); | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); | ||||
| 		const auto& reversed = bytes.reverseBits(); | ||||
|  | ||||
| 		_sector->logicalTrack = reversed[1]; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalSector = reversed[2]; | ||||
|  | ||||
| 		/* Check header 'checksum' (which seems far too simple to mean much). */ | ||||
|  | ||||
| 		{ | ||||
| 			uint8_t wanted = reversed[3]; | ||||
| 			uint8_t got = reversed[1] + reversed[2]; | ||||
| 			if (wanted != got) | ||||
| 				return; | ||||
| 		} | ||||
|  | ||||
| 		/* Check data checksum, which also includes the header and is | ||||
| 			* significantly better. */ | ||||
|  | ||||
| 		_sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); | ||||
| 		uint16_t wanted = reversed.reader().seek(0x101).read_le16(); | ||||
| 		uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); | ||||
| 		_sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAesLanierDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AesLanierDecoder(config)); | ||||
|     for (uint8_t b : input) | ||||
|         bw.write_8(reverse_bits(b)); | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| AbstractDecoder::RecordType AesLanierDecoder::advanceToNextRecord() | ||||
| { | ||||
|     _sector->clock = _fmr->seekToPattern(SECTOR_PATTERN); | ||||
|     if (_fmr->eof() || !_sector->clock) | ||||
|         return UNKNOWN_RECORD; | ||||
|     return SECTOR_RECORD; | ||||
| } | ||||
|  | ||||
| void AesLanierDecoder::decodeSectorRecord() | ||||
| { | ||||
|     /* Skip ID mark. */ | ||||
|  | ||||
|     readRawBits(16); | ||||
|  | ||||
|     const auto& rawbits = readRawBits(AESLANIER_RECORD_SIZE*16); | ||||
|     const auto& bytes = decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); | ||||
|     const auto& reversed = reverse_bits(bytes); | ||||
|  | ||||
|     _sector->logicalTrack = reversed[1]; | ||||
|     _sector->logicalSide = 0; | ||||
|     _sector->logicalSector = reversed[2]; | ||||
|  | ||||
|     /* Check header 'checksum' (which seems far too simple to mean much). */ | ||||
|  | ||||
|     { | ||||
|         uint8_t wanted = reversed[3]; | ||||
|         uint8_t got = reversed[1] + reversed[2]; | ||||
|         if (wanted != got) | ||||
|             return; | ||||
|     } | ||||
|  | ||||
|     /* Check data checksum, which also includes the header and is | ||||
|         * significantly better. */ | ||||
|  | ||||
|     _sector->data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); | ||||
|     uint16_t wanted = reversed.reader().seek(0x101).read_le16(); | ||||
|     uint16_t got = crc16ref(MODBUS_POLY_REF, _sector->data); | ||||
|     _sector->status = (wanted == got) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "agat.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| uint8_t agatChecksum(const Bytes& bytes) | ||||
| { | ||||
|     uint16_t checksum = 0; | ||||
|  | ||||
| 	for (uint8_t b : bytes) | ||||
| 	{ | ||||
| 		if (checksum > 0xff) | ||||
| 			checksum = (checksum + 1) & 0xff; | ||||
|  | ||||
| 		checksum += b; | ||||
| 	} | ||||
|  | ||||
| 	return checksum & 0xff; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,20 +0,0 @@ | ||||
| #ifndef AGAT_H | ||||
| #define AGAT_H | ||||
|  | ||||
| #define AGAT_SECTOR_SIZE 256 | ||||
|  | ||||
| static constexpr uint64_t SECTOR_ID = 0x8924555549111444; | ||||
| static constexpr uint64_t DATA_ID = 0x8924555514444911; | ||||
|  | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t agatChecksum(const Bytes& bytes); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @@ -1,19 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message AgatDecoderProto {} | ||||
|  | ||||
| message AgatEncoderProto { | ||||
| 	optional double target_clock_period_us = 1 | ||||
| 		[default=2.00, (help)="Data clock period of target format."]; | ||||
| 	optional double target_rotational_period_ms = 2 | ||||
| 		[default=200.0, (help)="Rotational period of target format."]; | ||||
| 	optional int32 post_index_gap_bytes = 3 | ||||
| 		[default=40, (help)="Post-index gap before first sector header."]; | ||||
| 	optional int32 pre_sector_gap_bytes = 4 | ||||
| 		[default=11, (help)="Gap before each sector header."]; | ||||
| 	optional int32 pre_data_gap_bytes = 5 | ||||
| 		[default=2, (help)="Gap before each sector data record."]; | ||||
| } | ||||
|  | ||||
| @@ -1,94 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "agat.h" | ||||
| #include "crc.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
|  | ||||
| /* | ||||
|  * data:    X  X  X  X   X  X  X  X   X  -  -  X   -  X  -  X   -  X  X  -   X  -  X  -  = 0xff956a | ||||
|  * flux:   01 01 01 01  01 01 01 01  01 00 10 01  00 01 00 01  00 01 01 00  01 00 01 00  = 0x555549111444 | ||||
|  *  | ||||
|  * data:    X  X  X  X   X  X  X  X   -  X  X  -   X  -  X  -   X  -  -  X   -  X  -  X  = 0xff6a95 | ||||
|  * flux:   01 01 01 01  01 01 01 01  00 01 01 00  01 00 01 00  01 00 10 01  00 01 00 01  = 0x555514444911 | ||||
|  *  | ||||
|  * Each pattern is prefixed with this one: | ||||
|  * | ||||
|  * data:          -  -   -  X   -  -   X  - = 0x12 | ||||
|  * flux:    (10) 10 10  10 01  00 10  01 00 = 0xa924 | ||||
|  * magic:   (10) 10 00  10 01  00 10  01 00 = 0x8924 | ||||
|  *                  ^ | ||||
|  * | ||||
|  * This seems to be generated by emitting A4 in MFM and then a single 0 bit to | ||||
|  * shift it out of phase, so the data bits become clock bits and vice versa. | ||||
|  * | ||||
|  *           X - X - - X - -  = 0xA4 | ||||
|  *          0100010010010010  = MFM encoded | ||||
|  *           1000100100100100 = with trailing zero | ||||
|  *            - - - X - - X - = effective bitstream = 0x12 | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(64, SECTOR_ID); | ||||
| static const FluxPattern DATA_PATTERN(64, DATA_ID); | ||||
|  | ||||
| static const FluxMatchers ALL_PATTERNS = { | ||||
| 	&SECTOR_PATTERN, | ||||
| 	&DATA_PATTERN | ||||
| }; | ||||
|  | ||||
| class AgatDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	AgatDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ALL_PATTERNS); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw64() != SECTOR_ID) | ||||
| 			return; | ||||
|  | ||||
| 		auto bytes = decodeFmMfm(readRawBits(64)).slice(0, 4); | ||||
| 		if (bytes[3] != 0x5a) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->logicalTrack = bytes[1] >> 1; | ||||
| 		_sector->logicalSector = bytes[2]; | ||||
| 		_sector->logicalSide = bytes[1] & 1; | ||||
| 		_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|  | ||||
| 	void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw64() != DATA_ID) | ||||
| 			return; | ||||
|  | ||||
| 		Bytes bytes = decodeFmMfm(readRawBits((AGAT_SECTOR_SIZE+2)*16)).slice(0, AGAT_SECTOR_SIZE+2); | ||||
|  | ||||
| 		if (bytes[AGAT_SECTOR_SIZE+1] != 0x5a) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->data = bytes.slice(0, AGAT_SECTOR_SIZE); | ||||
| 		uint8_t wantChecksum = bytes[AGAT_SECTOR_SIZE]; | ||||
| 		uint8_t gotChecksum = agatChecksum(_sector->data); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAgatDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AgatDecoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,118 +0,0 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/encoders/encoders.h" | ||||
| #include "agat.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/readerwriter.h" | ||||
| #include "lib/image.h" | ||||
| #include "lib/layout.h" | ||||
| #include "arch/agat/agat.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| class AgatEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     AgatEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.agat()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeRawBits(uint64_t data, int width) | ||||
|     { | ||||
|         _cursor += width; | ||||
|         _lastBit = data & 1; | ||||
|         for (int i = 0; i < width; i++) | ||||
|         { | ||||
|             unsigned pos = _cursor - i - 1; | ||||
|             if (pos < _bits.size()) | ||||
|                 _bits[pos] = data & 1; | ||||
|             data >>= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void writeBytes(const Bytes& bytes) | ||||
|     { | ||||
|         encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
|     } | ||||
|  | ||||
|     void writeByte(uint8_t byte) | ||||
|     { | ||||
|         Bytes b; | ||||
|         b.writer().write_8(byte); | ||||
|         writeBytes(b); | ||||
|     } | ||||
|  | ||||
|     void writeFillerRawBytes(int count, uint16_t byte) | ||||
|     { | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeRawBits(byte, 16); | ||||
|     }; | ||||
|  | ||||
|     void writeFillerBytes(int count, uint8_t byte) | ||||
|     { | ||||
|         Bytes b{byte}; | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeBytes(b); | ||||
|     }; | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         auto trackLayout = Layout::getLayoutOfTrack( | ||||
|             trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         double clockRateUs = _config.target_clock_period_us() / 2.0; | ||||
|         int bitsPerRevolution = | ||||
|             (_config.target_rotational_period_ms() * 1000.0) / clockRateUs; | ||||
|         _bits.resize(bitsPerRevolution); | ||||
|         _cursor = 0; | ||||
|  | ||||
|         writeFillerRawBytes(_config.post_index_gap_bytes(), 0xaaaa); | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|         { | ||||
|             /* Header */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_sector_gap_bytes(), 0xaaaa); | ||||
|             writeRawBits(SECTOR_ID, 64); | ||||
|             writeByte(0x5a); | ||||
|             writeByte((sector->logicalTrack << 1) | sector->logicalSide); | ||||
|             writeByte(sector->logicalSector); | ||||
|             writeByte(0x5a); | ||||
|  | ||||
|             /* Data */ | ||||
|  | ||||
|             writeFillerRawBytes(_config.pre_data_gap_bytes(), 0xaaaa); | ||||
|             auto data = sector->data.slice(0, AGAT_SECTOR_SIZE); | ||||
|             writeRawBits(DATA_ID, 64); | ||||
|             writeBytes(data); | ||||
|             writeByte(agatChecksum(data)); | ||||
|             writeByte(0x5a); | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         fillBitmapTo(_bits, _cursor, _bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(_config.target_clock_period_us() * 1e3, | ||||
|                 _config.target_rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const AgatEncoderProto& _config; | ||||
|     uint32_t _cursor; | ||||
|     bool _lastBit; | ||||
|     std::vector<bool> _bits; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createAgatEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new AgatEncoder(config)); | ||||
| } | ||||
| @@ -1,100 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "amiga.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| uint32_t amigaChecksum(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     uint32_t checksum = 0; | ||||
|  | ||||
|     assert((bytes.size() & 3) == 0); | ||||
|     while (!br.eof()) | ||||
|         checksum ^= br.read_be32(); | ||||
|  | ||||
|     return checksum & 0x55555555; | ||||
| } | ||||
|  | ||||
| static uint8_t everyother(uint16_t x) | ||||
| { | ||||
| 	                  /* aabb ccdd eeff gghh */ | ||||
| 	x &= 0x6666;      /* 0ab0 0cd0 0ef0 0gh0 */ | ||||
| 	x >>= 1;          /* 00ab 00cd 00ef 00gh */ | ||||
| 	x |= x << 2;      /* abab cdcd efef ghgh */ | ||||
| 	x &= 0x3c3c;      /* 00ab cd00 00ef gh00 */ | ||||
| 	x >>= 2;          /* 0000 abcd 0000 efgh */ | ||||
| 	x |= x >> 4;      /* 0000 abcd abcd efgh */ | ||||
| 	return x; | ||||
| } | ||||
|  | ||||
| Bytes amigaInterleave(const Bytes& input) | ||||
| { | ||||
| 	Bytes output; | ||||
| 	ByteWriter bw(output); | ||||
|  | ||||
| 	/* Write all odd bits. (Numbering starts at 0...) */ | ||||
|  | ||||
| 	{ | ||||
| 		ByteReader br(input); | ||||
| 		while (!br.eof()) | ||||
| 		{ | ||||
| 			uint16_t x = br.read_be16(); | ||||
| 			x &= 0xaaaa;       /* a0b0 c0d0 e0f0 g0h0 */ | ||||
| 			x |= x >> 1;       /* aabb ccdd eeff gghh */ | ||||
| 			x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
| 			bw.write_8(x); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* Write all even bits. */ | ||||
|  | ||||
| 	{ | ||||
| 		ByteReader br(input); | ||||
| 		while (!br.eof()) | ||||
| 		{ | ||||
| 			uint16_t x = br.read_be16(); | ||||
| 			x &= 0x5555;       /* 0a0b 0c0d 0e0f 0g0h */ | ||||
| 			x |= x << 1;       /* aabb ccdd eeff gghh */ | ||||
| 			x = everyother(x); /* 0000 0000 abcd efgh */ | ||||
| 			bw.write_8(x); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| Bytes amigaDeinterleave(const uint8_t*& input, size_t len) | ||||
| { | ||||
|     assert(!(len & 1)); | ||||
|     const uint8_t* odds = &input[0]; | ||||
|     const uint8_t* evens = &input[len/2]; | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     for (size_t i=0; i<len/2; i++) | ||||
|     { | ||||
|         uint8_t o = *odds++; | ||||
|         uint8_t e = *evens++; | ||||
|  | ||||
|         /* This is the 'Interleave bits with 64-bit multiply' technique from | ||||
|          * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN | ||||
|          */ | ||||
|         uint16_t result = | ||||
|             (((e * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 49) & 0x5555) | | ||||
|             (((o * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 48) & 0xAAAA); | ||||
|          | ||||
|         bw.write_be16(result); | ||||
|     } | ||||
|  | ||||
|     input += len; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| Bytes amigaDeinterleave(const Bytes& input) | ||||
| { | ||||
| 	const uint8_t* ptr = input.cbegin(); | ||||
| 	return amigaDeinterleave(ptr, input.size()); | ||||
| } | ||||
| @@ -1,20 +1,20 @@ | ||||
| #ifndef AMIGA_H | ||||
| #define AMIGA_H | ||||
|  | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define AMIGA_SECTOR_RECORD 0xaaaa44894489LL | ||||
|  | ||||
| #define AMIGA_TRACKS_PER_DISK 80 | ||||
| #define AMIGA_SECTORS_PER_TRACK 11 | ||||
| #define AMIGA_RECORD_SIZE 0x21c | ||||
| #define AMIGA_RECORD_SIZE 0x21f | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createAmigaEncoder(const EncoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| extern uint32_t amigaChecksum(const Bytes& bytes); | ||||
| extern Bytes amigaInterleave(const Bytes& input); | ||||
| extern Bytes amigaDeinterleave(const uint8_t*& input, size_t len); | ||||
| extern Bytes amigaDeinterleave(const Bytes& input); | ||||
| class AmigaDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~AmigaDecoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message AmigaDecoderProto {} | ||||
|  | ||||
| message AmigaEncoderProto { | ||||
| 	optional double clock_rate_us = 1 | ||||
| 		[default=2.00, (help)="Encoded data clock rate."]; | ||||
| 	optional double post_index_gap_ms = 2 | ||||
| 		[default=0.5, (help)="Post-index gap before first sector header."]; | ||||
| } | ||||
|  | ||||
| @@ -2,12 +2,12 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "amiga.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| @@ -21,60 +21,79 @@ | ||||
|           | ||||
| static const FluxPattern SECTOR_PATTERN(48, AMIGA_SECTOR_RECORD); | ||||
|  | ||||
| class AmigaDecoder : public Decoder | ||||
| static Bytes deinterleave(const uint8_t*& input, size_t len) | ||||
| { | ||||
| public: | ||||
| 	AmigaDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.amiga()) | ||||
| 	{} | ||||
|     assert(!(len & 1)); | ||||
|     const uint8_t* odds = &input[0]; | ||||
|     const uint8_t* evens = &input[len/2]; | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|     for (size_t i=0; i<len/2; i++) | ||||
|     { | ||||
|         uint8_t o = *odds++; | ||||
|         uint8_t e = *evens++; | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw48() != AMIGA_SECTOR_RECORD) | ||||
| 			return; | ||||
| 			 | ||||
| 		const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16); | ||||
| 		if (rawbits.size() < (AMIGA_RECORD_SIZE*16)) | ||||
| 			return; | ||||
| 		const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); | ||||
|         /* This is the 'Interleave bits with 64-bit multiply' technique from | ||||
|          * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN | ||||
|          */ | ||||
|         uint16_t result = | ||||
|             (((e * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 49) & 0x5555) | | ||||
|             (((o * 0x0101010101010101ULL & 0x8040201008040201ULL) | ||||
|                 * 0x0102040810204081ULL >> 48) & 0xAAAA); | ||||
|          | ||||
|         bw.write_be16(result); | ||||
|     } | ||||
|  | ||||
| 		const uint8_t* ptr = bytes.begin(); | ||||
|  | ||||
| 		Bytes header = amigaDeinterleave(ptr, 4); | ||||
| 		Bytes recoveryinfo = amigaDeinterleave(ptr, 16); | ||||
|  | ||||
| 		_sector->logicalTrack = header[1] >> 1; | ||||
| 		_sector->logicalSide = header[1] & 1; | ||||
| 		_sector->logicalSector = header[2]; | ||||
|  | ||||
| 		uint32_t wantedheaderchecksum = amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
| 		uint32_t gotheaderchecksum = amigaChecksum(rawbytes.slice(0, 40)); | ||||
| 		if (gotheaderchecksum != wantedheaderchecksum) | ||||
| 			return; | ||||
|  | ||||
| 		uint32_t wanteddatachecksum = amigaDeinterleave(ptr, 4).reader().read_be32(); | ||||
| 		uint32_t gotdatachecksum = amigaChecksum(rawbytes.slice(56, 1024)); | ||||
|  | ||||
| 		Bytes data; | ||||
| 		data.writer().append(amigaDeinterleave(ptr, 512)).append(recoveryinfo); | ||||
| 		_sector->data = data; | ||||
| 		_sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	const AmigaDecoderProto& _config; | ||||
| 	nanoseconds_t _clock; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createAmigaDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new AmigaDecoder(config)); | ||||
|     input += len; | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| static uint32_t checksum(const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     uint32_t checksum = 0; | ||||
|  | ||||
|     assert((bytes.size() & 3) == 0); | ||||
|     while (!br.eof()) | ||||
|         checksum ^= br.read_be32(); | ||||
|  | ||||
|     return checksum & 0x55555555; | ||||
| } | ||||
|  | ||||
| AbstractDecoder::RecordType AmigaDecoder::advanceToNextRecord() | ||||
| { | ||||
|     _sector->clock = _fmr->seekToPattern(SECTOR_PATTERN); | ||||
|     if (_fmr->eof() || !_sector->clock) | ||||
|         return UNKNOWN_RECORD; | ||||
|     return SECTOR_RECORD; | ||||
| } | ||||
|  | ||||
| void AmigaDecoder::decodeSectorRecord() | ||||
| { | ||||
|     const auto& rawbits = readRawBits(AMIGA_RECORD_SIZE*16); | ||||
|     const auto& rawbytes = toBytes(rawbits).slice(0, AMIGA_RECORD_SIZE*2); | ||||
|     const auto& bytes = decodeFmMfm(rawbits).slice(0, AMIGA_RECORD_SIZE); | ||||
|  | ||||
|     const uint8_t* ptr = bytes.begin() + 3; | ||||
|  | ||||
|     Bytes header = deinterleave(ptr, 4); | ||||
|     Bytes recoveryinfo = deinterleave(ptr, 16); | ||||
|  | ||||
|     _sector->logicalTrack = header[1] >> 1; | ||||
|     _sector->logicalSide = header[1] & 1; | ||||
|     _sector->logicalSector = header[2]; | ||||
|  | ||||
|     uint32_t wantedheaderchecksum = deinterleave(ptr, 4).reader().read_be32(); | ||||
|     uint32_t gotheaderchecksum = checksum(rawbytes.slice(6, 40)); | ||||
|     if (gotheaderchecksum != wantedheaderchecksum) | ||||
|         return; | ||||
|  | ||||
|     uint32_t wanteddatachecksum = deinterleave(ptr, 4).reader().read_be32(); | ||||
|     uint32_t gotdatachecksum = checksum(rawbytes.slice(62, 1024)); | ||||
|  | ||||
|     _sector->data.clear(); | ||||
|     _sector->data.writer().append(deinterleave(ptr, 512)).append(recoveryinfo); | ||||
|     _sector->status = (gotdatachecksum == wanteddatachecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,149 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "amiga.h" | ||||
| #include "crc.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "arch/amiga/amiga.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static int charToInt(char c) | ||||
| { | ||||
|     if (isdigit(c)) | ||||
|         return c - '0'; | ||||
|     return 10 + tolower(c) - 'a'; | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src) | ||||
| { | ||||
|     for (bool bit : src) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             lastBit = bits[cursor++] = bit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width) | ||||
| { | ||||
|     cursor += width; | ||||
|     lastBit = data & 1; | ||||
|     for (int i = 0; i < width; i++) | ||||
|     { | ||||
|         unsigned pos = cursor - i - 1; | ||||
|         if (pos < bits.size()) | ||||
|             bits[pos] = data & 1; | ||||
|         data >>= 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     BitReader bitr(br); | ||||
|  | ||||
|     while (!bitr.eof()) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             bits[cursor++] = bitr.get(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     if ((sector->data.size() != 512) && (sector->data.size() != 528)) | ||||
|         Error() << "unsupported sector size --- you must pick 512 or 528"; | ||||
|  | ||||
|     uint32_t checksum = 0; | ||||
|  | ||||
|     auto write_interleaved_bytes = [&](const Bytes& bytes) | ||||
|     { | ||||
|         Bytes interleaved = amigaInterleave(bytes); | ||||
|         Bytes mfm = encodeMfm(interleaved, lastBit); | ||||
|         checksum ^= amigaChecksum(mfm); | ||||
|         checksum &= 0x55555555; | ||||
|         write_bits(bits, cursor, mfm); | ||||
|     }; | ||||
|  | ||||
|     auto write_interleaved_word = [&](uint32_t word) | ||||
|     { | ||||
|         Bytes b(4); | ||||
|         b.writer().write_be32(word); | ||||
|         write_interleaved_bytes(b); | ||||
|     }; | ||||
|  | ||||
|     write_bits(bits, cursor, 0xaaaa, 2 * 8); | ||||
|     write_bits(bits, cursor, AMIGA_SECTOR_RECORD, 6 * 8); | ||||
|  | ||||
|     checksum = 0; | ||||
|     Bytes header = {0xff, /* Amiga 1.0 format byte */ | ||||
|         (uint8_t)((sector->logicalTrack << 1) | sector->logicalSide), | ||||
|         (uint8_t)sector->logicalSector, | ||||
|         (uint8_t)(AMIGA_SECTORS_PER_TRACK - sector->logicalSector)}; | ||||
|     write_interleaved_bytes(header); | ||||
|     Bytes recoveryInfo(16); | ||||
|     if (sector->data.size() == 528) | ||||
|         recoveryInfo = sector->data.slice(512, 16); | ||||
|     write_interleaved_bytes(recoveryInfo); | ||||
|     write_interleaved_word(checksum); | ||||
|  | ||||
|     Bytes data = sector->data.slice(0, 512); | ||||
|     write_interleaved_word( | ||||
|         amigaChecksum(encodeMfm(amigaInterleave(data), lastBit))); | ||||
|     write_interleaved_bytes(data); | ||||
| } | ||||
|  | ||||
| class AmigaEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     AmigaEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.amiga()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         /* Number of bits for one nominal revolution of a real 200ms Amiga disk. */ | ||||
|         int bitsPerRevolution = 200e3 / _config.clock_rate_us(); | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         fillBitmapTo(bits, | ||||
|             cursor, | ||||
|             _config.post_index_gap_ms() * 1000 / _config.clock_rate_us(), | ||||
|             {true, false}); | ||||
|         lastBit = false; | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|             write_sector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 _config.clock_rate_us() * 1e3, 200e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const AmigaEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createAmigaEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new AmigaEncoder(config)); | ||||
| } | ||||
| @@ -1,20 +1,25 @@ | ||||
| #ifndef APPLE2_H | ||||
| #define APPLE2_H | ||||
|  | ||||
| #include <memory.h> | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define APPLE2_SECTOR_RECORD   0xd5aa96 | ||||
| #define APPLE2_DATA_RECORD     0xd5aaad | ||||
|  | ||||
| #define APPLE2_SECTOR_LENGTH   256 | ||||
| #define APPLE2_ENCODED_SECTOR_LENGTH 342 | ||||
|  | ||||
| #define APPLE2_SECTORS         16 | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class Apple2Decoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~Apple2Decoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,22 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Apple2DecoderProto { | ||||
| 	optional uint32 side_one_track_offset = 1 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
|  | ||||
| message Apple2EncoderProto | ||||
| { | ||||
|     /* 245kHz. */ | ||||
|     optional double clock_period_us = 1 | ||||
|         [ default = 4, (help) = "clock rate on the real device" ]; | ||||
|      | ||||
|     /* Apple II disk drives spin at 300rpm. */ | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
|  | ||||
| 	optional uint32 side_one_track_offset = 3 | ||||
| 		[ default = 0, (help) = "offset to apply to track numbers on side 1" ]; | ||||
| } | ||||
| @@ -2,11 +2,10 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "apple2.h" | ||||
| #include "arch/apple2/apple2.pb.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
| @@ -14,25 +13,22 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(24, APPLE2_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(24, APPLE2_DATA_RECORD); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     {&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN}); | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
|  | ||||
| static int decode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case gcr:                \ | ||||
|         return data; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case gcr: return data; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|  * Woods and R. Belmont: | ||||
|  * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|  * and R. Belmont: https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
|  */ | ||||
| static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
| { | ||||
| @@ -52,11 +48,9 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
|         { | ||||
|             /* 3 * 2 bit */ | ||||
|             output[i + 0] = ((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02); | ||||
|             output[i + 86] = | ||||
|                 ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             output[i + 86] = ((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02); | ||||
|             if ((i + 172) < APPLE2_SECTOR_LENGTH) | ||||
|                 output[i + 172] = | ||||
|                     ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|                 output[i + 172] = ((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -66,110 +60,53 @@ static Bytes decode_crazy_data(const uint8_t* inp, Sector::Status& status) | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| static uint8_t combine(uint16_t word) | ||||
| uint8_t combine(uint16_t word) | ||||
| { | ||||
|     return word & (word >> 7); | ||||
| } | ||||
|  | ||||
| class Apple2Decoder : public Decoder | ||||
| AbstractDecoder::RecordType Apple2Decoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
|     Apple2Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         if (readRaw24() != APPLE2_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
|         /* Read header. */ | ||||
|  | ||||
|         auto header = toBytes(readRawBits(8 * 8)).slice(0, 8); | ||||
|         ByteReader br(header); | ||||
|  | ||||
|         uint8_t volume = combine(br.read_be16()); | ||||
|         _sector->logicalTrack = combine(br.read_be16()); | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = combine(br.read_be16()); | ||||
|         uint8_t checksum = combine(br.read_be16()); | ||||
|  | ||||
|         // If the checksum is correct, upgrade the sector from MISSING | ||||
|         // to DATA_MISSING in anticipation of its data record | ||||
|         if (checksum == | ||||
|             (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|  | ||||
|         if (_sector->logicalSide == 1) | ||||
|             _sector->logicalTrack -= _config.apple2().side_one_track_offset(); | ||||
|  | ||||
|         /* Sanity check. */ | ||||
|  | ||||
|         if (_sector->logicalTrack > 100) | ||||
|         { | ||||
|             _sector->status = Sector::MISSING; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
|     { | ||||
|         /* Check ID. */ | ||||
|  | ||||
|         if (readRaw24() != APPLE2_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
|         // Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and | ||||
|         // the data itself.  This has been seen on real world disks | ||||
|         // such as the Apple II Operating System Kit from Apple2Online. | ||||
|         // However, I haven't seen it described in any of the various | ||||
|         // references. | ||||
|         // | ||||
|         // This extra '0' bit would not affect the real disk interface, | ||||
|         // as it was a '1' reaching the top bit of a shift register | ||||
|         // that triggered a byte to be available, but it affects the | ||||
|         // way the data is read here. | ||||
|         // | ||||
|         // While the floppies tested only seemed to need this applied | ||||
|         // to the first byte of the data record, applying it | ||||
|         // consistently to all of them doesn't seem to hurt, and | ||||
|         // simplifies the code. | ||||
|  | ||||
|         /* Read and decode data. */ | ||||
|  | ||||
|         auto readApple8 = [&]() | ||||
|         { | ||||
|             auto result = 0; | ||||
|             while ((result & 0x80) == 0) | ||||
|             { | ||||
|                 auto b = readRawBits(1); | ||||
|                 if (b.empty()) | ||||
|                     break; | ||||
|                 result = (result << 1) | b[0]; | ||||
|             } | ||||
|             return result; | ||||
|         }; | ||||
|  | ||||
|         constexpr unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
|         uint8_t bytes[recordLength]; | ||||
|         for (auto& byte : bytes) | ||||
|         { | ||||
|             byte = readApple8(); | ||||
|         } | ||||
|  | ||||
|         // Upgrade the sector from MISSING to BAD_CHECKSUM. | ||||
|         // If decode_crazy_data succeeds, it upgrades the sector to | ||||
|         // OK. | ||||
|         _sector->status = Sector::BAD_CHECKSUM; | ||||
|         _sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createApple2Decoder(const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new Apple2Decoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return RecordType::DATA_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void Apple2Decoder::decodeSectorRecord() | ||||
| { | ||||
|     /* Skip ID (as we know it's a APPLE2_SECTOR_RECORD). */ | ||||
|     readRawBits(24); | ||||
|  | ||||
|     /* Read header. */ | ||||
|  | ||||
|     auto header = toBytes(readRawBits(8*8)).slice(0, 8); | ||||
|     ByteReader br(header); | ||||
|  | ||||
|     uint8_t volume = combine(br.read_be16()); | ||||
|     _sector->logicalTrack = combine(br.read_be16()); | ||||
|     _sector->logicalSector = combine(br.read_be16()); | ||||
|     uint8_t checksum = combine(br.read_be16()); | ||||
|     if (checksum == (volume ^ _sector->logicalTrack ^ _sector->logicalSector)) | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| } | ||||
|  | ||||
| void Apple2Decoder::decodeDataRecord() | ||||
| { | ||||
|     /* Check ID. */ | ||||
|  | ||||
|     Bytes bytes = toBytes(readRawBits(3*8)).slice(0, 3); | ||||
|     if (bytes.reader().read_be24() != APPLE2_DATA_RECORD) | ||||
|         return; | ||||
|  | ||||
|     /* Read and decode data. */ | ||||
|  | ||||
|     unsigned recordLength = APPLE2_ENCODED_SECTOR_LENGTH + 2; | ||||
|     bytes = toBytes(readRawBits(recordLength*8)).slice(0, recordLength); | ||||
|  | ||||
|     _sector->status = Sector::BAD_CHECKSUM; | ||||
|     _sector->data = decode_crazy_data(&bytes[0], _sector->status); | ||||
| } | ||||
|   | ||||
| @@ -1,194 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "arch/apple2/apple2.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "sector.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include <ctype.h> | ||||
| #include "bytes.h" | ||||
|  | ||||
| static int encode_data_gcr(uint8_t data) | ||||
| { | ||||
|     switch (data) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| class Apple2Encoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     Apple2Encoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.apple2()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Apple2EncoderProto& _config; | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         int bitsPerRevolution = | ||||
|             (_config.rotational_period_ms() * 1e3) / _config.clock_period_us(); | ||||
|  | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|             writeSector(bits, cursor, *sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod(_config.clock_period_us() * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     uint8_t volume_id = 254; | ||||
|  | ||||
|     /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|      * Woods and R. Belmont: | ||||
|      * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp | ||||
|      * as well as Understanding the Apple II (1983) Chapter 9 | ||||
|      * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater | ||||
|      */ | ||||
|  | ||||
|     void writeSector( | ||||
|         std::vector<bool>& bits, unsigned& cursor, const Sector& sector) const | ||||
|     { | ||||
|         if ((sector.status == Sector::OK) or | ||||
|             (sector.status == Sector::BAD_CHECKSUM)) | ||||
|         { | ||||
|             auto write_bit = [&](bool val) | ||||
|             { | ||||
|                 if (cursor <= bits.size()) | ||||
|                 { | ||||
|                     bits[cursor] = val; | ||||
|                 } | ||||
|                 cursor++; | ||||
|             }; | ||||
|  | ||||
|             auto write_bits = [&](uint32_t bits, int width) | ||||
|             { | ||||
|                 for (int i = width; i--;) | ||||
|                 { | ||||
|                     write_bit(bits & (1u << i)); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             auto write_gcr44 = [&](uint8_t value) | ||||
|             { | ||||
|                 write_bits((value << 7) | value | 0xaaaa, 16); | ||||
|             }; | ||||
|  | ||||
|             auto write_gcr6 = [&](uint8_t value) | ||||
|             { | ||||
|                 write_bits(encode_data_gcr(value), 8); | ||||
|             }; | ||||
|  | ||||
|             // The special "FF40" sequence is used to synchronize the receiving | ||||
|             // shift register. It's written as "1111 1111 00"; FF indicates the | ||||
|             // 8 consecutive 1-bits, while "40" indicates the total number of | ||||
|             // microseconds. | ||||
|             auto write_ff40 = [&](int n = 1) | ||||
|             { | ||||
|                 for (; n--;) | ||||
|                 { | ||||
|                     write_bits(0xff << 2, 10); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             // There is data to encode to disk. | ||||
|             if ((sector.data.size() != APPLE2_SECTOR_LENGTH)) | ||||
|                 Error() << fmt::format( | ||||
|                     "unsupported sector size {} --- you must pick 256", | ||||
|                     sector.data.size()); | ||||
|  | ||||
|             // Write address syncing leader : A sequence of "FF40"s; 5 of them | ||||
|             // are said to suffice to synchronize the decoder. | ||||
|             // "FF40" indicates that the actual data written is "1111 | ||||
|             // 1111 00" i.e., 8 1s and a total of 40 microseconds | ||||
|             // | ||||
|             // In standard formatting, the first logical sector apparently gets | ||||
|             // extra padding. | ||||
|             write_ff40(sector.logicalSector == 0 ? 32 : 8); | ||||
|  | ||||
|             int track = sector.logicalTrack; | ||||
|             if (sector.logicalSide == 1) | ||||
|                 track += _config.side_one_track_offset(); | ||||
|  | ||||
|             // Write address field: APPLE2_SECTOR_RECORD + sector identifier + | ||||
|             // DE AA EB | ||||
|             write_bits(APPLE2_SECTOR_RECORD, 24); | ||||
|             write_gcr44(volume_id); | ||||
|             write_gcr44(track); | ||||
|             write_gcr44(sector.logicalSector); | ||||
|             write_gcr44(volume_id ^ track ^ sector.logicalSector); | ||||
|             write_bits(0xDEAAEB, 24); | ||||
|  | ||||
|             // Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector | ||||
|             // data + sum + DE AA EB (+ mystery bits cut off of the scan?) | ||||
|             write_ff40(8); | ||||
|             write_bits(APPLE2_DATA_RECORD, 24); | ||||
|  | ||||
|             // Convert the sector data to GCR, append the checksum, and write it | ||||
|             // out | ||||
|             constexpr auto TWOBIT_COUNT = | ||||
|                 0x56; // Size of the 'twobit' area at the start of the GCR data | ||||
|             uint8_t checksum = 0; | ||||
|             for (int i = 0; i < APPLE2_ENCODED_SECTOR_LENGTH; i++) | ||||
|             { | ||||
|                 int value; | ||||
|                 if (i >= TWOBIT_COUNT) | ||||
|                 { | ||||
|                     value = sector.data[i - TWOBIT_COUNT] >> 2; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     uint8_t tmp = sector.data[i]; | ||||
|                     value = ((tmp & 1) << 1) | ((tmp & 2) >> 1); | ||||
|  | ||||
|                     tmp = sector.data[i + TWOBIT_COUNT]; | ||||
|                     value |= ((tmp & 1) << 3) | ((tmp & 2) << 1); | ||||
|  | ||||
|                     if (i + 2 * TWOBIT_COUNT < APPLE2_SECTOR_LENGTH) | ||||
|                     { | ||||
|                         tmp = sector.data[i + 2 * TWOBIT_COUNT]; | ||||
|                         value |= ((tmp & 1) << 5) | ((tmp & 2) << 3); | ||||
|                     } | ||||
|                 } | ||||
|                 checksum ^= value; | ||||
|                 // assert(checksum & ~0x3f == 0); | ||||
|                 write_gcr6(checksum); | ||||
|                 checksum = value; | ||||
|             } | ||||
|             if (sector.status == Sector::BAD_CHECKSUM) | ||||
|                 checksum ^= 0x3f; | ||||
|             write_gcr6(checksum); | ||||
|             write_bits(0xDEAAEB, 24); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createApple2Encoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Apple2Encoder(config)); | ||||
| } | ||||
| @@ -9,11 +9,31 @@ | ||||
| #define BROTHER_DATA_RECORD_CHECKSUM     3 | ||||
| #define BROTHER_DATA_RECORD_ENCODED_SIZE 415 | ||||
|  | ||||
| #define BROTHER_TRACKS_PER_240KB_DISK    78 | ||||
| #define BROTHER_TRACKS_PER_120KB_DISK    39 | ||||
| #define BROTHER_TRACKS_PER_DISK          78 | ||||
| #define BROTHER_SECTORS_PER_TRACK        12 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createBrotherEncoder(const EncoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class BrotherDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~BrotherDecoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| class BrotherEncoder : public AbstractEncoder | ||||
| { | ||||
| public: | ||||
| 	virtual ~BrotherEncoder() {} | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(int physicalTrack, int physicalSide, const SectorSet& allSectors); | ||||
| }; | ||||
|  | ||||
| extern FlagGroup brotherEncoderFlags; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,18 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message BrotherDecoderProto {} | ||||
|  | ||||
| enum BrotherFormat { | ||||
| 	BROTHER240 = 0; | ||||
| 	BROTHER120 = 1; | ||||
| }; | ||||
|  | ||||
| message BrotherEncoderProto { | ||||
| 	optional double clock_rate_us = 1 [default = 3.83]; | ||||
| 	optional double post_index_gap_ms = 2 [default = 1.0]; | ||||
| 	optional double sector_spacing_ms = 3 [default = 16.2]; | ||||
| 	optional double post_header_spacing_ms = 4 [default = 0.69]; | ||||
|  | ||||
| 	optional BrotherFormat format = 6 [default = BROTHER240]; | ||||
| } | ||||
|  | ||||
							
								
								
									
										28
									
								
								arch/brother/brotherdecode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								arch/brother/brotherdecode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #ifndef BROTHER_H | ||||
| #define BROTHER_H | ||||
|  | ||||
| /* Brother word processor format (or at least, one of them) */ | ||||
|  | ||||
| #define BROTHER_SECTOR_RECORD 0xFFFFFD57 | ||||
| #define BROTHER_DATA_RECORD   0xFFFFFDDB | ||||
| #define BROTHER_DATA_RECORD_PAYLOAD  256 | ||||
| #define BROTHER_DATA_RECORD_CHECKSUM 3 | ||||
|  | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class BrotherDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~BrotherDecoder() {} | ||||
|  | ||||
|     SectorVector decodeToSectors(const RawRecordVector& rawRecords); | ||||
|     int recordMatcher(uint64_t fifo) const; | ||||
| }; | ||||
|  | ||||
| extern void writeBrotherSectorHeader(std::vector<bool>& bits, unsigned& cursor, | ||||
| 		int track, int sector); | ||||
| extern void writeBrotherSectorData(std::vector<bool>& bits, unsigned& cursor, | ||||
| 		const std::vector<uint8_t>& data); | ||||
|  | ||||
| #endif | ||||
| @@ -1,8 +1,10 @@ | ||||
| #include "globals.h" | ||||
| #include "sql.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "record.h" | ||||
| #include "brother.h" | ||||
| #include "sector.h" | ||||
| #include "bytes.h" | ||||
| @@ -38,7 +40,7 @@ static int decode_data_gcr(uint8_t gcr) | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| static int decode_header_gcr(uint16_t word) | ||||
| { | ||||
| @@ -50,70 +52,58 @@ static int decode_header_gcr(uint16_t word) | ||||
| 		#undef GCR_ENTRY | ||||
| 	}                        | ||||
| 	return -1;              | ||||
| } | ||||
|  | ||||
| class BrotherDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
|     BrotherDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw32() != BROTHER_SECTOR_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(32); | ||||
| 		const auto& bytes = toBytes(rawbits).slice(0, 4); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		_sector->logicalTrack = decode_header_gcr(br.read_be16()); | ||||
| 		_sector->logicalSector = decode_header_gcr(br.read_be16()); | ||||
|  | ||||
| 		/* Sanity check the values read; there's no header checksum and | ||||
| 			* occasionally we get garbage due to bit errors. */ | ||||
| 		if (_sector->logicalSector > 11) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 79) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->status = Sector::DATA_MISSING; | ||||
| 	} | ||||
| 	 | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw32() != BROTHER_DATA_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8); | ||||
| 		const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE); | ||||
|  | ||||
| 		Bytes bytes; | ||||
| 		ByteWriter bw(bytes); | ||||
| 		BitWriter bitw(bw); | ||||
| 		for (uint8_t b : rawbytes) | ||||
| 		{ | ||||
| 			uint32_t nibble = decode_data_gcr(b); | ||||
| 			bitw.push(nibble, 5); | ||||
| 		} | ||||
| 		bitw.flush(); | ||||
|  | ||||
| 		_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD); | ||||
| 		uint32_t realCrc = crcbrother(_sector->data); | ||||
| 		uint32_t wantCrc = bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24(); | ||||
| 		_sector->status = (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createBrotherDecoder(const DecoderProto& config) | ||||
| AbstractDecoder::RecordType BrotherDecoder::advanceToNextRecord() | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new BrotherDecoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return RecordType::DATA_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void BrotherDecoder::decodeSectorRecord() | ||||
| { | ||||
| 	readRawBits(32); | ||||
| 	const auto& rawbits = readRawBits(32); | ||||
| 	const auto& bytes = toBytes(rawbits).slice(0, 4); | ||||
|  | ||||
| 	ByteReader br(bytes); | ||||
| 	_sector->logicalTrack = decode_header_gcr(br.read_be16()); | ||||
| 	_sector->logicalSector = decode_header_gcr(br.read_be16()); | ||||
|  | ||||
| 	/* Sanity check the values read; there's no header checksum and | ||||
| 		* occasionally we get garbage due to bit errors. */ | ||||
| 	if (_sector->logicalSector > 11) | ||||
| 		return; | ||||
| 	if (_sector->logicalTrack > 79) | ||||
| 		return; | ||||
|  | ||||
| 	_sector->status = Sector::DATA_MISSING; | ||||
| } | ||||
|  | ||||
| void BrotherDecoder::decodeDataRecord() | ||||
| { | ||||
| 	readRawBits(32); | ||||
|  | ||||
| 	const auto& rawbits = readRawBits(BROTHER_DATA_RECORD_ENCODED_SIZE*8); | ||||
| 	const auto& rawbytes = toBytes(rawbits).slice(0, BROTHER_DATA_RECORD_ENCODED_SIZE); | ||||
|  | ||||
| 	Bytes bytes; | ||||
| 	ByteWriter bw(bytes); | ||||
| 	BitWriter bitw(bw); | ||||
| 	for (uint8_t b : rawbytes) | ||||
| 	{ | ||||
| 		uint32_t nibble = decode_data_gcr(b); | ||||
| 		bitw.push(nibble, 5); | ||||
| 	} | ||||
| 	bitw.flush(); | ||||
|  | ||||
| 	_sector->data = bytes.slice(0, BROTHER_DATA_RECORD_PAYLOAD); | ||||
| 	uint32_t realCrc = crcbrother(_sector->data); | ||||
| 	uint32_t wantCrc = bytes.reader().seek(BROTHER_DATA_RECORD_PAYLOAD).read_be24(); | ||||
| 	_sector->status = (realCrc == wantCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,154 +1,167 @@ | ||||
| #include "globals.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "brother.h" | ||||
| #include "crc.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "arch/brother/brother.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include "sectorset.h" | ||||
| #include "writer.h" | ||||
|  | ||||
| FlagGroup brotherEncoderFlags; | ||||
|  | ||||
| static DoubleFlag clockRateUs( | ||||
| 	{ "--clock-rate" }, | ||||
| 	"Encoded data clock rate (microseconds).", | ||||
| 	3.83); | ||||
|  | ||||
| static DoubleFlag postIndexGapMs( | ||||
| 	{ "--post-index-gap" }, | ||||
| 	"Post-index gap before first sector header (milliseconds).", | ||||
| 	1.0); | ||||
|  | ||||
| static DoubleFlag sectorSpacingMs( | ||||
| 	{ "--sector-spacing" }, | ||||
| 	"Time between successive sector headers (milliseconds).", | ||||
| 	16.2); | ||||
|  | ||||
| static DoubleFlag postHeaderSpacingMs( | ||||
| 	{ "--post-header-spacing" }, | ||||
| 	"Time between a sector's header and data records (milliseconds).", | ||||
| 	0.69); | ||||
|  | ||||
| static StringFlag sectorSkew( | ||||
| 	{ "--sector-skew" }, | ||||
| 	"Order in which to write sectors.", | ||||
| 	"05a3816b4927"); | ||||
|  | ||||
| static int encode_header_gcr(uint16_t word) | ||||
| { | ||||
|     switch (word) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "header_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| 	switch (word) | ||||
| 	{ | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case data: return gcr; | ||||
| 		#include "header_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
| 	}                        | ||||
| 	return -1;              | ||||
| }; | ||||
|  | ||||
| static int encode_data_gcr(uint8_t data) | ||||
| { | ||||
|     switch (data) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| 	switch (data) | ||||
| 	{ | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case data: return gcr; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
| 	}                        | ||||
| 	return -1;              | ||||
| }; | ||||
|  | ||||
| static void write_bits(std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width) | ||||
| { | ||||
| 	cursor += width; | ||||
| 	for (int i=0; i<width; i++) | ||||
| 	{ | ||||
| 		unsigned pos = cursor - i - 1; | ||||
| 		if (pos < bits.size()) | ||||
| 			bits[pos] = data & 1; | ||||
| 		data >>= 1; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, uint32_t data, int width) | ||||
| static void write_sector_header(std::vector<bool>& bits, unsigned& cursor, | ||||
| 		int track, int sector) | ||||
| { | ||||
|     cursor += width; | ||||
|     for (int i = 0; i < width; i++) | ||||
|     { | ||||
|         unsigned pos = cursor - i - 1; | ||||
|         if (pos < bits.size()) | ||||
|             bits[pos] = data & 1; | ||||
|         data >>= 1; | ||||
|     } | ||||
| 	write_bits(bits, cursor, 0xffffffff, 31); | ||||
| 	write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32); | ||||
| 	write_bits(bits, cursor, encode_header_gcr(track), 16); | ||||
| 	write_bits(bits, cursor, encode_header_gcr(sector), 16); | ||||
| 	write_bits(bits, cursor, encode_header_gcr(0x2f), 16); | ||||
| } | ||||
|  | ||||
| static void write_sector_header( | ||||
|     std::vector<bool>& bits, unsigned& cursor, int track, int sector) | ||||
| static void write_sector_data(std::vector<bool>& bits, unsigned& cursor, const Bytes& data) | ||||
| { | ||||
|     write_bits(bits, cursor, 0xffffffff, 31); | ||||
|     write_bits(bits, cursor, BROTHER_SECTOR_RECORD, 32); | ||||
|     write_bits(bits, cursor, encode_header_gcr(track), 16); | ||||
|     write_bits(bits, cursor, encode_header_gcr(sector), 16); | ||||
|     write_bits(bits, cursor, encode_header_gcr(0x2f), 16); | ||||
| } | ||||
| 	write_bits(bits, cursor, 0xffffffff, 32); | ||||
| 	write_bits(bits, cursor, BROTHER_DATA_RECORD, 32); | ||||
|  | ||||
| static void write_sector_data( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const Bytes& data) | ||||
| { | ||||
|     write_bits(bits, cursor, 0xffffffff, 32); | ||||
|     write_bits(bits, cursor, BROTHER_DATA_RECORD, 32); | ||||
| 	uint16_t fifo = 0; | ||||
| 	int width = 0; | ||||
|  | ||||
|     uint16_t fifo = 0; | ||||
|     int width = 0; | ||||
| 	if (data.size() != BROTHER_DATA_RECORD_PAYLOAD) | ||||
| 		Error() << "unsupported sector size"; | ||||
|  | ||||
|     if (data.size() != BROTHER_DATA_RECORD_PAYLOAD) | ||||
|         Error() << "unsupported sector size"; | ||||
| 	auto write_byte = [&](uint8_t byte) | ||||
| 	{ | ||||
| 		fifo |= (byte << (8 - width)); | ||||
| 		width += 8; | ||||
|  | ||||
|     auto write_byte = [&](uint8_t byte) | ||||
|     { | ||||
|         fifo |= (byte << (8 - width)); | ||||
|         width += 8; | ||||
| 		while (width >= 5) | ||||
| 		{ | ||||
| 			uint8_t quintet = fifo >> 11; | ||||
| 			fifo <<= 5; | ||||
| 			width -= 5; | ||||
|  | ||||
|         while (width >= 5) | ||||
|         { | ||||
|             uint8_t quintet = fifo >> 11; | ||||
|             fifo <<= 5; | ||||
|             width -= 5; | ||||
| 			write_bits(bits, cursor, encode_data_gcr(quintet), 8); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
|             write_bits(bits, cursor, encode_data_gcr(quintet), 8); | ||||
|         } | ||||
|     }; | ||||
| 	for (uint8_t byte : data) | ||||
| 		write_byte(byte); | ||||
|  | ||||
|     for (uint8_t byte : data) | ||||
|         write_byte(byte); | ||||
|  | ||||
|     uint32_t realCrc = crcbrother(data); | ||||
|     write_byte(realCrc >> 16); | ||||
|     write_byte(realCrc >> 8); | ||||
|     write_byte(realCrc); | ||||
|     write_byte(0x58); /* magic */ | ||||
| 	uint32_t realCrc = crcbrother(data); | ||||
| 	write_byte(realCrc>>16); | ||||
| 	write_byte(realCrc>>8); | ||||
| 	write_byte(realCrc); | ||||
| 	write_byte(0x58); /* magic */ | ||||
|     write_byte(0xd4); | ||||
|     while (width != 0) | ||||
|         write_byte(0); | ||||
| } | ||||
|  | ||||
| class BrotherEncoder : public Encoder | ||||
| static int charToInt(char c) | ||||
| { | ||||
| public: | ||||
|     BrotherEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.brother()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode( | ||||
| 		std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         int bitsPerRevolution = 200000.0 / _config.clock_rate_us(); | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
| 		int sectorCount = 0; | ||||
| 		for (const auto& sectorData : sectors) | ||||
|         { | ||||
|             double headerMs = _config.post_index_gap_ms() + | ||||
|                               sectorCount * _config.sector_spacing_ms(); | ||||
|             unsigned headerCursor = headerMs * 1e3 / _config.clock_rate_us(); | ||||
|             double dataMs = headerMs + _config.post_header_spacing_ms(); | ||||
|             unsigned dataCursor = dataMs * 1e3 / _config.clock_rate_us(); | ||||
|  | ||||
|             fillBitmapTo(bits, cursor, headerCursor, {true, false}); | ||||
|             write_sector_header( | ||||
|                 bits, cursor, sectorData->logicalTrack, sectorData->logicalSector); | ||||
|             fillBitmapTo(bits, cursor, dataCursor, {true, false}); | ||||
|             write_sector_data(bits, cursor, sectorData->data); | ||||
|  | ||||
| 			sectorCount++; | ||||
|         } | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, _config.clock_rate_us() * 1e3); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const BrotherEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createBrotherEncoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new BrotherEncoder(config)); | ||||
| 	if (isdigit(c)) | ||||
| 		return c - '0'; | ||||
| 	return 10 + tolower(c) - 'a'; | ||||
| } | ||||
|  | ||||
| std::unique_ptr<Fluxmap> BrotherEncoder::encode( | ||||
| 	int physicalTrack, int physicalSide, const SectorSet& allSectors) | ||||
| { | ||||
| 	if ((physicalTrack < 0) || (physicalTrack >= BROTHER_TRACKS_PER_DISK) | ||||
|         || (physicalSide != 0)) | ||||
| 		return std::unique_ptr<Fluxmap>(); | ||||
|  | ||||
| 	int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
| 	const std::string& skew = sectorSkew.get(); | ||||
| 	std::vector<bool> bits(bitsPerRevolution); | ||||
| 	unsigned cursor = 0; | ||||
|  | ||||
| 	for (int sectorCount=0; sectorCount<BROTHER_SECTORS_PER_TRACK; sectorCount++) | ||||
| 	{ | ||||
| 		int sectorId = charToInt(skew.at(sectorCount)); | ||||
| 		double headerMs = postIndexGapMs + sectorCount*sectorSpacingMs; | ||||
| 		unsigned headerCursor = headerMs*1e3 / clockRateUs; | ||||
| 		double dataMs = headerMs + postHeaderSpacingMs; | ||||
| 		unsigned dataCursor = dataMs*1e3 / clockRateUs; | ||||
|  | ||||
| 		const auto& sectorData = allSectors.get(physicalTrack, 0, sectorId); | ||||
|  | ||||
| 		fillBitmapTo(bits, cursor, headerCursor, { true, false }); | ||||
| 		write_sector_header(bits, cursor, physicalTrack, sectorId); | ||||
| 		fillBitmapTo(bits, cursor, dataCursor, { true, false }); | ||||
| 		write_sector_data(bits, cursor, sectorData->data); | ||||
| 	} | ||||
|  | ||||
| 	if (cursor > bits.size()) | ||||
| 		Error() << "track data overrun"; | ||||
| 	fillBitmapTo(bits, cursor, bits.size(), { true, false }); | ||||
|  | ||||
| 	// The pre-index gap is not normally reported. | ||||
| 	// std::cerr << "pre-index gap " << 200.0 - (double)cursor*clockRateUs/1e3 << std::endl; | ||||
| 	 | ||||
| 	std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
| 	fluxmap->appendBits(bits, clockRateUs*1e3); | ||||
| 	return fluxmap; | ||||
| } | ||||
|   | ||||
| @@ -1,44 +0,0 @@ | ||||
| LIBARCH_SRCS = \ | ||||
| 	arch/aeslanier/decoder.cc \ | ||||
| 	arch/agat/agat.cc \ | ||||
| 	arch/agat/decoder.cc \ | ||||
| 	arch/agat/encoder.cc \ | ||||
| 	arch/amiga/amiga.cc \ | ||||
| 	arch/amiga/decoder.cc \ | ||||
| 	arch/amiga/encoder.cc \ | ||||
| 	arch/apple2/decoder.cc \ | ||||
| 	arch/apple2/encoder.cc \ | ||||
| 	arch/brother/decoder.cc \ | ||||
| 	arch/brother/encoder.cc \ | ||||
| 	arch/c64/c64.cc \ | ||||
| 	arch/c64/decoder.cc \ | ||||
| 	arch/c64/encoder.cc \ | ||||
| 	arch/f85/decoder.cc \ | ||||
| 	arch/fb100/decoder.cc \ | ||||
| 	arch/ibm/decoder.cc \ | ||||
| 	arch/ibm/encoder.cc \ | ||||
| 	arch/macintosh/decoder.cc \ | ||||
| 	arch/macintosh/encoder.cc \ | ||||
| 	arch/micropolis/decoder.cc \ | ||||
| 	arch/micropolis/encoder.cc \ | ||||
| 	arch/mx/decoder.cc \ | ||||
| 	arch/northstar/decoder.cc \ | ||||
| 	arch/northstar/encoder.cc \ | ||||
| 	arch/rolandd20/decoder.cc \ | ||||
| 	arch/smaky6/decoder.cc \ | ||||
| 	arch/tids990/decoder.cc \ | ||||
| 	arch/tids990/encoder.cc \ | ||||
| 	arch/victor9k/decoder.cc \ | ||||
| 	arch/victor9k/encoder.cc \ | ||||
| 	arch/zilogmcz/decoder.cc \ | ||||
|  | ||||
| LIBARCH_OBJS = $(patsubst %.cc, $(OBJDIR)/%.o, $(LIBARCH_SRCS)) | ||||
| OBJS += $(LIBARCH_OBJS) | ||||
| $(LIBARCH_SRCS): | $(PROTO_HDRS) | ||||
| $(LIBARCH_SRCS): CFLAGS += $(PROTO_CFLAGS) | ||||
| LIBARCH_LIB = $(OBJDIR)/libarch.a | ||||
| $(LIBARCH_LIB): $(LIBARCH_OBJS) | ||||
|  | ||||
| LIBARCH_LDFLAGS = $(LIBARCH_LIB) | ||||
|  | ||||
| $(call use-pkgconfig, $(LIBARCH_LIB), $(LIBARCH_OBJS), fmt) | ||||
| @@ -1,28 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "c64.h" | ||||
|  | ||||
| /* | ||||
|     *   Track   Sectors/track   # Sectors   Storage in Bytes   Clock rate | ||||
|     *   -----   -------------   ---------   ----------------   ---------- | ||||
|     *    1-17        21            357           7820             3.25 | ||||
|     *   18-24        19            133           7170             3.5 | ||||
|     *   25-30        18            108           6300             3.75 | ||||
|     *   31-40(*)     17             85           6020             4 | ||||
|     *                              --- | ||||
|     *                              683 (for a 35 track image) | ||||
|     *  | ||||
|     * The clock rate is normalised for a 200ms drive. | ||||
|     */ | ||||
|  | ||||
| nanoseconds_t clockPeriodForC64Track(unsigned track) | ||||
| { | ||||
|     constexpr double BYTE_SIZE = 8.0; | ||||
|  | ||||
|     if (track < 17) | ||||
|         return 26.0 / BYTE_SIZE; | ||||
|     if (track < 24) | ||||
|         return 28.0 / BYTE_SIZE; | ||||
|     if (track < 30) | ||||
|         return 30.0 / BYTE_SIZE; | ||||
|     return 32.0 / BYTE_SIZE; | ||||
| } | ||||
| @@ -1,35 +1,21 @@ | ||||
| #ifndef C64_H | ||||
| #define C64_H | ||||
|  | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
|  | ||||
| #define C64_SECTOR_RECORD    0xffd49 | ||||
| #define C64_DATA_RECORD      0xffd57 | ||||
| #define C64_SECTOR_LENGTH    256 | ||||
|  | ||||
| /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html  | ||||
|    1. Header sync       FF FF FF FF FF (40 'on' bits, not GCR) | ||||
|    2. Header info       52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) | ||||
|    3. Header gap        55 55 55 55 55 55 55 55 55 (9 bytes, never read) | ||||
|    4. Data sync         FF FF FF FF FF (40 'on' bits, not GCR) | ||||
|    5. Data block        55...4A (325 GCR bytes) | ||||
|    6. Inter-sector gap  55 55 55 55...55 55 (4 to 12 bytes, never read) | ||||
|    1. Header sync       (SYNC for the next sector) | ||||
| */ | ||||
| #define C64_HEADER_DATA_SYNC        0xFF | ||||
| #define C64_HEADER_BLOCK_ID         0x08 | ||||
| #define C64_DATA_BLOCK_ID           0x07 | ||||
| #define C64_HEADER_GAP              0x55 | ||||
| #define C64_INTER_SECTOR_GAP        0x55 | ||||
| #define C64_PADDING                 0x0F | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| #define C64_TRACKS_PER_DISK         40 | ||||
| #define C64_BAM_TRACK               17 | ||||
| class Commodore64Decoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~Commodore64Decoder() {} | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createCommodore64Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createCommodore64Encoder(const EncoderProto& config); | ||||
|  | ||||
| extern nanoseconds_t clockPeriodForC64Track(unsigned track); | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Commodore64DecoderProto {} | ||||
|  | ||||
| message Commodore64EncoderProto { | ||||
| 	optional double post_index_gap_us = 1 [default=0.0, | ||||
| 		(help) = "post-index gap before first sector header."]; | ||||
| } | ||||
|  | ||||
| @@ -2,6 +2,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "c64.h" | ||||
| @@ -13,21 +14,19 @@ | ||||
|  | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(20, C64_SECTOR_RECORD); | ||||
| const FluxPattern DATA_RECORD_PATTERN(20, C64_DATA_RECORD); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     {&SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN}); | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
|  | ||||
| static int decode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case gcr:                \ | ||||
|         return data; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
| 		#define GCR_ENTRY(gcr, data) \ | ||||
| 			case gcr: return data; | ||||
| 		#include "data_gcr.h" | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| static Bytes decode(const std::vector<bool>& bits) | ||||
| { | ||||
| @@ -39,11 +38,11 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     while (ii != bits.end()) | ||||
|     { | ||||
|         uint8_t inputfifo = 0; | ||||
|         for (size_t i = 0; i < 5; i++) | ||||
|         for (size_t i=0; i<5; i++) | ||||
|         { | ||||
|             if (ii == bits.end()) | ||||
|                 break; | ||||
|             inputfifo = (inputfifo << 1) | *ii++; | ||||
|             inputfifo = (inputfifo<<1) | *ii++; | ||||
|         } | ||||
|  | ||||
|         bitw.push(decode_data_gcr(inputfifo), 4); | ||||
| @@ -53,51 +52,41 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| class Commodore64Decoder : public Decoder | ||||
| AbstractDecoder::RecordType Commodore64Decoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
|     Commodore64Decoder(const DecoderProto& config): Decoder(config) {} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         if (readRaw20() != C64_SECTOR_RECORD) | ||||
|             return; | ||||
|  | ||||
|         const auto& bits = readRawBits(5 * 10); | ||||
|         const auto& bytes = decode(bits).slice(0, 5); | ||||
|  | ||||
|         uint8_t checksum = bytes[0]; | ||||
|         _sector->logicalSector = bytes[1]; | ||||
|         _sector->logicalSide = 0; | ||||
|         _sector->logicalTrack = bytes[2] - 1; | ||||
|         if (checksum == xorBytes(bytes.slice(1, 4))) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
|     { | ||||
|         if (readRaw20() != C64_DATA_RECORD) | ||||
|             return; | ||||
|  | ||||
|         const auto& bits = readRawBits(259 * 10); | ||||
|         const auto& bytes = decode(bits).slice(0, 259); | ||||
|  | ||||
|         _sector->data = bytes.slice(0, C64_SECTOR_LENGTH); | ||||
|         uint8_t gotChecksum = xorBytes(_sector->data); | ||||
|         uint8_t wantChecksum = bytes[256]; | ||||
|         _sector->status = | ||||
|             (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createCommodore64Decoder( | ||||
|     const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new Commodore64Decoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return RecordType::DATA_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void Commodore64Decoder::decodeSectorRecord() | ||||
| { | ||||
|     readRawBits(20); | ||||
|  | ||||
|     const auto& bits = readRawBits(5*10); | ||||
|     const auto& bytes = decode(bits).slice(0, 5); | ||||
|  | ||||
|     uint8_t checksum = bytes[0]; | ||||
|     _sector->logicalSector = bytes[1]; | ||||
|     _sector->logicalSide = 0; | ||||
|     _sector->logicalTrack = bytes[2] - 1; | ||||
|     if (checksum == xorBytes(bytes.slice(1, 4))) | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| } | ||||
|  | ||||
| void Commodore64Decoder::decodeDataRecord() | ||||
| { | ||||
|     readRawBits(20); | ||||
|  | ||||
|     const auto& bits = readRawBits(259*10); | ||||
|     const auto& bytes = decode(bits).slice(0, 259); | ||||
|  | ||||
|     _sector->data = bytes.slice(0, C64_SECTOR_LENGTH); | ||||
|     uint8_t gotChecksum = xorBytes(_sector->data); | ||||
|     uint8_t wantChecksum = bytes[256]; | ||||
|     _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,340 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "c64.h" | ||||
| #include "crc.h" | ||||
| #include "sector.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "arch/c64/c64.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include <ctype.h> | ||||
| #include "bytes.h" | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static int encode_data_gcr(uint8_t data) | ||||
| { | ||||
|     switch (data) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src) | ||||
| { | ||||
|     for (bool bit : src) // Range-based for loop | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             bits[cursor++] = bit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width) | ||||
| { | ||||
|     cursor += width; | ||||
|     for (int i = 0; i < width; i++) | ||||
|     { | ||||
|         unsigned pos = cursor - i - 1; | ||||
|         if (pos < bits.size()) | ||||
|             bits[pos] = data & 1; | ||||
|         data >>= 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void bindump(std::ostream& stream, std::vector<bool>& buffer) | ||||
| { | ||||
|     size_t pos = 0; | ||||
|  | ||||
|     while ((pos < buffer.size()) and (pos < 520)) | ||||
|     { | ||||
|         stream << fmt::format("{:5d} : ", pos); | ||||
|         for (int i = 0; i < 40; i++) | ||||
|         { | ||||
|             if ((pos + i) < buffer.size()) | ||||
|                 stream << fmt::format("{:01b}", (buffer[pos + i])); | ||||
|             else | ||||
|                 stream << "-- "; | ||||
|             if ((((pos + i + 1) % 8) == 0) and i != 0) | ||||
|                 stream << "  "; | ||||
|         } | ||||
|         stream << std::endl; | ||||
|         pos += 40; | ||||
|     } | ||||
| } | ||||
| static std::vector<bool> encode_data(uint8_t input) | ||||
| { | ||||
|     /* | ||||
|      * Four 8-bit data bytes are converted to four 10-bit GCR bytes at a time by | ||||
|      * the 1541 DOS.  RAM is only an 8-bit storage device though. This hardware | ||||
|      * limitation prevents a 10-bit GCR byte from being stored in a single | ||||
|      * memory location. Four 10-bit GCR bytes total 40 bits - a number evenly | ||||
|      * divisible by our overriding 8-bit constraint. Commodore sub- divides the | ||||
|      * 40 GCR bits into five 8-bit bytes to solve this dilemma. This explains | ||||
|      * why four 8-bit data bytes are converted to GCR form at a time. The | ||||
|      * following step by step example demonstrates how this bit manipulation is | ||||
|      * performed by the DOS. | ||||
|      * | ||||
|      * STEP 1. Four 8-bit Data Bytes | ||||
|      * $08 $10 $00 $12 | ||||
|      * | ||||
|      * STEP 2. Hexadecimal to Binary Conversion | ||||
|      * 1. Binary Equivalents | ||||
|      * $08       $10         $00         $12 | ||||
|      * 00001000  00010000    00000000    00010010 | ||||
|      * | ||||
|      * STEP 3. Binary to GCR Conversion | ||||
|      * 1. Four 8-bit Data Bytes | ||||
|      * 00001000 00010000 00000000 00010010 | ||||
|      * 2. High and Low Nybbles | ||||
|      * 0000 1000 0001 0000 0000 0000 0001 0010 | ||||
|      * 3. High and Low Nybble GCR Equivalents | ||||
|      * 01010 01001 01011 01010 01010 01010 01011 10010 | ||||
|      * 4. Four 10-bit GCR Bytes | ||||
|      * 0101001001 0101101010 0101001010 0101110010 | ||||
|      * | ||||
|      * STEP 4. 10-bit GCR to 8-bit GCR Conversion | ||||
|      *   1. Concatenate Four 10-bit GCR Bytes | ||||
|      *   0101001001010110101001010010100101110010 | ||||
|      *   2. Five 8-bit Subdivisions | ||||
|      *   01010010 01010110 10100101 00101001 01110010 | ||||
|      * | ||||
|      * STEP 5. Binary to Hexadecimal Conversion | ||||
|      * 1. Hexadecimal Equivalents | ||||
|      *   01010010    01010110    10100101    00101001    01110010 | ||||
|      *   $52         $56         $A5         $29         $72 | ||||
|      * | ||||
|      * STEP 6. Four 8-bit Data Bytes are Recorded as Five 8-bit GCR Bytes | ||||
|      *   $08 $10 $00 $12 | ||||
|      * | ||||
|      * are recorded as | ||||
|      *   $52 $56 $A5 $29 $72 | ||||
|      */ | ||||
|  | ||||
|     std::vector<bool> output(10, false); | ||||
|     uint8_t hi = 0; | ||||
|     uint8_t lo = 0; | ||||
|     uint8_t lo_GCR = 0; | ||||
|     uint8_t hi_GCR = 0; | ||||
|  | ||||
|     // Convert the byte in high and low nibble | ||||
|     lo = input >> 4; // get the lo nibble shift the bits 4 to the right | ||||
|     hi = input & 15; // get the hi nibble bij masking the lo bits (00001111) | ||||
|  | ||||
|     lo_GCR = encode_data_gcr(lo); // example value: 0000   GCR = 01010 | ||||
|     hi_GCR = encode_data_gcr(hi); // example value: 1000   GCR = 01001 | ||||
|     // output = [0,1,2,3,4,5,6,7,8,9] | ||||
|     // value  = [0,1,0,1,0,0,1,0,0,1] | ||||
|     //           01010 01001 | ||||
|  | ||||
|     int b = 4; | ||||
|     for (int i = 0; i < 10; i++) | ||||
|     { | ||||
|         if (i < 5)                        // 01234 | ||||
|         {                                 // i = 0 op | ||||
|             output[4 - i] = (lo_GCR & 1); // 01010 | ||||
|  | ||||
|             // 01010 -> & 00001 -> 00000 output[4] = 0 | ||||
|             // 00101 -> & 00001 -> 00001 output[3] = 1 | ||||
|             // 00010 -> & 00001 -> 00000 output[2] = 0 | ||||
|             // 00001 -> & 00001 -> 00001 output[1] = 1 | ||||
|             // 00000 -> & 00001 -> 00000 output[0] = 0 | ||||
|             lo_GCR >>= 1; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             output[i + b] = (hi_GCR & 1); // 01001 | ||||
|             // 01001 -> & 00001 -> 00001 output[9] = 1 | ||||
|             // 00100 -> & 00001 -> 00000 output[8] = 0 | ||||
|             // 00010 -> & 00001 -> 00000 output[7] = 0 | ||||
|             // 00001 -> & 00001 -> 00001 output[6] = 1 | ||||
|             // 00000 -> & 00001 -> 00000 output[5] = 0 | ||||
|             hi_GCR >>= 1; | ||||
|             b = b - 2; | ||||
|         } | ||||
|     } | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| class Commodore64Encoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     Commodore64Encoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.c64()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         /* The format ID Character # 1 and # 2 are in the .d64 image only | ||||
|          * present in track 18 sector zero which contains the BAM info in byte | ||||
|          * 162 and 163. it is written in every header of every sector and track. | ||||
|          * headers are not stored in a d64 disk image so we have to get it from | ||||
|          * track 18 which contains the BAM. | ||||
|          */ | ||||
|  | ||||
|         const auto& sectorData = image.get( | ||||
|             C64_BAM_TRACK, 0, 0); // Read de BAM to get the DISK ID bytes | ||||
|         if (sectorData) | ||||
|         { | ||||
|             ByteReader br(sectorData->data); | ||||
|             br.seek(162); // goto position of the first Disk ID Byte | ||||
|             _formatByte1 = br.read_8(); | ||||
|             _formatByte2 = br.read_8(); | ||||
|         } | ||||
|         else | ||||
|             _formatByte1 = _formatByte2 = 0; | ||||
|  | ||||
|         double clockRateUs = clockPeriodForC64Track(trackInfo->logicalTrack); | ||||
|         int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
|  | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         fillBitmapTo(bits, | ||||
|             cursor, | ||||
|             _config.post_index_gap_us() / clockRateUs, | ||||
|             {true, false}); | ||||
|         lastBit = false; | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|             writeSector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits( | ||||
|             bits, calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeSector(std::vector<bool>& bits, | ||||
|         unsigned& cursor, | ||||
|         std::shared_ptr<const Sector> sector) const | ||||
|     { | ||||
|         /* Source: http://www.unusedino.de/ec64/technical/formats/g64.html | ||||
|          * 1. Header sync       FF FF FF FF FF (40 'on' bits, not GCR) | ||||
|          * 2. Header info       52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) | ||||
|          * 3. Header gap        55 55 55 55 55 55 55 55 55 (9 bytes, never read) | ||||
|          * 4. Data sync         FF FF FF FF FF (40 'on' bits, not GCR) | ||||
|          * 5. Data block        55...4A (325 GCR bytes) | ||||
|          * 6. Inter-sector gap  55 55 55 55...55 55 (4 to 12 bytes, never read) | ||||
|          * 1. Header sync       (SYNC for the next sector) | ||||
|          */ | ||||
|         if ((sector->status == Sector::OK) or | ||||
|             (sector->status == Sector::BAD_CHECKSUM)) | ||||
|         { | ||||
|             // There is data to encode to disk. | ||||
|             if ((sector->data.size() != C64_SECTOR_LENGTH)) | ||||
|                 Error() << fmt::format( | ||||
|                     "unsupported sector size {} --- you must pick 256", | ||||
|                     sector->data.size()); | ||||
|  | ||||
|             // 1. Write header Sync (not GCR) | ||||
|             for (int i = 0; i < 6; i++) | ||||
|                 write_bits( | ||||
|                     bits, cursor, C64_HEADER_DATA_SYNC, 1 * 8); /* sync */ | ||||
|  | ||||
|             // 2. Write Header info 10 GCR bytes | ||||
|             /* | ||||
|              * The 10 byte header info (#2) is GCR encoded and must be decoded | ||||
|              * to it's normal 8 bytes to be understood. Once decoded, its | ||||
|              * breakdown is as follows: | ||||
|              * | ||||
|              * Byte $00 - header block ID           ($08) | ||||
|              *   01 - header block checksum 16  (EOR of $02-$05) | ||||
|              *   02 - Sector | ||||
|              *   03 - Track | ||||
|              *   04 - Format ID byte #2 | ||||
|              *   05 - Format ID byte #1 | ||||
|              *   06-07 - $0F ("off" bytes) | ||||
|              */ | ||||
|             uint8_t encodedTrack = | ||||
|                 ((sector->logicalTrack) + | ||||
|                     1); // C64 track numbering starts with 1. Fluxengine with 0. | ||||
|             uint8_t encodedSector = sector->logicalSector; | ||||
|             // uint8_t formatByte1 = C64_FORMAT_ID_BYTE1; | ||||
|             // uint8_t formatByte2 = C64_FORMAT_ID_BYTE2; | ||||
|             uint8_t headerChecksum = | ||||
|                 (encodedTrack ^ encodedSector ^ _formatByte1 ^ _formatByte2); | ||||
|             write_bits(bits, cursor, encode_data(C64_HEADER_BLOCK_ID)); | ||||
|             write_bits(bits, cursor, encode_data(headerChecksum)); | ||||
|             write_bits(bits, cursor, encode_data(encodedSector)); | ||||
|             write_bits(bits, cursor, encode_data(encodedTrack)); | ||||
|             write_bits(bits, cursor, encode_data(_formatByte2)); | ||||
|             write_bits(bits, cursor, encode_data(_formatByte1)); | ||||
|             write_bits(bits, cursor, encode_data(C64_PADDING)); | ||||
|             write_bits(bits, cursor, encode_data(C64_PADDING)); | ||||
|  | ||||
|             // 3. Write header GAP not GCR | ||||
|             for (int i = 0; i < 9; i++) | ||||
|                 write_bits( | ||||
|                     bits, cursor, C64_HEADER_GAP, 1 * 8); /* header gap */ | ||||
|  | ||||
|             // 4. Write Data sync not GCR | ||||
|             for (int i = 0; i < 6; i++) | ||||
|                 write_bits( | ||||
|                     bits, cursor, C64_HEADER_DATA_SYNC, 1 * 8); /* sync */ | ||||
|  | ||||
|             // 5. Write data block 325 GCR bytes | ||||
|             /* | ||||
|              * The 325 byte data block (#5) is GCR encoded and must be  decoded | ||||
|              * to  its normal 260 bytes to be understood. The data block is made | ||||
|              * up of the following: | ||||
|              * | ||||
|              * Byte    $00 - data block ID ($07) | ||||
|              *      01-100 - 256 bytes data | ||||
|              *      101 - data block checksum (EOR of $01-100) | ||||
|              *      102-103 - $00 ("off" bytes, to make the sector size a | ||||
|              * multiple of 5) | ||||
|              */ | ||||
|  | ||||
|             write_bits(bits, cursor, encode_data(C64_DATA_BLOCK_ID)); | ||||
|             uint8_t dataChecksum = xorBytes(sector->data); | ||||
|             ByteReader br(sector->data); | ||||
|             int i = 0; | ||||
|             for (i = 0; i < C64_SECTOR_LENGTH; i++) | ||||
|             { | ||||
|                 uint8_t val = br.read_8(); | ||||
|                 write_bits(bits, cursor, encode_data(val)); | ||||
|             } | ||||
|             write_bits(bits, cursor, encode_data(dataChecksum)); | ||||
|             write_bits(bits, cursor, encode_data(C64_PADDING)); | ||||
|             write_bits(bits, cursor, encode_data(C64_PADDING)); | ||||
|  | ||||
|             // 6. Write inter-sector gap 9 - 12 bytes nor gcr | ||||
|             for (int i = 0; i < 9; i++) | ||||
|                 write_bits( | ||||
|                     bits, cursor, C64_INTER_SECTOR_GAP, 1 * 8); /* sync */ | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Commodore64EncoderProto& _config; | ||||
|     uint8_t _formatByte1; | ||||
|     uint8_t _formatByte2; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createCommodore64Encoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Commodore64Encoder(config)); | ||||
| } | ||||
|  | ||||
| // vim: sw=4 ts=4 et | ||||
| @@ -2,6 +2,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "f85.h" | ||||
| @@ -25,7 +26,7 @@ static int decode_data_gcr(uint8_t gcr) | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| static Bytes decode(const std::vector<bool>& bits) | ||||
| { | ||||
| @@ -51,59 +52,49 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| class DurangoF85Decoder : public Decoder | ||||
| AbstractDecoder::RecordType DurangoF85Decoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
| 	DurangoF85Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip sync bits and ID byte. */ | ||||
|  | ||||
| 		if (readRaw24() != F85_SECTOR_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|  | ||||
| 		const auto& bytes = decode(readRawBits(6*10)); | ||||
|  | ||||
| 		_sector->logicalSector = bytes[2]; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalTrack = bytes[0]; | ||||
|  | ||||
| 		uint16_t wantChecksum = bytes.reader().seek(4).read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4)); | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Skip sync bits ID byte. */ | ||||
|  | ||||
| 		if (readRaw24() != F85_DATA_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10)) | ||||
| 			.slice(0, F85_SECTOR_LENGTH+3); | ||||
| 		ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->data = br.read(F85_SECTOR_LENGTH); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new DurangoF85Decoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return RecordType::DATA_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void DurangoF85Decoder::decodeSectorRecord() | ||||
| { | ||||
|     /* Skip sync bits and ID byte. */ | ||||
|  | ||||
|     readRawBits(24); | ||||
|  | ||||
|     /* Read header. */ | ||||
|  | ||||
|     const auto& bytes = decode(readRawBits(6*10)); | ||||
|  | ||||
|     _sector->logicalSector = bytes[2]; | ||||
|     _sector->logicalSide = 0; | ||||
|     _sector->logicalTrack = bytes[0]; | ||||
|  | ||||
|     uint16_t wantChecksum = bytes.reader().seek(4).read_be16(); | ||||
|     uint16_t gotChecksum = crc16(CCITT_POLY, 0xef21, bytes.slice(0, 4)); | ||||
|     if (wantChecksum == gotChecksum) | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| } | ||||
|  | ||||
| void DurangoF85Decoder::decodeDataRecord() | ||||
| { | ||||
|     /* Skip sync bits ID byte. */ | ||||
|  | ||||
|     readRawBits(24); | ||||
|  | ||||
|     const auto& bytes = decode(readRawBits((F85_SECTOR_LENGTH+3)*10)) | ||||
|         .slice(0, F85_SECTOR_LENGTH+3); | ||||
|     ByteReader br(bytes); | ||||
|  | ||||
|     _sector->data = br.read(F85_SECTOR_LENGTH); | ||||
|     uint16_t wantChecksum = br.read_be16(); | ||||
|     uint16_t gotChecksum = crc16(CCITT_POLY, 0xbf84, _sector->data); | ||||
|     _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,17 @@ | ||||
| #define F85_DATA_RECORD 0xffffcb /* 1111 1111 1111 1111 1100 1101 */ | ||||
| #define F85_SECTOR_LENGTH    512 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createDurangoF85Decoder(const DecoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class DurangoF85Decoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~DurangoF85Decoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message F85DecoderProto {} | ||||
|  | ||||
| @@ -2,12 +2,14 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "fb100.h" | ||||
| #include "crc.h" | ||||
| #include "bytes.h" | ||||
| #include "decoders/rawbits.h" | ||||
| #include "track.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
| @@ -97,48 +99,37 @@ static uint16_t checksum(const Bytes& bytes) | ||||
|     return (crchi << 8) | crclo; | ||||
| } | ||||
|  | ||||
| class Fb100Decoder : public Decoder | ||||
| AbstractDecoder::RecordType Fb100Decoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
| 	Fb100Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_ID_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(FB100_RECORD_SIZE*16); | ||||
|  | ||||
| 		const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE); | ||||
| 		ByteReader br(bytes); | ||||
| 		br.seek(1); | ||||
| 		const Bytes id = br.read(FB100_ID_SIZE); | ||||
| 		uint16_t wantIdCrc = br.read_be16(); | ||||
| 		uint16_t gotIdCrc = checksum(id); | ||||
| 		const Bytes payload = br.read(FB100_PAYLOAD_SIZE); | ||||
| 		uint16_t wantPayloadCrc = br.read_be16(); | ||||
| 		uint16_t gotPayloadCrc = checksum(payload); | ||||
|  | ||||
| 		if (wantIdCrc != gotIdCrc) | ||||
| 			return; | ||||
|  | ||||
| 		uint8_t abssector = id[2]; | ||||
| 		_sector->logicalTrack = abssector >> 1; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalSector = abssector & 1; | ||||
| 		_sector->data.writer().append(id.slice(5, 12)).append(payload); | ||||
|  | ||||
| 		_sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Fb100Decoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(SECTOR_ID_PATTERN, matcher); | ||||
|     if (matcher == &SECTOR_ID_PATTERN) | ||||
| 		return RecordType::SECTOR_RECORD; | ||||
| 	return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void Fb100Decoder::decodeSectorRecord() | ||||
| { | ||||
|     auto rawbits = readRawBits(FB100_RECORD_SIZE*16); | ||||
|  | ||||
|     const Bytes bytes = decodeFmMfm(rawbits).slice(0, FB100_RECORD_SIZE); | ||||
|     ByteReader br(bytes); | ||||
|     br.seek(1); | ||||
|     const Bytes id = br.read(FB100_ID_SIZE); | ||||
|     uint16_t wantIdCrc = br.read_be16(); | ||||
|     uint16_t gotIdCrc = checksum(id); | ||||
|     const Bytes payload = br.read(FB100_PAYLOAD_SIZE); | ||||
|     uint16_t wantPayloadCrc = br.read_be16(); | ||||
|     uint16_t gotPayloadCrc = checksum(payload); | ||||
|  | ||||
|     if (wantIdCrc != gotIdCrc) | ||||
|         return; | ||||
|  | ||||
|     uint8_t abssector = id[2]; | ||||
|     _sector->logicalTrack = abssector >> 1; | ||||
|     _sector->logicalSide = 0; | ||||
|     _sector->logicalSector = abssector & 1; | ||||
|     _sector->data.writer().append(id.slice(5, 12)).append(payload); | ||||
|  | ||||
|     _sector->status = (wantPayloadCrc == gotPayloadCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
| @@ -5,7 +5,18 @@ | ||||
| #define FB100_ID_SIZE 17 | ||||
| #define FB100_PAYLOAD_SIZE 0x500 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createFb100Decoder(const DecoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
| class Track; | ||||
|  | ||||
| class Fb100Decoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~Fb100Decoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message Fb100DecoderProto {} | ||||
|  | ||||
| @@ -5,27 +5,25 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include "arch/ibm/ibm.pb.h" | ||||
| #include "proto.h" | ||||
| #include "lib/layout.h" | ||||
| #include "record.h" | ||||
| #include <string.h> | ||||
|  | ||||
| static_assert(std::is_trivially_copyable<IbmIdam>::value, | ||||
|     "IbmIdam is not trivially copyable"); | ||||
| 		"IbmIdam is not trivially copyable"); | ||||
|  | ||||
| /* | ||||
|  * The markers at the beginning of records are special, and have | ||||
|  * missing clock pulses, allowing them to be found by the logic. | ||||
|  * | ||||
|  *  | ||||
|  * IAM record: | ||||
|  * flux:   XXXX-XXX-XXXX-X- = 0xf77a | ||||
|  * clock:  X X - X - X X X  = 0xd7 | ||||
|  * data:    X X X X X X - - = 0xfc | ||||
|  * | ||||
|  *  | ||||
|  * (We just ignore this one --- it's useless and optional.) | ||||
|  */ | ||||
|  | ||||
| /* | ||||
| /*  | ||||
|  * IDAM record: | ||||
|  * flux:   XXXX-X-X-XXXXXX- = 0xf57e | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
| @@ -33,7 +31,7 @@ static_assert(std::is_trivially_copyable<IbmIdam>::value, | ||||
|  */ | ||||
| const FluxPattern FM_IDAM_PATTERN(16, 0xf57e); | ||||
|  | ||||
| /* | ||||
| /*  | ||||
|  * DAM1 record: | ||||
|  * flux:   XXXX-X-X-XX-X-X- = 0xf56a | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
| @@ -41,7 +39,7 @@ const FluxPattern FM_IDAM_PATTERN(16, 0xf57e); | ||||
|  */ | ||||
| const FluxPattern FM_DAM1_PATTERN(16, 0xf56a); | ||||
|  | ||||
| /* | ||||
| /*  | ||||
|  * DAM2 record: | ||||
|  * flux:   XXXX-X-X-XX-XXXX = 0xf56f | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
| @@ -49,7 +47,7 @@ const FluxPattern FM_DAM1_PATTERN(16, 0xf56a); | ||||
|  */ | ||||
| const FluxPattern FM_DAM2_PATTERN(16, 0xf56f); | ||||
|  | ||||
| /* | ||||
| /*  | ||||
|  * TRS80DAM1 record: | ||||
|  * flux:   XXXX-X-X-XX-X-XX = 0xf56b | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
| @@ -57,13 +55,13 @@ const FluxPattern FM_DAM2_PATTERN(16, 0xf56f); | ||||
|  */ | ||||
| const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b); | ||||
|  | ||||
| /* | ||||
| /*  | ||||
|  * TRS80DAM2 record: | ||||
|  * flux:   XXXX-X-X-XX-XXX- = 0xf56e | ||||
|  * flux:   XXXX-X-X-XX-XXX- = 0xf56c | ||||
|  * clock:  X X - - - X X X  = 0xc7 | ||||
|  * data:    X X X X X - X - = 0xfa | ||||
|  */ | ||||
| const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e); | ||||
| const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56c); | ||||
|  | ||||
| /* MFM record separator: | ||||
|  * 0xA1 is: | ||||
| @@ -73,178 +71,83 @@ const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e); | ||||
|  *                       ^^^^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  * | ||||
|  *  | ||||
|  * shifted: 10 00 10 01 00 01 00 1 | ||||
|  * | ||||
|  * It's repeated three times. | ||||
|  */ | ||||
| const FluxPattern MFM_PATTERN(48, 0x448944894489LL); | ||||
| const FluxPattern MFM_PATTERN(16, 0x4489); | ||||
|  | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ | ||||
|     &MFM_PATTERN, | ||||
|     &FM_IDAM_PATTERN, | ||||
|     &FM_DAM1_PATTERN, | ||||
|     &FM_DAM2_PATTERN, | ||||
|     &FM_TRS80DAM1_PATTERN, | ||||
|     &FM_TRS80DAM2_PATTERN, | ||||
| }); | ||||
| const FluxMatchers ANY_RECORD_PATTERN( | ||||
|     { | ||||
|         &MFM_PATTERN, | ||||
|         &FM_IDAM_PATTERN, | ||||
|         &FM_DAM1_PATTERN, | ||||
|         &FM_DAM2_PATTERN, | ||||
|         &FM_TRS80DAM1_PATTERN, | ||||
|         &FM_TRS80DAM2_PATTERN, | ||||
|     } | ||||
| ); | ||||
|  | ||||
| class IbmDecoder : public Decoder | ||||
| AbstractDecoder::RecordType IbmDecoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
|     IbmDecoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.ibm()) | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
|  | ||||
|     /* If this is the MFM prefix byte, the the decoder is going to expect three | ||||
|      * extra bytes on the front of the header. */ | ||||
|     _currentHeaderLength = (matcher == &MFM_PATTERN) ? 3 : 0; | ||||
|  | ||||
|     Fluxmap::Position here = tell(); | ||||
|     if (_currentHeaderLength > 0) | ||||
|         readRawBits(_currentHeaderLength*16); | ||||
|     auto idbits = readRawBits(16); | ||||
|     uint8_t id = decodeFmMfm(idbits).slice(0, 1)[0]; | ||||
|     seek(here); | ||||
|      | ||||
|     switch (id) | ||||
|     { | ||||
|         case IBM_IDAM: | ||||
|             return RecordType::SECTOR_RECORD; | ||||
|  | ||||
|         case IBM_DAM1: | ||||
|         case IBM_DAM2: | ||||
|         case IBM_TRS80DAM1: | ||||
|         case IBM_TRS80DAM2: | ||||
|             return RecordType::DATA_RECORD; | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         return seekToPattern(ANY_RECORD_PATTERN); | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         /* This is really annoying because the IBM record scheme has a | ||||
|          * variable-sized header _and_ the checksum covers this header too. So | ||||
|          * we have to read and decode a byte at a time until we know where the | ||||
|          * record itself starts, saving the bytes for the checksumming later. | ||||
|          */ | ||||
|  | ||||
|         Bytes bytes; | ||||
|         ByteWriter bw(bytes); | ||||
|  | ||||
|         auto readByte = [&]() | ||||
|         { | ||||
|             auto bits = readRawBits(16); | ||||
|             auto bytes = decodeFmMfm(bits).slice(0, 1); | ||||
|             uint8_t byte = bytes[0]; | ||||
|             bw.write_8(byte); | ||||
|             return byte; | ||||
|         }; | ||||
|  | ||||
|         uint8_t id = readByte(); | ||||
|         if (id == 0xa1) | ||||
|         { | ||||
|             readByte(); | ||||
|             readByte(); | ||||
|             id = readByte(); | ||||
|         } | ||||
|         if (id != IBM_IDAM) | ||||
|             return; | ||||
|  | ||||
|         ByteReader br(bytes); | ||||
|         br.seek(bw.pos); | ||||
|  | ||||
|         auto bits = readRawBits(IBM_IDAM_LEN * 16); | ||||
|         bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN); | ||||
|  | ||||
|         IbmDecoderProto::TrackdataProto trackdata; | ||||
|         getTrackFormat( | ||||
|             trackdata, _sector->physicalTrack, _sector->physicalSide); | ||||
|  | ||||
|         _sector->logicalTrack = br.read_8(); | ||||
|         _sector->logicalSide = br.read_8(); | ||||
|         _sector->logicalSector = br.read_8(); | ||||
|         _currentSectorSize = 1 << (br.read_8() + 7); | ||||
|  | ||||
|         uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos)); | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         if (wantCrc == gotCrc) | ||||
|             _sector->status = | ||||
|                 Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
|  | ||||
|         if (trackdata.ignore_side_byte()) | ||||
|             _sector->logicalSide = | ||||
|                 Layout::remapSidePhysicalToLogical(_sector->physicalSide); | ||||
|         _sector->logicalSide ^= trackdata.invert_side_byte(); | ||||
|         if (trackdata.ignore_track_byte()) | ||||
|             _sector->logicalTrack = _sector->physicalTrack; | ||||
|  | ||||
|         for (int sector : trackdata.ignore_sector()) | ||||
|             if (_sector->logicalSector == sector) | ||||
|             { | ||||
|                 _sector->status = Sector::MISSING; | ||||
|                 break; | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
|     { | ||||
|         /* This is the same deal as the sector record. */ | ||||
|  | ||||
|         Bytes bytes; | ||||
|         ByteWriter bw(bytes); | ||||
|  | ||||
|         auto readByte = [&]() | ||||
|         { | ||||
|             auto bits = readRawBits(16); | ||||
|             auto bytes = decodeFmMfm(bits).slice(0, 1); | ||||
|             uint8_t byte = bytes[0]; | ||||
|             bw.write_8(byte); | ||||
|             return byte; | ||||
|         }; | ||||
|  | ||||
|         uint8_t id = readByte(); | ||||
|         if (id == 0xa1) | ||||
|         { | ||||
|             readByte(); | ||||
|             readByte(); | ||||
|             id = readByte(); | ||||
|         } | ||||
|         if ((id != IBM_DAM1) && (id != IBM_DAM2) && (id != IBM_TRS80DAM1) && | ||||
|             (id != IBM_TRS80DAM2)) | ||||
|             return; | ||||
|  | ||||
|         ByteReader br(bytes); | ||||
|         br.seek(bw.pos); | ||||
|  | ||||
|         auto bits = readRawBits((_currentSectorSize + 2) * 16); | ||||
|         bw += decodeFmMfm(bits).slice(0, _currentSectorSize + 2); | ||||
|  | ||||
|         _sector->data = br.read(_currentSectorSize); | ||||
|         uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos)); | ||||
|         uint16_t wantCrc = br.read_be16(); | ||||
|         _sector->status = | ||||
|             (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|  | ||||
|         auto layout = Layout::getLayoutOfTrack( | ||||
|             _sector->logicalTrack, _sector->logicalSide); | ||||
|         if (_currentSectorSize != layout->sectorSize) | ||||
|             std::cerr << fmt::format( | ||||
|                 "Warning: configured sector size for t{}.h{}.s{} is {} bytes " | ||||
|                 "but that seen on disk is {} bytes\n", | ||||
|                 _sector->logicalTrack, | ||||
|                 _sector->logicalSide, | ||||
|                 _sector->logicalSector, | ||||
|                 layout->sectorSize, | ||||
|                 _currentSectorSize); | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata, | ||||
|         unsigned track, | ||||
|         unsigned head) const | ||||
|     { | ||||
|         trackdata.Clear(); | ||||
|         for (const auto& f : _config.trackdata()) | ||||
|         { | ||||
|             if (f.has_track() && (f.track() != track)) | ||||
|                 continue; | ||||
|             if (f.has_head() && (f.head() != head)) | ||||
|                 continue; | ||||
|  | ||||
|             trackdata.MergeFrom(f); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const IbmDecoderProto& _config; | ||||
|     unsigned _currentSectorSize; | ||||
|     unsigned _currentHeaderLength; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createIbmDecoder(const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new IbmDecoder(config)); | ||||
|     return RecordType::UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void IbmDecoder::decodeSectorRecord() | ||||
| { | ||||
|     unsigned recordSize = _currentHeaderLength + IBM_IDAM_LEN; | ||||
|     auto bits = readRawBits(recordSize*16); | ||||
|     auto bytes = decodeFmMfm(bits).slice(0, recordSize); | ||||
|  | ||||
|     ByteReader br(bytes); | ||||
|     br.seek(_currentHeaderLength); | ||||
|     br.read_8(); /* skip ID byte */ | ||||
|     _sector->logicalTrack = br.read_8(); | ||||
|     _sector->logicalSide = br.read_8(); | ||||
|     _sector->logicalSector = br.read_8() - _sectorBase; | ||||
|     _currentSectorSize = 1 << (br.read_8() + 7); | ||||
|     uint16_t wantCrc = br.read_be16(); | ||||
|     uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, _currentHeaderLength + 5)); | ||||
|     if (wantCrc == gotCrc) | ||||
|         _sector->status = Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
| } | ||||
|  | ||||
| void IbmDecoder::decodeDataRecord() | ||||
| { | ||||
|     unsigned recordLength = _currentHeaderLength + _currentSectorSize + 3; | ||||
|     auto bits = readRawBits(recordLength*16); | ||||
|     auto bytes = decodeFmMfm(bits).slice(0, recordLength); | ||||
|  | ||||
|     ByteReader br(bytes); | ||||
|     br.seek(_currentHeaderLength); | ||||
|     br.read_8(); /* skip ID byte */ | ||||
|  | ||||
|     _sector->data = br.read(_currentSectorSize); | ||||
|     uint16_t wantCrc = br.read_be16(); | ||||
|     uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, recordLength-2)); | ||||
|     _sector->status = (wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,281 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "ibm.h" | ||||
| #include "crc.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "arch/ibm/ibm.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/proto.h" | ||||
| #include "lib/layout.h" | ||||
| #include <ctype.h> | ||||
|  | ||||
| /* IAM record separator: | ||||
|  * 0xC2 is: | ||||
|  * data:    1  1  0  0  0  0  1  0  = 0xc2 | ||||
|  * mfm:     01 01 00 10 10 10 01 00 = 0x5254 | ||||
|  * special: 01 01 00 10 00 10 01 00 = 0x5224 | ||||
|  */ | ||||
| #define MFM_IAM_SEPARATOR 0x5224 | ||||
|  | ||||
| /* FM IAM record: | ||||
|  * flux:   XXXX-XXX-XXXX-X- = 0xf77a | ||||
|  * clock:  X X - X - X X X  = 0xd7 | ||||
|  * data:    X X X X X X - - = 0xfc | ||||
|  */ | ||||
| #define FM_IAM_RECORD 0xf77a | ||||
|  | ||||
| /* MFM IAM record: | ||||
|  * data:   1  1  1  1  1  1  0  0  = 0xfc | ||||
|  * flux:   01 01 01 01 01 01 00 10 = 0x5552 | ||||
|  */ | ||||
| #define MFM_IAM_RECORD 0x5552 | ||||
|  | ||||
| /* MFM record separator: | ||||
|  * 0xA1 is: | ||||
|  * data:    1  0  1  0  0  0  0  1  = 0xa1 | ||||
|  * mfm:     01 00 01 00 10 10 10 01 = 0x44a9 | ||||
|  * special: 01 00 01 00 10 00 10 01 = 0x4489 | ||||
|  *                       ^^^^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  * | ||||
|  * shifted: 10 00 10 01 00 01 00 1 | ||||
|  * | ||||
|  * It's repeated three times. | ||||
|  */ | ||||
| #define MFM_RECORD_SEPARATOR 0x4489 | ||||
| #define MFM_RECORD_SEPARATOR_BYTE 0xa1 | ||||
|  | ||||
| /* MFM IDAM byte: | ||||
|  * data:    1  1  1  1  1  1  1  0  = 0xfe | ||||
|  * mfm:     01 01 01 01 01 01 01 00 = 0x5554 | ||||
|  */ | ||||
|  | ||||
| /* MFM DAM byte: | ||||
|  * data:    1  1  1  1  1  0  1  1  = 0xfb | ||||
|  * mfm:     01 01 01 01 01 00 01 01 = 0x5545 | ||||
|  */ | ||||
|  | ||||
| static uint8_t decodeUint16(uint16_t raw) | ||||
| { | ||||
|     Bytes b; | ||||
|     ByteWriter bw(b); | ||||
|     bw.write_be16(raw); | ||||
|     return decodeFmMfm(b.toBits())[0]; | ||||
| } | ||||
|  | ||||
| class IbmEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     IbmEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.ibm()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeRawBits(uint32_t data, int width) | ||||
|     { | ||||
|         _cursor += width; | ||||
|         _lastBit = data & 1; | ||||
|         for (int i = 0; i < width; i++) | ||||
|         { | ||||
|             unsigned pos = _cursor - i - 1; | ||||
|             if (pos < _bits.size()) | ||||
|                 _bits[pos] = data & 1; | ||||
|             data >>= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void getEncoderTrackData(IbmEncoderProto::TrackdataProto& trackdata, | ||||
|         unsigned track, | ||||
|         unsigned head) | ||||
|     { | ||||
|         trackdata.Clear(); | ||||
|         for (const auto& f : _config.trackdata()) | ||||
|         { | ||||
|             if (f.has_track() && (f.track() != track)) | ||||
|                 continue; | ||||
|             if (f.has_head() && (f.head() != head)) | ||||
|                 continue; | ||||
|  | ||||
|             trackdata.MergeFrom(f); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         IbmEncoderProto::TrackdataProto trackdata; | ||||
|         getEncoderTrackData(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         auto trackLayout = | ||||
|             Layout::getLayoutOfTrack(trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         auto writeBytes = [&](const Bytes& bytes) | ||||
|         { | ||||
|             if (trackdata.use_fm()) | ||||
|                 encodeFm(_bits, _cursor, bytes); | ||||
|             else | ||||
|                 encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
|         }; | ||||
|  | ||||
|         auto writeFillerRawBytes = [&](int count, uint16_t byte) | ||||
|         { | ||||
|             for (int i = 0; i < count; i++) | ||||
|                 writeRawBits(byte, 16); | ||||
|         }; | ||||
|  | ||||
|         auto writeFillerBytes = [&](int count, uint8_t byte) | ||||
|         { | ||||
|             Bytes b{byte}; | ||||
|             for (int i = 0; i < count; i++) | ||||
|                 writeBytes(b); | ||||
|         }; | ||||
|  | ||||
|         double clockRateUs = trackdata.target_clock_period_us(); | ||||
|         if (!trackdata.use_fm()) | ||||
|             clockRateUs /= 2.0; | ||||
|         int bitsPerRevolution = | ||||
|             (trackdata.target_rotational_period_ms() * 1000.0) / clockRateUs; | ||||
|         _bits.resize(bitsPerRevolution); | ||||
|         _cursor = 0; | ||||
|  | ||||
|         uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte()); | ||||
|         uint8_t damUnencoded = decodeUint16(trackdata.dam_byte()); | ||||
|  | ||||
|         uint8_t sectorSize = 0; | ||||
|         { | ||||
|             int s = trackLayout->sectorSize >> 7; | ||||
|             while (s > 1) | ||||
|             { | ||||
|                 s >>= 1; | ||||
|                 sectorSize += 1; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         uint16_t gapFill = trackdata.gap_fill_byte(); | ||||
|  | ||||
|         writeFillerRawBytes(trackdata.gap0(), gapFill); | ||||
|         if (trackdata.emit_iam()) | ||||
|         { | ||||
|             writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); | ||||
|             if (!trackdata.use_fm()) | ||||
|             { | ||||
|                 for (int i = 0; i < 3; i++) | ||||
|                     writeRawBits(MFM_IAM_SEPARATOR, 16); | ||||
|             } | ||||
|             writeRawBits( | ||||
|                 trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16); | ||||
|             writeFillerRawBytes(trackdata.gap1(), gapFill); | ||||
|         } | ||||
|  | ||||
|         bool first = true; | ||||
|         for (const auto& sectorData : sectors) | ||||
|         { | ||||
|             if (!first) | ||||
|                 writeFillerRawBytes(trackdata.gap3(), gapFill); | ||||
|             first = false; | ||||
|  | ||||
|             /* Writing the sector and data records are fantastically annoying. | ||||
|              * The CRC is calculated from the *very start* of the record, and | ||||
|              * include the malformed marker bytes. Our encoder doesn't know | ||||
|              * about this, of course, with the result that we have to construct | ||||
|              * the unencoded header, calculate the checksum, and then use the | ||||
|              * same logic to emit the bytes which require special encoding | ||||
|              * before encoding the rest of the header normally. */ | ||||
|  | ||||
|             { | ||||
|                 Bytes header; | ||||
|                 ByteWriter bw(header); | ||||
|  | ||||
|                 writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); | ||||
|                 if (!trackdata.use_fm()) | ||||
|                 { | ||||
|                     for (int i = 0; i < 3; i++) | ||||
|                         bw.write_8(MFM_RECORD_SEPARATOR_BYTE); | ||||
|                 } | ||||
|                 bw.write_8(idamUnencoded); | ||||
|                 bw.write_8(sectorData->logicalTrack); | ||||
|                 bw.write_8( | ||||
|                     sectorData->logicalSide ^ trackdata.invert_side_byte()); | ||||
|                 bw.write_8(sectorData->logicalSector); | ||||
|                 bw.write_8(sectorSize); | ||||
|                 uint16_t crc = crc16(CCITT_POLY, header); | ||||
|                 bw.write_be16(crc); | ||||
|  | ||||
|                 int conventionalHeaderStart = 0; | ||||
|                 if (!trackdata.use_fm()) | ||||
|                 { | ||||
|                     for (int i = 0; i < 3; i++) | ||||
|                         writeRawBits(MFM_RECORD_SEPARATOR, 16); | ||||
|                     conventionalHeaderStart += 3; | ||||
|                 } | ||||
|                 writeRawBits(trackdata.idam_byte(), 16); | ||||
|                 conventionalHeaderStart += 1; | ||||
|  | ||||
|                 writeBytes(header.slice(conventionalHeaderStart)); | ||||
|             } | ||||
|  | ||||
|             writeFillerRawBytes(trackdata.gap2(), gapFill); | ||||
|  | ||||
|             { | ||||
|                 Bytes data; | ||||
|                 ByteWriter bw(data); | ||||
|  | ||||
|                 writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00); | ||||
|                 if (!trackdata.use_fm()) | ||||
|                 { | ||||
|                     for (int i = 0; i < 3; i++) | ||||
|                         bw.write_8(MFM_RECORD_SEPARATOR_BYTE); | ||||
|                 } | ||||
|                 bw.write_8(damUnencoded); | ||||
|  | ||||
|                 Bytes truncatedData = | ||||
|                     sectorData->data.slice(0, trackLayout->sectorSize); | ||||
|                 bw += truncatedData; | ||||
|                 uint16_t crc = crc16(CCITT_POLY, data); | ||||
|                 bw.write_be16(crc); | ||||
|  | ||||
|                 int conventionalHeaderStart = 0; | ||||
|                 if (!trackdata.use_fm()) | ||||
|                 { | ||||
|                     for (int i = 0; i < 3; i++) | ||||
|                         writeRawBits(MFM_RECORD_SEPARATOR, 16); | ||||
|                     conventionalHeaderStart += 3; | ||||
|                 } | ||||
|                 writeRawBits(trackdata.dam_byte(), 16); | ||||
|                 conventionalHeaderStart += 1; | ||||
|  | ||||
|                 writeBytes(data.slice(conventionalHeaderStart)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         while (_cursor < _bits.size()) | ||||
|             writeFillerRawBytes(1, gapFill); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(clockRateUs * 1e3, | ||||
|                 trackdata.target_rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const IbmEncoderProto& _config; | ||||
|     std::vector<bool> _bits; | ||||
|     unsigned _cursor; | ||||
|     bool _lastBit; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createIbmEncoder(const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new IbmEncoder(config)); | ||||
| } | ||||
| @@ -1,39 +1,96 @@ | ||||
| #ifndef IBM_H | ||||
| #define IBM_H | ||||
|  | ||||
| #include "decoders/decoders.h" | ||||
|  | ||||
| /* IBM format (i.e. ordinary PC floppies). */ | ||||
|  | ||||
| #define IBM_MFM_SYNC 0xA1  /* sync byte for MFM */ | ||||
| #define IBM_IAM 0xFC       /* start-of-track record */ | ||||
| #define IBM_IAM_LEN 1      /* plus prologue */ | ||||
| #define IBM_IDAM 0xFE      /* sector header */ | ||||
| #define IBM_IDAM_LEN 7     /* plus prologue */ | ||||
| #define IBM_DAM1 0xF8      /* sector data (type 1) */ | ||||
| #define IBM_DAM2 0xFB      /* sector data (type 2) */ | ||||
| #define IBM_TRS80DAM1 0xF9 /* sector data (TRS-80 directory) */ | ||||
| #define IBM_TRS80DAM2 0xFA /* sector data (TRS-80 directory) */ | ||||
| #define IBM_DAM_LEN 1      /* plus prologue and user data */ | ||||
| #define IBM_MFM_SYNC   0xA1   /* sync byte for MFM */ | ||||
| #define IBM_IAM        0xFC   /* start-of-track record */ | ||||
| #define IBM_IAM_LEN    1      /* plus prologue */ | ||||
| #define IBM_IDAM       0xFE   /* sector header */ | ||||
| #define IBM_IDAM_LEN   7      /* plus prologue */ | ||||
| #define IBM_DAM1       0xF8   /* sector data (type 1) */ | ||||
| #define IBM_DAM2       0xFB   /* sector data (type 2) */ | ||||
| #define IBM_TRS80DAM1  0xF9   /* sector data (TRS-80 directory) */ | ||||
| #define IBM_TRS80DAM2  0xFA   /* sector data (TRS-80 directory) */ | ||||
| #define IBM_DAM_LEN    1      /* plus prologue and user data */ | ||||
|  | ||||
| /* Length of a DAM record is determined by the previous sector header. */ | ||||
|  | ||||
| struct IbmIdam | ||||
| { | ||||
|     uint8_t id; | ||||
|     uint8_t track; | ||||
|     uint8_t cylinder; | ||||
|     uint8_t side; | ||||
|     uint8_t sector; | ||||
|     uint8_t sectorSize; | ||||
|     uint8_t crc[2]; | ||||
| }; | ||||
|  | ||||
| class Encoder; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
| class IbmDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     IbmDecoder(unsigned sectorBase): | ||||
|         _sectorBase(sectorBase) | ||||
|     {} | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createIbmDecoder( | ||||
|     const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createIbmEncoder( | ||||
|     const EncoderProto& config); | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
|  | ||||
| private: | ||||
|     unsigned _sectorBase; | ||||
|     unsigned _currentSectorSize; | ||||
|     unsigned _currentHeaderLength; | ||||
| }; | ||||
|  | ||||
| #if 0 | ||||
| class AbstractIbmDecoder : public AbstractSoftSectorDecoder | ||||
| { | ||||
| public: | ||||
|     AbstractIbmDecoder(unsigned sectorIdBase): | ||||
|         _sectorIdBase(sectorIdBase) | ||||
|     {} | ||||
|     virtual ~AbstractIbmDecoder() {} | ||||
|  | ||||
|     SectorVector decodeToSectors(const RawRecordVector& rawRecords, unsigned physicalTrack, unsigned physicalSide); | ||||
|  | ||||
| protected: | ||||
|     virtual int skipHeaderBytes() const = 0; | ||||
|  | ||||
| private: | ||||
|     unsigned _sectorIdBase; | ||||
| }; | ||||
|  | ||||
| class IbmFmDecoder : public AbstractIbmDecoder | ||||
| { | ||||
| public: | ||||
|     IbmFmDecoder(unsigned sectorIdBase): | ||||
|         AbstractIbmDecoder(sectorIdBase) | ||||
|     {} | ||||
|  | ||||
|     int recordMatcher(uint64_t fifo) const; | ||||
|  | ||||
| protected: | ||||
|     int skipHeaderBytes() const | ||||
|     { return 0; } | ||||
| }; | ||||
|  | ||||
| class IbmMfmDecoder : public AbstractIbmDecoder | ||||
| { | ||||
| public: | ||||
|     IbmMfmDecoder(unsigned sectorIdBase): | ||||
|         AbstractIbmDecoder(sectorIdBase) | ||||
|     {} | ||||
|  | ||||
|     nanoseconds_t guessClock(Fluxmap& fluxmap) const; | ||||
|     int recordMatcher(uint64_t fifo) const; | ||||
|  | ||||
| protected: | ||||
|     int skipHeaderBytes() const | ||||
|     { return 3; } | ||||
| }; | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,43 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message IbmDecoderProto { | ||||
| 	// Next: 11 | ||||
| 	message TrackdataProto { | ||||
| 		optional int32 track = 7              [(help) = "if set, the format applies only to this track"]; | ||||
| 		optional int32 head = 8                  [(help) = "if set, the format applies only to this head"]; | ||||
|  | ||||
| 		optional bool ignore_side_byte = 2       [default = false, (help) = "ignore side byte in sector header"]; | ||||
| 		optional bool ignore_track_byte = 6      [default = false, (help) = "ignore track byte in sector header"]; | ||||
| 		optional bool invert_side_byte = 4       [default = false, (help) = "invert the side byte in the sector header"]; | ||||
|  | ||||
| 		repeated int32 ignore_sector = 10        [(help) = "sectors with these IDs will not be read"]; | ||||
| 	} | ||||
|  | ||||
| 	repeated TrackdataProto trackdata = 1; | ||||
| } | ||||
|  | ||||
| message IbmEncoderProto { | ||||
| 	// Next: 20 | ||||
| 	message TrackdataProto { | ||||
| 		optional int32 track = 15        [(help) = "if set, the format applies only to this track"]; | ||||
| 		optional int32 head = 16            [(help) = "if set, the format applies only to this head"]; | ||||
|  | ||||
| 		optional bool emit_iam = 3          [default=true, (help) = "whether to emit an IAM record"]; | ||||
| 		optional double target_clock_period_us = 5  [default=4, (help) = "data clock rate on target disk"]; | ||||
| 		optional bool use_fm = 6            [default=false, (help) = "whether to use FM encoding rather than MFM"]; | ||||
| 		optional int32 idam_byte = 7        [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"]; | ||||
| 		optional int32 dam_byte = 8         [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"]; | ||||
| 		optional int32 gap0 = 9             [default=80, (help) = "size of gap 1 (the post-index gap)"]; | ||||
| 		optional int32 gap1 = 10            [default=50, (help) = "size of gap 2 (the post-ID gap)"]; | ||||
| 		optional int32 gap2 = 11            [default=22, (help) = "size of gap 3 (the pre-data gap)"]; | ||||
| 		optional int32 gap3 = 12            [default=80, (help) = "size of gap 4 (the post-data or format gap)"]; | ||||
| 		optional bool invert_side_byte = 19 [default=false, (help) = "invert the side byte before writing"]; | ||||
| 		optional int32 gap_fill_byte = 18   [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"]; | ||||
| 		optional double target_rotational_period_ms = 1 [default=200, (help) = "rotational period of target disk"]; | ||||
| 	} | ||||
|  | ||||
| 	repeated TrackdataProto trackdata = 1; | ||||
| } | ||||
|  | ||||
| @@ -2,8 +2,10 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "track.h" | ||||
| #include "macintosh.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| @@ -24,7 +26,7 @@ static int decode_data_gcr(uint8_t gcr) | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan Woods | ||||
|  * and R. Belmont: https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp | ||||
| @@ -116,76 +118,69 @@ static Bytes decode_crazy_data(const Bytes& input, Sector::Status& status) | ||||
| uint8_t decode_side(uint8_t side) | ||||
| { | ||||
|     /* Mac disks, being weird, use the side byte to encode both the side (in | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 0). | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 6). | ||||
|      */ | ||||
|  | ||||
|     return !!(side & 0x20); | ||||
|     return !!(side & 0x40); | ||||
| } | ||||
|  | ||||
| class MacintoshDecoder : public Decoder | ||||
| AbstractDecoder::RecordType MacintoshDecoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
| 	MacintoshDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != MAC_SECTOR_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|  | ||||
| 		auto header = toBytes(readRawBits(7*8)).slice(0, 7); | ||||
| 					 | ||||
| 		uint8_t encodedTrack = decode_data_gcr(header[0]); | ||||
| 		if (encodedTrack != (_sector->physicalTrack & 0x3f)) | ||||
| 			return; | ||||
| 					 | ||||
| 		uint8_t encodedSector = decode_data_gcr(header[1]); | ||||
| 		uint8_t encodedSide = decode_data_gcr(header[2]); | ||||
| 		uint8_t formatByte = decode_data_gcr(header[3]); | ||||
| 		uint8_t wantedsum = decode_data_gcr(header[4]); | ||||
|  | ||||
| 		if (encodedSector > 11) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
| 		_sector->logicalSide = decode_side(encodedSide); | ||||
| 		_sector->logicalSector = encodedSector; | ||||
| 		uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
| 		if (wantedsum == gotsum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		if (readRaw24() != MAC_DATA_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		/* Read data. */ | ||||
|  | ||||
| 		readRawBits(8); /* skip spare byte */ | ||||
| 		auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH*8)) | ||||
| 			.slice(0, MAC_ENCODED_SECTOR_LENGTH); | ||||
|  | ||||
| 		for (unsigned i=0; i<inputbuffer.size(); i++) | ||||
| 			inputbuffer[i] = decode_data_gcr(inputbuffer[i]); | ||||
| 			 | ||||
| 		_sector->status = Sector::BAD_CHECKSUM; | ||||
| 		Bytes userData = decode_crazy_data(inputbuffer, _sector->status); | ||||
| 		_sector->data.clear(); | ||||
| 		_sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MacintoshDecoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return DATA_RECORD; | ||||
| 	return UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void MacintoshDecoder::decodeSectorRecord() | ||||
| { | ||||
|     /* Skip ID (as we know it's a MAC_SECTOR_RECORD). */ | ||||
|     readRawBits(24); | ||||
|  | ||||
|     /* Read header. */ | ||||
|  | ||||
|     auto header = toBytes(readRawBits(7*8)).slice(0, 7); | ||||
|                  | ||||
|     uint8_t encodedTrack = decode_data_gcr(header[0]); | ||||
|     if (encodedTrack != (_track->physicalTrack & 0x3f)) | ||||
|         return; | ||||
|                  | ||||
|     uint8_t encodedSector = decode_data_gcr(header[1]); | ||||
|     uint8_t encodedSide = decode_data_gcr(header[2]); | ||||
|     uint8_t formatByte = decode_data_gcr(header[3]); | ||||
|     uint8_t wantedsum = decode_data_gcr(header[4]); | ||||
|  | ||||
|     if (encodedSector > 11) | ||||
|         return; | ||||
|  | ||||
|     _sector->logicalTrack = _track->physicalTrack; | ||||
|     _sector->logicalSide = decode_side(encodedSide); | ||||
|     _sector->logicalSector = encodedSector; | ||||
|     uint8_t gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
|     if (wantedsum == gotsum) | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| } | ||||
|  | ||||
| void MacintoshDecoder::decodeDataRecord() | ||||
| { | ||||
|     auto id = toBytes(readRawBits(24)).reader().read_be24(); | ||||
|     if (id != MAC_DATA_RECORD) | ||||
|         return; | ||||
|  | ||||
|     /* Read data. */ | ||||
|  | ||||
|     readRawBits(8); /* skip spare byte */ | ||||
|     auto inputbuffer = toBytes(readRawBits(MAC_ENCODED_SECTOR_LENGTH*8)) | ||||
|         .slice(0, MAC_ENCODED_SECTOR_LENGTH); | ||||
|  | ||||
|     for (unsigned i=0; i<inputbuffer.size(); i++) | ||||
|         inputbuffer[i] = decode_data_gcr(inputbuffer[i]); | ||||
|          | ||||
|     _sector->status = Sector::BAD_CHECKSUM; | ||||
|     Bytes userData = decode_crazy_data(inputbuffer, _sector->status); | ||||
|     _sector->data.clear(); | ||||
|     _sector->data.writer().append(userData.slice(12, 512)).append(userData.slice(0, 12)); | ||||
| } | ||||
|   | ||||
| @@ -1,260 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "macintosh.h" | ||||
| #include "crc.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include "arch/macintosh/macintosh.pb.h" | ||||
| #include <ctype.h> | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static double clockRateUsForTrack(unsigned track) | ||||
| { | ||||
|     if (track < 16) | ||||
|         return 2.63; | ||||
|     if (track < 32) | ||||
|         return 2.89; | ||||
|     if (track < 48) | ||||
|         return 3.20; | ||||
|     if (track < 64) | ||||
|         return 3.57; | ||||
|     return 3.98; | ||||
| } | ||||
|  | ||||
| static unsigned sectorsForTrack(unsigned track) | ||||
| { | ||||
|     if (track < 16) | ||||
|         return 12; | ||||
|     if (track < 32) | ||||
|         return 11; | ||||
|     if (track < 48) | ||||
|         return 10; | ||||
|     if (track < 64) | ||||
|         return 9; | ||||
|     return 8; | ||||
| } | ||||
|  | ||||
| static int encode_data_gcr(uint8_t gcr) | ||||
| { | ||||
|     switch (gcr) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| /* This is extremely inspired by the MESS implementation, written by Nathan | ||||
|  * Woods and R. Belmont: | ||||
|  * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp | ||||
|  */ | ||||
| static Bytes encode_crazy_data(const Bytes& input) | ||||
| { | ||||
|     Bytes output; | ||||
|     ByteWriter bw(output); | ||||
|     ByteReader br(input); | ||||
|  | ||||
|     uint8_t w1, w2, w3, w4; | ||||
|  | ||||
|     static const int LOOKUP_LEN = MAC_SECTOR_LENGTH / 3; | ||||
|  | ||||
|     uint8_t b1[LOOKUP_LEN + 1]; | ||||
|     uint8_t b2[LOOKUP_LEN + 1]; | ||||
|     uint8_t b3[LOOKUP_LEN + 1]; | ||||
|  | ||||
|     uint32_t c1 = 0; | ||||
|     uint32_t c2 = 0; | ||||
|     uint32_t c3 = 0; | ||||
|     for (int j = 0;; j++) | ||||
|     { | ||||
|         c1 = (c1 & 0xff) << 1; | ||||
|         if (c1 & 0x0100) | ||||
|             c1++; | ||||
|  | ||||
|         uint8_t val = br.read_8(); | ||||
|         c3 += val; | ||||
|         if (c1 & 0x0100) | ||||
|         { | ||||
|             c3++; | ||||
|             c1 &= 0xff; | ||||
|         } | ||||
|         b1[j] = (val ^ c1) & 0xff; | ||||
|  | ||||
|         val = br.read_8(); | ||||
|         c2 += val; | ||||
|         if (c3 > 0xff) | ||||
|         { | ||||
|             c2++; | ||||
|             c3 &= 0xff; | ||||
|         } | ||||
|         b2[j] = (val ^ c3) & 0xff; | ||||
|  | ||||
|         if (br.pos == 524) | ||||
|             break; | ||||
|  | ||||
|         val = br.read_8(); | ||||
|         c1 += val; | ||||
|         if (c2 > 0xff) | ||||
|         { | ||||
|             c1++; | ||||
|             c2 &= 0xff; | ||||
|         } | ||||
|         b3[j] = (val ^ c2) & 0xff; | ||||
|     } | ||||
|     uint32_t c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2); | ||||
|     b3[LOOKUP_LEN] = 0; | ||||
|  | ||||
|     for (int i = 0; i <= LOOKUP_LEN; i++) | ||||
|     { | ||||
|         w1 = b1[i] & 0x3f; | ||||
|         w2 = b2[i] & 0x3f; | ||||
|         w3 = b3[i] & 0x3f; | ||||
|         w4 = ((b1[i] & 0xc0) >> 2); | ||||
|         w4 |= ((b2[i] & 0xc0) >> 4); | ||||
|         w4 |= ((b3[i] & 0xc0) >> 6); | ||||
|  | ||||
|         bw.write_8(w4); | ||||
|         bw.write_8(w1); | ||||
|         bw.write_8(w2); | ||||
|  | ||||
|         if (i != LOOKUP_LEN) | ||||
|             bw.write_8(w3); | ||||
|     } | ||||
|  | ||||
|     bw.write_8(c4 & 0x3f); | ||||
|     bw.write_8(c3 & 0x3f); | ||||
|     bw.write_8(c2 & 0x3f); | ||||
|     bw.write_8(c1 & 0x3f); | ||||
|  | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src) | ||||
| { | ||||
|     for (bool bit : src) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             bits[cursor++] = bit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width) | ||||
| { | ||||
|     cursor += width; | ||||
|     for (int i = 0; i < width; i++) | ||||
|     { | ||||
|         unsigned pos = cursor - i - 1; | ||||
|         if (pos < bits.size()) | ||||
|             bits[pos] = data & 1; | ||||
|         data >>= 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static uint8_t encode_side(uint8_t track, uint8_t side) | ||||
| { | ||||
|     /* Mac disks, being weird, use the side byte to encode both the side (in | ||||
|      * bit 5) and also whether we're above track 0x3f (in bit 0). | ||||
|      */ | ||||
|  | ||||
|     return (side ? 0x20 : 0x00) | ((track > 0x3f) ? 0x01 : 0x00); | ||||
| } | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     if ((sector->data.size() != 512) && (sector->data.size() != 524)) | ||||
|         Error() << "unsupported sector size --- you must pick 512 or 524"; | ||||
|  | ||||
|     write_bits(bits, cursor, 0xff, 1 * 8); /* pad byte */ | ||||
|     for (int i = 0; i < 7; i++) | ||||
|         write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */ | ||||
|     write_bits(bits, cursor, MAC_SECTOR_RECORD, 3 * 8); | ||||
|  | ||||
|     uint8_t encodedTrack = sector->logicalTrack & 0x3f; | ||||
|     uint8_t encodedSector = sector->logicalSector; | ||||
|     uint8_t encodedSide = | ||||
|         encode_side(sector->logicalTrack, sector->logicalSide); | ||||
|     uint8_t formatByte = MAC_FORMAT_BYTE; | ||||
|     uint8_t headerChecksum = | ||||
|         (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; | ||||
|  | ||||
|     write_bits(bits, cursor, encode_data_gcr(encodedTrack), 1 * 8); | ||||
|     write_bits(bits, cursor, encode_data_gcr(encodedSector), 1 * 8); | ||||
|     write_bits(bits, cursor, encode_data_gcr(encodedSide), 1 * 8); | ||||
|     write_bits(bits, cursor, encode_data_gcr(formatByte), 1 * 8); | ||||
|     write_bits(bits, cursor, encode_data_gcr(headerChecksum), 1 * 8); | ||||
|  | ||||
|     write_bits(bits, cursor, 0xdeaaff, 3 * 8); | ||||
|     write_bits(bits, cursor, 0xff3fcff3fcffLL, 6 * 8); /* sync */ | ||||
|     write_bits(bits, cursor, MAC_DATA_RECORD, 3 * 8); | ||||
|     write_bits(bits, cursor, encode_data_gcr(sector->logicalSector), 1 * 8); | ||||
|  | ||||
|     Bytes wireData; | ||||
|     wireData.writer() | ||||
|         .append(sector->data.slice(512, 12)) | ||||
|         .append(sector->data.slice(0, 512)); | ||||
|     for (uint8_t b : encode_crazy_data(wireData)) | ||||
|         write_bits(bits, cursor, encode_data_gcr(b), 1 * 8); | ||||
|  | ||||
|     write_bits(bits, cursor, 0xdeaaff, 3 * 8); | ||||
| } | ||||
|  | ||||
| class MacintoshEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     MacintoshEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.macintosh()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         double clockRateUs = clockRateUsForTrack(trackInfo->logicalTrack); | ||||
|         int bitsPerRevolution = 200000.0 / clockRateUs; | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         fillBitmapTo(bits, | ||||
|             cursor, | ||||
|             _config.post_index_gap_us() / clockRateUs, | ||||
|             {true, false}); | ||||
|         lastBit = false; | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|             write_sector(bits, cursor, sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod(clockRateUs * 1e3, 200e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const MacintoshEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createMacintoshEncoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new MacintoshEncoder(config)); | ||||
| } | ||||
| @@ -6,17 +6,19 @@ | ||||
|  | ||||
| #define MAC_SECTOR_LENGTH   524 /* yes, really */ | ||||
| #define MAC_ENCODED_SECTOR_LENGTH 703 | ||||
| #define MAC_FORMAT_BYTE     0x22 | ||||
|  | ||||
| #define MAC_TRACKS_PER_DISK 80 | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class Encoder; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
| class MacintoshDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~MacintoshDecoder() {} | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createMacintoshDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMacintoshEncoder(const EncoderProto& config); | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message MacintoshDecoderProto {} | ||||
|  | ||||
| message MacintoshEncoderProto { | ||||
| 	optional double post_index_gap_us = 1 [default = 0.0, | ||||
| 		(help) = "post-index gap before first sector header (microseconds)."]; | ||||
| } | ||||
|  | ||||
| @@ -1,185 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "micropolis.h" | ||||
| #include "bytes.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
|  | ||||
| /* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. | ||||
|  * | ||||
|  * 00        00        00        F         F | ||||
|  * 0000 0000 0000 0000 0000 0000 0101 0101 0101 0101 | ||||
|  * A    A    A    A    A    A    5    5    5    5 | ||||
|  */ | ||||
| static const FluxPattern SECTOR_SYNC_PATTERN(64, 0xAAAAAAAAAAAA5555LL); | ||||
|  | ||||
| /* Pattern to skip past current SYNC. */ | ||||
| static const FluxPattern SECTOR_ADVANCE_PATTERN(64, 0xAAAAAAAAAAAAAAAALL); | ||||
|  | ||||
| /* Standard Micropolis checksum.  Adds all bytes, with carry. */ | ||||
| uint8_t micropolisChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint16_t sum = 0; | ||||
| 	while (!br.eof()) { | ||||
| 		if (sum > 0xFF) { | ||||
| 			sum -= 0x100 - 1; | ||||
| 		} | ||||
| 		sum += br.read_8(); | ||||
| 	} | ||||
| 	/* The last carry is ignored */ | ||||
| 	return sum & 0xFF; | ||||
| } | ||||
|  | ||||
| /* Vector MZOS does not use the standard Micropolis checksum. | ||||
|  * The checksum is initially 0. | ||||
|  * For each data byte in the 256-byte payload, rotate left, | ||||
|  * carrying bit 7 to bit 0.  XOR with the current checksum. | ||||
|  * | ||||
|  * Unlike the Micropolis checksum, this does not cover the 12-byte | ||||
|  * header (track, sector, 10 OS-specific bytes.) | ||||
|  */ | ||||
| uint8_t mzosChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint8_t checksum = 0; | ||||
| 	uint8_t databyte; | ||||
|  | ||||
| 	while (!br.eof()) { | ||||
| 		databyte = br.read_8(); | ||||
| 	checksum ^= ((databyte << 1) | (databyte >> 7)); | ||||
| 	} | ||||
|  | ||||
| 	return checksum; | ||||
| } | ||||
|  | ||||
| class MicropolisDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	MicropolisDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.micropolis()) | ||||
| 	{ | ||||
| 		_checksumType = _config.checksum_type(); | ||||
| 	} | ||||
|  | ||||
| 	nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		nanoseconds_t now = tell().ns(); | ||||
|  | ||||
| 		/* For all but the first sector, seek to the next sector pulse. | ||||
| 		 * The first sector does not contain the sector pulse in the fluxmap. | ||||
| 		 */ | ||||
| 		if (now != 0) { | ||||
| 			seekToIndexMark(); | ||||
| 			now = tell().ns(); | ||||
| 		} | ||||
|  | ||||
| 		/* Discard a possible partial sector at the end of the track. | ||||
| 		 * This partial sector could be mistaken for a conflicted sector, if | ||||
| 		 * whatever data read happens to match the checksum of 0, which is | ||||
| 		 * rare, but has been observed on some disks. | ||||
| 		 */ | ||||
| 		if (now > (getFluxmapDuration() - 12.5e6)) { | ||||
| 			seekToIndexMark(); | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		nanoseconds_t clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
|  | ||||
| 		auto syncDelta = tell().ns() - now; | ||||
| 		/* Due to the weak nature of the Micropolis SYNC patern, | ||||
| 		 * it's possible to detect a false SYNC during the gap | ||||
| 		 * between the sector pulse and the write gate.  If the SYNC | ||||
| 		 * is detected less than 100uS after the sector pulse, search | ||||
| 		 * for another valid SYNC. | ||||
| 		 * | ||||
| 		 * Reference: Vector Micropolis Disk Controller Board Technical | ||||
| 		 * Information Manual, pp. 1-16. | ||||
| 		 */ | ||||
| 		if ((syncDelta > 0) && (syncDelta < 100e3)) { | ||||
| 			seekToPattern(SECTOR_ADVANCE_PATTERN); | ||||
| 			clock = seekToPattern(SECTOR_SYNC_PATTERN); | ||||
| 		} | ||||
|  | ||||
| 		_sector->headerStartTime = tell().ns(); | ||||
|  | ||||
| 		/* seekToPattern() can skip past the index hole, if this happens | ||||
| 		 * too close to the end of the Fluxmap, discard the sector. | ||||
| 		 */ | ||||
| 		if (_sector->headerStartTime > (getFluxmapDuration() - 12.5e6)) { | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		return clock; | ||||
| 	} | ||||
|  | ||||
| 	void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		readRawBits(48); | ||||
| 		auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); | ||||
| 		ByteReader br(bytes); | ||||
|  | ||||
| 		int syncByte = br.read_8();  /* sync */ | ||||
| 		if (syncByte != 0xFF) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->logicalTrack = br.read_8(); | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = br.read_8(); | ||||
| 		if (_sector->logicalSector > 15) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 76) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack != _sector->physicalTrack) | ||||
| 			return; | ||||
|  | ||||
| 		br.read(10);  /* OS data or padding */ | ||||
| 		auto data = br.read(MICROPOLIS_PAYLOAD_SIZE); | ||||
| 		uint8_t wantChecksum = br.read_8(); | ||||
|  | ||||
| 		/* If not specified, automatically determine the checksum type. | ||||
| 		* Once the checksum type is determined, it will be used for the | ||||
| 		* entire disk. | ||||
| 		*/ | ||||
| 		if (_checksumType == MicropolisDecoderProto::AUTO) { | ||||
| 			/* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS checksums */ | ||||
| 			if (wantChecksum == micropolisChecksum(bytes.slice(1, 2+266))) { | ||||
| 				_checksumType = MicropolisDecoderProto::MICROPOLIS; | ||||
| 			} else if (wantChecksum == mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE))) { | ||||
| 				_checksumType = MicropolisDecoderProto::MZOS; | ||||
| 				std::cout << "Note: MZOS checksum detected." << std::endl; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		uint8_t gotChecksum; | ||||
|  | ||||
| 		if (_checksumType == MicropolisDecoderProto::MZOS) { | ||||
| 			gotChecksum = mzosChecksum(bytes.slice(MICROPOLIS_HEADER_SIZE, MICROPOLIS_PAYLOAD_SIZE)); | ||||
| 		} else { | ||||
| 			gotChecksum = micropolisChecksum(bytes.slice(1, 2+266)); | ||||
| 		} | ||||
|  | ||||
| 		br.read(5);  /* 4 byte ECC and ECC-present flag */ | ||||
|  | ||||
| 		if (_config.sector_output_size() == MICROPOLIS_PAYLOAD_SIZE) | ||||
| 			_sector->data = data; | ||||
| 		else if (_config.sector_output_size() == MICROPOLIS_ENCODED_SECTOR_SIZE) | ||||
| 			_sector->data = bytes; | ||||
| 		else | ||||
| 			Error() << "Sector output size may only be 256 or 275"; | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	const MicropolisDecoderProto& _config; | ||||
| 	MicropolisDecoderProto_ChecksumType _checksumType;	/* -1 = auto, 1 = Micropolis, 2=MZOS */ | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MicropolisDecoder(config)); | ||||
| } | ||||
|  | ||||
| @@ -1,112 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "micropolis.h" | ||||
| #include "sector.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "image.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     if ((sector->data.size() != 256) && | ||||
|         (sector->data.size() != MICROPOLIS_ENCODED_SECTOR_SIZE)) | ||||
|         Error() << "unsupported sector size --- you must pick 256 or 275"; | ||||
|  | ||||
|     int fullSectorSize = 40 + MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35; | ||||
|     auto fullSector = std::make_shared<std::vector<uint8_t>>(); | ||||
|     fullSector->reserve(fullSectorSize); | ||||
|     /* sector preamble */ | ||||
|     for (int i = 0; i < 40; i++) | ||||
|         fullSector->push_back(0); | ||||
|     Bytes sectorData; | ||||
|     if (sector->data.size() == MICROPOLIS_ENCODED_SECTOR_SIZE) | ||||
|     { | ||||
|         if (sector->data[0] != 0xFF) | ||||
|             Error() << "275 byte sector doesn't start with sync byte 0xFF. " | ||||
|                        "Corrupted sector"; | ||||
|         uint8_t wantChecksum = sector->data[1 + 2 + 266]; | ||||
|         uint8_t gotChecksum = | ||||
|             micropolisChecksum(sector->data.slice(1, 2 + 266)); | ||||
|         if (wantChecksum != gotChecksum) | ||||
|             std::cerr << "Warning: checksum incorrect. Sector: " | ||||
|                       << sector->logicalSector << std::endl; | ||||
|         sectorData = sector->data; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         ByteWriter writer(sectorData); | ||||
|         writer.write_8(0xff); /* Sync */ | ||||
|         writer.write_8(sector->logicalTrack); | ||||
|         writer.write_8(sector->logicalSector); | ||||
|         for (int i = 0; i < 10; i++) | ||||
|             writer.write_8(0); /* Padding */ | ||||
|         writer += sector->data; | ||||
|         writer.write_8(micropolisChecksum(sectorData.slice(1))); | ||||
|         for (int i = 0; i < 5; i++) | ||||
|             writer.write_8(0); /* 4 byte ECC and ECC not present flag */ | ||||
|     } | ||||
|     for (uint8_t b : sectorData) | ||||
|         fullSector->push_back(b); | ||||
|     /* sector postamble */ | ||||
|     for (int i = 0; i < 40; i++) | ||||
|         fullSector->push_back(0); | ||||
|     /* filler */ | ||||
|     for (int i = 0; i < 35; i++) | ||||
|         fullSector->push_back(0); | ||||
|  | ||||
|     if (fullSector->size() != fullSectorSize) | ||||
|         Error() << "sector mismatched length"; | ||||
|     bool lastBit = false; | ||||
|     encodeMfm(bits, cursor, fullSector, lastBit); | ||||
|     /* filler */ | ||||
|     for (int i = 0; i < 5; i++) | ||||
|     { | ||||
|         bits[cursor++] = 1; | ||||
|         bits[cursor++] = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| class MicropolisEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     MicropolisEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.micropolis()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         int bitsPerRevolution = | ||||
|             (_config.rotational_period_ms() * 1e3) / _config.clock_period_us(); | ||||
|  | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         for (const auto& sectorData : sectors) | ||||
|             write_sector(bits, cursor, sectorData); | ||||
|  | ||||
|         if (cursor != bits.size()) | ||||
|             Error() << "track data mismatched length"; | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 _config.clock_period_us() * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const MicropolisEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createMicropolisEncoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new MicropolisEncoder(config)); | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| #ifndef MICROPOLIS_H | ||||
| #define MICROPOLIS_H | ||||
|  | ||||
| #define MICROPOLIS_PAYLOAD_SIZE         (256) | ||||
| #define MICROPOLIS_HEADER_SIZE          (1+2+10) | ||||
| #define MICROPOLIS_ENCODED_SECTOR_SIZE  (MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6) | ||||
|  | ||||
| class Decoder; | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createMicropolisDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createMicropolisEncoder(const EncoderProto& config); | ||||
|  | ||||
| extern uint8_t micropolisChecksum(const Bytes& bytes); | ||||
|  | ||||
| #endif | ||||
| @@ -1,24 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message MicropolisDecoderProto { | ||||
| 	enum ChecksumType { | ||||
| 		AUTO = 0; | ||||
| 		MICROPOLIS = 1; | ||||
| 		MZOS = 2; | ||||
| 	} | ||||
|  | ||||
| 	optional int32 sector_output_size = 1 [default = 256, | ||||
| 		(help) = "How much of the raw sector should be saved. Must be 256 or 275"]; | ||||
| 	optional ChecksumType checksum_type = 2 [default = AUTO, | ||||
| 		(help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"]; | ||||
| } | ||||
|  | ||||
| message MicropolisEncoderProto { | ||||
|     optional double clock_period_us = 1 | ||||
|         [ default = 2.0, (help) = "clock rate on the real device" ]; | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 200.0, (help) = "rotational period on the real device" ]; | ||||
| } | ||||
|  | ||||
| @@ -5,6 +5,8 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include "record.h" | ||||
| #include "track.h" | ||||
| #include <string.h> | ||||
|  | ||||
| const int SECTOR_SIZE = 256; | ||||
| @@ -16,71 +18,58 @@ const int SECTOR_SIZE = 256; | ||||
|  */ | ||||
|  | ||||
| /* FM beginning of track marker: | ||||
|  *         0         0         f         3 decoded nibbles | ||||
|  *  0 0  0 0  0 0  0 0  1 1  1 1  0 0  1 1 | ||||
|  * 1010 1010 1010 1010 1111 1111 1010 1111 | ||||
|  *    a    a    a    a    f    f    a    f encoded nibbles | ||||
|  *    a    a    a    a    f    f    a    f | ||||
|  */ | ||||
| const FluxPattern ID_PATTERN(32, 0xaaaaffaf); | ||||
|  | ||||
| class MxDecoder : public Decoder | ||||
| void MxDecoder::beginTrack() | ||||
| { | ||||
| public: | ||||
| 	MxDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     void beginTrack() override | ||||
| 	{ | ||||
| 		_clock = _sector->clock = seekToPattern(ID_PATTERN); | ||||
| 		_currentSector = 0; | ||||
| 	} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		if (_currentSector == 11) | ||||
| 		{ | ||||
| 			/* That was the last sector on the disk. */ | ||||
| 			return 0; | ||||
| 		} | ||||
| 		else | ||||
| 			return _clock; | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Skip the ID pattern and track word, which is only present on the | ||||
| 		 * first sector. We don't trust the track word because some driver | ||||
| 		 * don't write it correctly. */ | ||||
|  | ||||
| 		if (_currentSector == 0) | ||||
| 			readRawBits(64); | ||||
|  | ||||
| 		auto bits = readRawBits((SECTOR_SIZE+2)*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2); | ||||
|  | ||||
| 		uint16_t gotChecksum = 0; | ||||
| 		ByteReader br(bytes); | ||||
| 		for (int i=0; i<(SECTOR_SIZE/2); i++) | ||||
| 			gotChecksum += br.read_be16(); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
|  | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = _currentSector; | ||||
| 		_sector->data = bytes.slice(0, SECTOR_SIZE).swab(); | ||||
| 		_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 		_currentSector++; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _clock; | ||||
|     int _currentSector; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new MxDecoder(config)); | ||||
|     _currentSector = -1; | ||||
|     _clock = 0; | ||||
| } | ||||
|  | ||||
| AbstractDecoder::RecordType MxDecoder::advanceToNextRecord() | ||||
| { | ||||
|     if (_currentSector == -1) | ||||
|     { | ||||
|         /* First sector in the track: look for the sync marker. */ | ||||
|         const FluxMatcher* matcher = nullptr; | ||||
|         _sector->clock = _clock = _fmr->seekToPattern(ID_PATTERN, matcher); | ||||
|         readRawBits(32); /* skip the ID mark */ | ||||
|         _logicalTrack = decodeFmMfm(readRawBits(32)).reader().read_be16(); | ||||
|     } | ||||
|     else if (_currentSector == 10) | ||||
|     { | ||||
|         /* That was the last sector on the disk. */ | ||||
|         return UNKNOWN_RECORD; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         /* Otherwise we assume the clock from the first sector is still valid. | ||||
|          * The decoder framwork will automatically stop when we hit the end of | ||||
|          * the track. */ | ||||
|         _sector->clock = _clock; | ||||
|     } | ||||
|  | ||||
|     _currentSector++; | ||||
|     return SECTOR_RECORD; | ||||
| } | ||||
|  | ||||
| void MxDecoder::decodeSectorRecord() | ||||
| { | ||||
|     auto bits = readRawBits((SECTOR_SIZE+2)*16); | ||||
|     auto bytes = decodeFmMfm(bits).slice(0, SECTOR_SIZE+2).swab(); | ||||
|  | ||||
|     uint16_t gotChecksum = 0; | ||||
|     ByteReader br(bytes); | ||||
|     for (int i=0; i<(SECTOR_SIZE/2); i++) | ||||
|         gotChecksum += br.read_le16(); | ||||
|     uint16_t wantChecksum = br.read_le16(); | ||||
|  | ||||
|     _sector->logicalTrack = _logicalTrack; | ||||
|     _sector->logicalSide = _track->physicalSide; | ||||
|     _sector->logicalSector = _currentSector; | ||||
|     _sector->data = bytes.slice(0, SECTOR_SIZE); | ||||
|     _sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
							
								
								
									
										15
									
								
								arch/mx/mx.h
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								arch/mx/mx.h
									
									
									
									
									
								
							| @@ -3,6 +3,19 @@ | ||||
|  | ||||
| #include "decoders/decoders.h" | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createMxDecoder(const DecoderProto& config); | ||||
| class MxDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~MxDecoder() {} | ||||
|  | ||||
|     void beginTrack(); | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|  | ||||
| private: | ||||
|     nanoseconds_t _clock; | ||||
|     int _currentSector; | ||||
|     int _logicalTrack; | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message MxDecoderProto {} | ||||
|  | ||||
| @@ -1,176 +0,0 @@ | ||||
| /* Decoder for North Star 10-sector hard-sectored disks. | ||||
|  * | ||||
|  * Supports both single- and double-density.  For the sector format and | ||||
|  * checksum algorithm, see pp. 33 of the North Star Double Density Controller | ||||
|  * manual: | ||||
|  * | ||||
|  * http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf | ||||
|  * | ||||
|  * North Star disks do not contain any track/head/sector information | ||||
|  * encoded in the sector record.  For this reason, we have to be absolutely | ||||
|  * sure that the hardSectorId is correct. | ||||
|  */ | ||||
|  | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "northstar.h" | ||||
| #include "bytes.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include "fmt/format.h" | ||||
|  | ||||
| #define MFM_ID 0xaaaaaaaaaaaa5545LL | ||||
| #define FM_ID  0xaaaaaaaaaaaaffefLL | ||||
| /* | ||||
|  * MFM sectors have 32 bytes of 00's followed by two sync characters, | ||||
|  * specified in the North Star MDS manual as 0xFBFB. | ||||
|  * | ||||
|  * This is true for most disks; however, I found a few disks, including an | ||||
|  * original North Star DOS/BASIC v2.2.1 DQ disk) that uses 0xFBnn, where | ||||
|  * nn is an incrementing pattern. | ||||
|  * | ||||
|  * 00        00        00        F         B | ||||
|  * 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101 | ||||
|  * A    A    A    A    A    A    5    5    4    5 | ||||
|  */ | ||||
| static const FluxPattern MFM_PATTERN(64, MFM_ID); | ||||
|  | ||||
| /* FM sectors have 16 bytes of 00's followed by 0xFB. | ||||
|  * 00        FB | ||||
|  * 0000 0000 1111 1111 1110 1111 | ||||
|  * A    A    F    F    E    F | ||||
|  */ | ||||
| static const FluxPattern FM_PATTERN(64, FM_ID); | ||||
|  | ||||
| const FluxMatchers ANY_SECTOR_PATTERN( | ||||
| 	{ | ||||
| 		&MFM_PATTERN, | ||||
| 		&FM_PATTERN, | ||||
| 	} | ||||
| ); | ||||
|  | ||||
| /* Checksum is initially 0. | ||||
|  * For each data byte, XOR with the current checksum. | ||||
|  * Rotate checksum left, carrying bit 7 to bit 0. | ||||
|  */ | ||||
| uint8_t northstarChecksum(const Bytes& bytes) { | ||||
| 	ByteReader br(bytes); | ||||
| 	uint8_t checksum = 0; | ||||
|  | ||||
| 	while (!br.eof()) { | ||||
| 		checksum ^= br.read_8(); | ||||
| 		checksum = ((checksum << 1) | ((checksum >> 7))); | ||||
| 	} | ||||
|  | ||||
| 	return checksum; | ||||
| } | ||||
|  | ||||
| class NorthstarDecoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	NorthstarDecoder(const DecoderProto& config): | ||||
| 		Decoder(config), | ||||
| 		_config(config.northstar()) | ||||
| 	{} | ||||
|  | ||||
| 	/* Search for FM or MFM sector record */ | ||||
| 	nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		nanoseconds_t now = tell().ns(); | ||||
|  | ||||
| 		/* For all but the first sector, seek to the next sector pulse. | ||||
| 		 * The first sector does not contain the sector pulse in the fluxmap. | ||||
| 		 */ | ||||
| 		if (now != 0) { | ||||
| 			seekToIndexMark(); | ||||
| 			now = tell().ns(); | ||||
| 		} | ||||
|  | ||||
| 		/* Discard a possible partial sector at the end of the track. | ||||
| 		 * This partial sector could be mistaken for a conflicted sector, if | ||||
| 		 * whatever data read happens to match the checksum of 0, which is | ||||
| 		 * rare, but has been observed on some disks. | ||||
| 		 */ | ||||
| 		if (now > (getFluxmapDuration() - 21e6)) { | ||||
| 			seekToIndexMark(); | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		int msSinceIndex = std::round(now / 1e6); | ||||
|  | ||||
| 		/* Note that the seekToPattern ignores the sector pulses, so if | ||||
| 		 * a sector is not found for some reason, the seek will advance | ||||
| 		 * past one or more sector pulses.  For this reason, calculate | ||||
| 		 * _hardSectorId after the sector header is found. | ||||
| 		 */ | ||||
| 		nanoseconds_t clock = seekToPattern(ANY_SECTOR_PATTERN); | ||||
| 		_sector->headerStartTime = tell().ns(); | ||||
|  | ||||
| 		/* Discard a possible partial sector. */ | ||||
| 		if (_sector->headerStartTime > (getFluxmapDuration() - 21e6)) { | ||||
| 			return 0; | ||||
| 		} | ||||
|  | ||||
| 		int sectorFoundTimeRaw = std::round(_sector->headerStartTime / 1e6); | ||||
| 		int sectorFoundTime; | ||||
|  | ||||
| 		/* Round time to the nearest 20ms */ | ||||
| 		if ((sectorFoundTimeRaw % 20) < 10) { | ||||
| 			sectorFoundTime = (sectorFoundTimeRaw / 20) * 20; | ||||
| 		} | ||||
| 		else { | ||||
| 			sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20; | ||||
| 		} | ||||
|  | ||||
| 		/* Calculate the sector ID based on time since the index */ | ||||
| 		_hardSectorId = (sectorFoundTime / 20) % 10; | ||||
|  | ||||
| 		return clock; | ||||
| 	} | ||||
|  | ||||
| 	void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		uint64_t id = toBytes(readRawBits(64)).reader().read_be64(); | ||||
| 		unsigned recordSize, payloadSize, headerSize; | ||||
|  | ||||
| 		if (id == MFM_ID) { | ||||
| 			recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_DD; | ||||
| 			payloadSize = NORTHSTAR_PAYLOAD_SIZE_DD; | ||||
| 			headerSize = NORTHSTAR_HEADER_SIZE_DD; | ||||
| 		} | ||||
| 		else { | ||||
| 			recordSize = NORTHSTAR_ENCODED_SECTOR_SIZE_SD; | ||||
| 			payloadSize = NORTHSTAR_PAYLOAD_SIZE_SD; | ||||
| 			headerSize = NORTHSTAR_HEADER_SIZE_SD; | ||||
| 		} | ||||
|  | ||||
| 		auto rawbits = readRawBits(recordSize * 16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, recordSize); | ||||
| 		ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->logicalSide = _sector->physicalSide; | ||||
| 		_sector->logicalSector = _hardSectorId; | ||||
| 		_sector->logicalTrack = _sector->physicalTrack; | ||||
|  | ||||
| 		if (headerSize == NORTHSTAR_HEADER_SIZE_DD) { | ||||
| 			br.read_8();	/* MFM second Sync char, usually 0xFB */ | ||||
| 		} | ||||
|  | ||||
| 		_sector->data = br.read(payloadSize); | ||||
| 		uint8_t wantChecksum = br.read_8(); | ||||
| 		uint8_t gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	const NorthstarDecoderProto& _config; | ||||
| 	uint8_t _hardSectorId; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new NorthstarDecoder(config)); | ||||
| } | ||||
|  | ||||
| @@ -1,168 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "northstar.h" | ||||
| #include "sector.h" | ||||
| #include "bytes.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "image.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
|  | ||||
| #define GAP_FILL_SIZE_SD 30 | ||||
| #define PRE_HEADER_GAP_FILL_SIZE_SD 9 | ||||
| #define GAP_FILL_SIZE_DD 62 | ||||
| #define PRE_HEADER_GAP_FILL_SIZE_DD 16 | ||||
|  | ||||
| #define GAP1_FILL_BYTE (0x4F) | ||||
| #define GAP2_FILL_BYTE (0x4F) | ||||
|  | ||||
| #define TOTAL_SECTOR_BYTES () | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const std::shared_ptr<const Sector>& sector) | ||||
| { | ||||
|     int preambleSize = 0; | ||||
|     int encodedSectorSize = 0; | ||||
|     int gapFillSize = 0; | ||||
|     int preHeaderGapFillSize = 0; | ||||
|  | ||||
|     bool doubleDensity; | ||||
|  | ||||
|     switch (sector->data.size()) | ||||
|     { | ||||
|         case NORTHSTAR_PAYLOAD_SIZE_SD: | ||||
|             preambleSize = NORTHSTAR_PREAMBLE_SIZE_SD; | ||||
|             encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + | ||||
|                                 NORTHSTAR_ENCODED_SECTOR_SIZE_SD + | ||||
|                                 GAP_FILL_SIZE_SD; | ||||
|             gapFillSize = GAP_FILL_SIZE_SD; | ||||
|             preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD; | ||||
|             doubleDensity = false; | ||||
|             break; | ||||
|         case NORTHSTAR_PAYLOAD_SIZE_DD: | ||||
|             preambleSize = NORTHSTAR_PREAMBLE_SIZE_DD; | ||||
|             encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + | ||||
|                                 NORTHSTAR_ENCODED_SECTOR_SIZE_DD + | ||||
|                                 GAP_FILL_SIZE_DD; | ||||
|             gapFillSize = GAP_FILL_SIZE_DD; | ||||
|             preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD; | ||||
|             doubleDensity = true; | ||||
|             break; | ||||
|         default: | ||||
|             Error() << "unsupported sector size --- you must pick 256 or 512"; | ||||
|             break; | ||||
|     } | ||||
|  | ||||
|     int fullSectorSize = preambleSize + encodedSectorSize; | ||||
|     auto fullSector = std::make_shared<std::vector<uint8_t>>(); | ||||
|     fullSector->reserve(fullSectorSize); | ||||
|  | ||||
|     /* sector gap after index pulse */ | ||||
|     for (int i = 0; i < preHeaderGapFillSize; i++) | ||||
|         fullSector->push_back(GAP1_FILL_BYTE); | ||||
|  | ||||
|     /* sector preamble */ | ||||
|     for (int i = 0; i < preambleSize; i++) | ||||
|         fullSector->push_back(0); | ||||
|  | ||||
|     Bytes sectorData; | ||||
|     if (sector->data.size() == encodedSectorSize) | ||||
|         sectorData = sector->data; | ||||
|     else | ||||
|     { | ||||
|         ByteWriter writer(sectorData); | ||||
|         writer.write_8(0xFB); /* sync character */ | ||||
|         if (doubleDensity == true) | ||||
|         { | ||||
|             writer.write_8(0xFB); /* Double-density has two sync characters */ | ||||
|         } | ||||
|         writer += sector->data; | ||||
|         if (doubleDensity == true) | ||||
|         { | ||||
|             writer.write_8(northstarChecksum(sectorData.slice(2))); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             writer.write_8(northstarChecksum(sectorData.slice(1))); | ||||
|         } | ||||
|     } | ||||
|     for (uint8_t b : sectorData) | ||||
|         fullSector->push_back(b); | ||||
|  | ||||
|     if (sector->logicalSector != 9) | ||||
|     { | ||||
|         /* sector postamble */ | ||||
|         for (int i = 0; i < gapFillSize; i++) | ||||
|             fullSector->push_back(GAP2_FILL_BYTE); | ||||
|  | ||||
|         if (fullSector->size() != fullSectorSize) | ||||
|             Error() << "sector mismatched length (" << sector->data.size() | ||||
|                     << ") expected: " << fullSector->size() << " got " | ||||
|                     << fullSectorSize; | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         /* sector postamble */ | ||||
|         for (int i = 0; i < gapFillSize; i++) | ||||
|             fullSector->push_back(GAP2_FILL_BYTE); | ||||
|     } | ||||
|  | ||||
|     bool lastBit = false; | ||||
|  | ||||
|     if (doubleDensity == true) | ||||
|     { | ||||
|         encodeMfm(bits, cursor, fullSector, lastBit); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         encodeFm(bits, cursor, fullSector); | ||||
|     } | ||||
| } | ||||
|  | ||||
| class NorthstarEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     NorthstarEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.northstar()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         int bitsPerRevolution = 100000; | ||||
|         double clockRateUs = _config.clock_period_us(); | ||||
|  | ||||
|         const auto& sector = *sectors.begin(); | ||||
|         if (sector->data.size() == NORTHSTAR_PAYLOAD_SIZE_SD) | ||||
|             bitsPerRevolution /= 2; // FM | ||||
|         else | ||||
|             clockRateUs /= 2.00; | ||||
|  | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         for (const auto& sectorData : sectors) | ||||
|             write_sector(bits, cursor, sectorData); | ||||
|  | ||||
|         if (cursor > bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, | ||||
|             calculatePhysicalClockPeriod( | ||||
|                 clockRateUs * 1e3, _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const NorthstarEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createNorthstarEncoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new NorthstarEncoder(config)); | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| #ifndef NORTHSTAR_H | ||||
| #define NORTHSTAR_H | ||||
|  | ||||
| /* Northstar floppies are 10-hard sectored disks with a sector format as follows: | ||||
|  * | ||||
|  * |----------------------------------| | ||||
|  * | SYNC Byte  | Payload  | Checksum | | ||||
|  * |------------+----------+----------| | ||||
|  * | 1 (0xFB)   | 256 (SD) |    1     | | ||||
|  * | 2 (0xFBFB) | 512 (DD) |          | | ||||
|  * |----------------------------------| | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_SD		(16) | ||||
| #define NORTHSTAR_PREAMBLE_SIZE_DD		(32) | ||||
| #define NORTHSTAR_HEADER_SIZE_SD		(1) | ||||
| #define NORTHSTAR_HEADER_SIZE_DD		(2) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_SD		(256) | ||||
| #define NORTHSTAR_PAYLOAD_SIZE_DD		(512) | ||||
| #define NORTHSTAR_CHECKSUM_SIZE		(1) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_SD	(NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE) | ||||
| #define NORTHSTAR_ENCODED_SECTOR_SIZE_DD	(NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE) | ||||
|  | ||||
| class Decoder; | ||||
| class Encoder; | ||||
| class EncoderProto; | ||||
| class DecoderProto; | ||||
|  | ||||
| extern uint8_t northstarChecksum(const Bytes& bytes); | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createNorthstarDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createNorthstarEncoder(const EncoderProto& config); | ||||
|  | ||||
| #endif /* NORTHSTAR */ | ||||
| @@ -1,13 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message NorthstarDecoderProto {} | ||||
|  | ||||
| message NorthstarEncoderProto { | ||||
|     optional double clock_period_us = 1 | ||||
|         [ default = 4.0, (help) = "clock rate on the real device (for FM)" ]; | ||||
|     optional double rotational_period_ms = 2 | ||||
|         [ default = 166.0, (help) = "rotational period on the real device" ]; | ||||
| } | ||||
|  | ||||
| @@ -1,56 +0,0 @@ | ||||
| #include "lib/globals.h" | ||||
| #include "lib/decoders/decoders.h" | ||||
| #include "lib/crc.h" | ||||
| #include "lib/fluxmap.h" | ||||
| #include "lib/decoders/fluxmapreader.h" | ||||
| #include "lib/sector.h" | ||||
| #include "lib/bytes.h" | ||||
| #include "rolandd20.h" | ||||
| #include <string.h> | ||||
|  | ||||
| /* Sector header record: | ||||
|  * | ||||
|  * BF FF FF FF FF FF FE AB | ||||
|  * | ||||
|  * This encodes to: | ||||
|  * | ||||
|  *    e    d    5    5    5    5    5    5 | ||||
|  * 1110 1101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    5    5    5    5    5 | ||||
|  * 0101 0101 0101 0101 0101 0101 0101 0101 | ||||
|  *    5    5    5    4    4    4    4    5 | ||||
|  * 0101 0101 0101 0100 0100 0100 0100 0101 | ||||
|  */ | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(64, 0xed55555555555555LL); | ||||
|  | ||||
| class RolandD20Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	RolandD20Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(SECTOR_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto rawbits = readRawBits(256); | ||||
| 		const auto& bytes = decodeFmMfm(rawbits); | ||||
| 		fmt::print("{} ", _sector->clock); | ||||
| 		hexdump(std::cout, bytes); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new RolandD20Decoder(config)); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -1,4 +0,0 @@ | ||||
| #pragma once | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createRolandD20Decoder(const DecoderProto& config); | ||||
|  | ||||
| @@ -1,5 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message RolandD20DecoderProto {} | ||||
|  | ||||
|  | ||||
| @@ -1,154 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "smaky6.h" | ||||
| #include "bytes.h" | ||||
| #include "crc.h" | ||||
| #include "fmt/format.h" | ||||
| #include "lib/decoders/decoders.pb.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
|  | ||||
| static const FluxPattern SECTOR_PATTERN(32, 0x54892aaa); | ||||
|  | ||||
| class Smaky6Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
|     Smaky6Decoder(const DecoderProto& config): | ||||
|         Decoder(config), | ||||
|         _config(config.smaky6()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     /* Returns the sector ID of the _current_ sector. */ | ||||
|     int advanceToNextSector() | ||||
|     { | ||||
|         auto previous = tell(); | ||||
|         seekToIndexMark(); | ||||
|         auto now = tell(); | ||||
|         if ((now.ns() - previous.ns()) < 9e6) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             auto next = tell(); | ||||
|             if ((next.ns() - now.ns()) < 9e6) | ||||
|             { | ||||
|                 /* We just found sector 0. */ | ||||
|  | ||||
|                 _sectorId = 0; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 /* Spurious... */ | ||||
|  | ||||
|                 seek(now); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return _sectorId++; | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     void beginTrack() override | ||||
|     { | ||||
|         /* Find the start-of-track index marks, which will be an interval | ||||
|          * of about 6ms. */ | ||||
|  | ||||
|         seekToIndexMark(); | ||||
|         _sectorId = 99; | ||||
|         for (;;) | ||||
|         { | ||||
|             auto pos = tell(); | ||||
|             advanceToNextSector(); | ||||
|             if (_sectorId < 99) | ||||
|             { | ||||
|                 seek(pos); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (eof()) | ||||
|                 return; | ||||
|         } | ||||
|  | ||||
|         /* Now we know where to start counting, start finding sectors. */ | ||||
|  | ||||
|         _sectorStarts.clear(); | ||||
|         for (;;) | ||||
|         { | ||||
|             auto now = tell(); | ||||
|             if (eof()) | ||||
|                 break; | ||||
|  | ||||
|             int id = advanceToNextSector(); | ||||
|             if (id < 16) | ||||
|                 _sectorStarts.push_back(std::make_pair(id, now)); | ||||
|         } | ||||
|  | ||||
|         _sectorIndex = 0; | ||||
|     } | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
|     { | ||||
|         if (_sectorIndex == _sectorStarts.size()) | ||||
|         { | ||||
|             seekToIndexMark(); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         const auto& p = _sectorStarts[_sectorIndex++]; | ||||
|         _sectorId = p.first; | ||||
|         seek(p.second); | ||||
|  | ||||
|         nanoseconds_t clock = seekToPattern(SECTOR_PATTERN); | ||||
|         _sector->headerStartTime = tell().ns(); | ||||
|  | ||||
|         return clock; | ||||
|     } | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
|     { | ||||
|         readRawBits(33); | ||||
|         const auto& rawbits = readRawBits(SMAKY6_RECORD_SIZE * 16); | ||||
|         if (rawbits.size() < SMAKY6_SECTOR_SIZE) | ||||
|             return; | ||||
|         const auto& rawbytes = | ||||
|             toBytes(rawbits).slice(0, SMAKY6_RECORD_SIZE * 16); | ||||
|  | ||||
|         /* The Smaky bytes are stored backwards! Backwards! */ | ||||
|  | ||||
|         const auto& bytes = | ||||
|             decodeFmMfm(rawbits).slice(0, SMAKY6_RECORD_SIZE).reverseBits(); | ||||
|         ByteReader br(bytes); | ||||
|  | ||||
|         uint8_t track = br.read_8(); | ||||
|         Bytes data = br.read(SMAKY6_SECTOR_SIZE); | ||||
|         uint8_t wantedChecksum = br.read_8(); | ||||
|         uint8_t gotChecksum = sumBytes(data) & 0xff; | ||||
|  | ||||
|         if (track != _sector->physicalTrack) | ||||
|             return; | ||||
|  | ||||
|         _sector->logicalTrack = _sector->physicalTrack; | ||||
|         _sector->logicalSide = _sector->physicalSide; | ||||
|         _sector->logicalSector = _sectorId; | ||||
|  | ||||
|         _sector->data = data; | ||||
|         _sector->status = | ||||
|             (wantedChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Smaky6DecoderProto& _config; | ||||
|     nanoseconds_t _startOfTrack; | ||||
|     std::vector<std::pair<int, Fluxmap::Position>> _sectorStarts; | ||||
|     int _sectorId; | ||||
|     int _sectorIndex; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Decoder>(new Smaky6Decoder(config)); | ||||
| } | ||||
| @@ -1,10 +0,0 @@ | ||||
| #ifndef SMAKY6_H | ||||
| #define SMAKY6_H | ||||
|  | ||||
| #define SMAKY6_SECTOR_SIZE 256 | ||||
| #define SMAKY6_RECORD_SIZE (1 + SMAKY6_SECTOR_SIZE + 1) | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createSmaky6Decoder(const DecoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| @@ -1,6 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Smaky6DecoderProto {} | ||||
|  | ||||
| @@ -1,98 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "tids990/tids990.h" | ||||
| #include "crc.h" | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "sector.h" | ||||
| #include <string.h> | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| /* The Texas Instruments DS990 uses MFM with a scheme similar to a simplified | ||||
|  * version of the IBM record scheme (it's actually easier to parse than IBM). | ||||
|  * There are 26 sectors per track, each holding a rather weird 288 bytes. | ||||
|  */ | ||||
|  | ||||
| /* | ||||
|  * Sector record: | ||||
|  * data:    0  1  0  1  0  1  0  1 .0  0  0  0  1  0  1  0  = 0x550a | ||||
|  * mfm:     00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 00 = 0x11112a44 | ||||
|  * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 00 = 0x11112244 | ||||
|  *                                        ^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  */ | ||||
| const uint16_t SECTOR_ID = 0x550a; | ||||
| const FluxPattern SECTOR_RECORD_PATTERN(32, 0x11112244); | ||||
|  | ||||
| /* | ||||
|  * Data record: | ||||
|  * data:    0  1  0  1  0  1  0  1 .0  0  0  0  1  0  1  1  = 0x550b | ||||
|  * mfm:     00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45 | ||||
|  * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245 | ||||
|  *                                        ^^ | ||||
|  * When shifted out of phase, the special 0xa1 byte becomes an illegal | ||||
|  * encoding (you can't do 10 00). So this can't be spoofed by user data. | ||||
|  */ | ||||
| const uint16_t DATA_ID = 0x550b; | ||||
| const FluxPattern DATA_RECORD_PATTERN(32, 0x11112245); | ||||
|  | ||||
| const FluxMatchers ANY_RECORD_PATTERN({ &SECTOR_RECORD_PATTERN, &DATA_RECORD_PATTERN }); | ||||
|  | ||||
| class Tids990Decoder : public Decoder | ||||
| { | ||||
| public: | ||||
| 	Tids990Decoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		auto bits = readRawBits(TIDS990_SECTOR_RECORD_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, TIDS990_SECTOR_RECORD_SIZE); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		if (br.read_be16() != SECTOR_ID) | ||||
| 			return; | ||||
|  | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_SECTOR_RECORD_SIZE-3)); | ||||
|  | ||||
| 		_sector->logicalSide = br.read_8() >> 3; | ||||
| 		_sector->logicalTrack = br.read_8(); | ||||
| 		br.read_8(); /* number of sectors per track */ | ||||
| 		_sector->logicalSector = br.read_8(); | ||||
| 		br.read_be16(); /* sector size */ | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
|  | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* correct but unintuitive */ | ||||
| 	} | ||||
|  | ||||
| 	void decodeDataRecord() override | ||||
| 	{ | ||||
| 		auto bits = readRawBits(TIDS990_DATA_RECORD_SIZE*16); | ||||
| 		auto bytes = decodeFmMfm(bits).slice(0, TIDS990_DATA_RECORD_SIZE); | ||||
|  | ||||
| 		ByteReader br(bytes); | ||||
| 		if (br.read_be16() != DATA_ID) | ||||
| 			return; | ||||
|  | ||||
| 		uint16_t gotChecksum = crc16(CCITT_POLY, bytes.slice(1, TIDS990_DATA_RECORD_SIZE-3)); | ||||
|  | ||||
| 		_sector->data = br.read(TIDS990_PAYLOAD_SIZE); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Tids990Decoder(config)); | ||||
| } | ||||
|  | ||||
| @@ -1,152 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "tids990.h" | ||||
| #include "crc.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "arch/tids990/tids990.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include <fmt/format.h> | ||||
|  | ||||
| static int charToInt(char c) | ||||
| { | ||||
|     if (isdigit(c)) | ||||
|         return c - '0'; | ||||
|     return 10 + tolower(c) - 'a'; | ||||
| } | ||||
|  | ||||
| static uint8_t decodeUint16(uint16_t raw) | ||||
| { | ||||
|     Bytes b; | ||||
|     ByteWriter bw(b); | ||||
|     bw.write_be16(raw); | ||||
|     return decodeFmMfm(b.toBits())[0]; | ||||
| } | ||||
|  | ||||
| class Tids990Encoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     Tids990Encoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.tids990()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void writeRawBits(uint32_t data, int width) | ||||
|     { | ||||
|         _cursor += width; | ||||
|         _lastBit = data & 1; | ||||
|         for (int i = 0; i < width; i++) | ||||
|         { | ||||
|             unsigned pos = _cursor - i - 1; | ||||
|             if (pos < _bits.size()) | ||||
|                 _bits[pos] = data & 1; | ||||
|             data >>= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void writeBytes(const Bytes& bytes) | ||||
|     { | ||||
|         encodeMfm(_bits, _cursor, bytes, _lastBit); | ||||
|     } | ||||
|  | ||||
|     void writeBytes(int count, uint8_t byte) | ||||
|     { | ||||
|         Bytes bytes = {byte}; | ||||
|         for (int i = 0; i < count; i++) | ||||
|             writeBytes(bytes); | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         double clockRateUs = _config.clock_period_us() / 2.0; | ||||
|         int bitsPerRevolution = | ||||
|             (_config.rotational_period_ms() * 1000.0) / clockRateUs; | ||||
|         _bits.resize(bitsPerRevolution); | ||||
|         _cursor = 0; | ||||
|  | ||||
|         uint8_t am1Unencoded = decodeUint16(_config.am1_byte()); | ||||
|         uint8_t am2Unencoded = decodeUint16(_config.am2_byte()); | ||||
|  | ||||
|         writeBytes(_config.gap1_bytes(), 0x55); | ||||
|  | ||||
|         bool first = true; | ||||
|         for (const auto& sectorData : sectors) | ||||
|         { | ||||
|             if (!first) | ||||
|                 writeBytes(_config.gap3_bytes(), 0x55); | ||||
|             first = false; | ||||
|  | ||||
|             /* Writing the sector and data records are fantastically annoying. | ||||
|              * The CRC is calculated from the *very start* of the record, and | ||||
|              * include the malformed marker bytes. Our encoder doesn't know | ||||
|              * about this, of course, with the result that we have to construct | ||||
|              * the unencoded header, calculate the checksum, and then use the | ||||
|              * same logic to emit the bytes which require special encoding | ||||
|              * before encoding the rest of the header normally. */ | ||||
|  | ||||
|             { | ||||
|                 Bytes header; | ||||
|                 ByteWriter bw(header); | ||||
|  | ||||
|                 writeBytes(12, 0x55); | ||||
|                 bw.write_8(am1Unencoded); | ||||
|                 bw.write_8(sectorData->logicalSide << 3); | ||||
|                 bw.write_8(sectorData->logicalTrack); | ||||
|                 bw.write_8(_config.sector_count()); | ||||
|                 bw.write_8(sectorData->logicalSector); | ||||
|                 bw.write_be16(sectorData->data.size()); | ||||
|                 uint16_t crc = crc16(CCITT_POLY, header); | ||||
|                 bw.write_be16(crc); | ||||
|  | ||||
|                 writeRawBits(_config.am1_byte(), 16); | ||||
|                 writeBytes(header.slice(1)); | ||||
|             } | ||||
|  | ||||
|             writeBytes(_config.gap2_bytes(), 0x55); | ||||
|  | ||||
|             { | ||||
|                 Bytes data; | ||||
|                 ByteWriter bw(data); | ||||
|  | ||||
|                 writeBytes(12, 0x55); | ||||
|                 bw.write_8(am2Unencoded); | ||||
|  | ||||
|                 bw += sectorData->data; | ||||
|                 uint16_t crc = crc16(CCITT_POLY, data); | ||||
|                 bw.write_be16(crc); | ||||
|  | ||||
|                 writeRawBits(_config.am2_byte(), 16); | ||||
|                 writeBytes(data.slice(1)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (_cursor >= _bits.size()) | ||||
|             Error() << "track data overrun"; | ||||
|         while (_cursor < _bits.size()) | ||||
|             writeBytes(1, 0x55); | ||||
|  | ||||
|         auto fluxmap = std::make_unique<Fluxmap>(); | ||||
|         fluxmap->appendBits(_bits, | ||||
|             calculatePhysicalClockPeriod(clockRateUs * 1e3, | ||||
|                 _config.rotational_period_ms() * 1e6)); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Tids990EncoderProto& _config; | ||||
|     std::vector<bool> _bits; | ||||
|     unsigned _cursor; | ||||
|     bool _lastBit; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createTids990Encoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Tids990Encoder(config)); | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| #ifndef TIDS990_H | ||||
| #define TIDS990_H | ||||
|  | ||||
| #define TIDS990_PAYLOAD_SIZE       288 /* bytes */ | ||||
| #define TIDS990_SECTOR_RECORD_SIZE 10 /* bytes */ | ||||
| #define TIDS990_DATA_RECORD_SIZE   (TIDS990_PAYLOAD_SIZE + 4) /* bytes */ | ||||
|  | ||||
| class Encoder; | ||||
| class Decoder; | ||||
| class DecoderProto; | ||||
| class EncoderProto; | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createTids990Decoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createTids990Encoder(const EncoderProto& config); | ||||
|  | ||||
| #endif | ||||
|  | ||||
|  | ||||
| @@ -1,25 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Tids990DecoderProto {} | ||||
|  | ||||
| message Tids990EncoderProto { | ||||
| 	optional double rotational_period_ms = 1 [ default = 166, | ||||
| 		(help) = "length of a track" ]; | ||||
| 	optional int32 sector_count = 2 [ default = 26, | ||||
| 		(help) = "number of sectors per track" ]; | ||||
| 	optional double clock_period_us = 3 [ default = 2, | ||||
| 		(help) = "clock rate of data to write" ]; | ||||
| 	optional int32 am1_byte = 4 [ default = 0x2244, | ||||
| 		(help) = "16-bit RAW bit pattern to use for the AM1 ID byte" ]; | ||||
| 	optional int32 am2_byte = 5 [ default = 0x2245, | ||||
| 		(help) = "16-bit RAW bit pattern to use for the AM2 ID byte" ]; | ||||
| 	optional int32 gap1_bytes = 6 [ default = 80, | ||||
| 		(help) = "size of gap 1 (the post-index gap)" ]; | ||||
| 	optional int32 gap2_bytes = 7 [ default = 21, | ||||
| 		(help) = "size of gap 2 (the post-ID gap)" ]; | ||||
| 	optional int32 gap3_bytes = 8 [ default = 51, | ||||
| 		(help) = "size of gap 3 (the post-data or format gap)" ]; | ||||
| } | ||||
|  | ||||
| @@ -2,11 +2,13 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "victor9k.h" | ||||
| #include "crc.h" | ||||
| #include "bytes.h" | ||||
| #include "track.h" | ||||
| #include "fmt/format.h" | ||||
| #include <string.h> | ||||
| #include <algorithm> | ||||
| @@ -25,7 +27,7 @@ static int decode_data_gcr(uint8_t gcr) | ||||
| 		#undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
| }; | ||||
|  | ||||
| static Bytes decode(const std::vector<bool>& bits) | ||||
| { | ||||
| @@ -52,66 +54,58 @@ static Bytes decode(const std::vector<bool>& bits) | ||||
|     return output; | ||||
| } | ||||
|  | ||||
| class Victor9kDecoder : public Decoder | ||||
| AbstractDecoder::RecordType Victor9kDecoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
| 	Victor9kDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		return seekToPattern(ANY_RECORD_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		/* Check the ID. */ | ||||
|  | ||||
| 		if (readRaw32() != VICTOR9K_SECTOR_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		/* Read header. */ | ||||
|  | ||||
| 		auto bytes = decode(readRawBits(3*10)).slice(0, 3); | ||||
|  | ||||
| 		uint8_t rawTrack = bytes[0]; | ||||
| 		_sector->logicalSector = bytes[1]; | ||||
| 		uint8_t gotChecksum = bytes[2]; | ||||
|  | ||||
| 		_sector->logicalTrack = rawTrack & 0x7f; | ||||
| 		_sector->logicalSide = rawTrack >> 7; | ||||
| 		uint8_t wantChecksum = bytes[0] + bytes[1]; | ||||
| 		if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1)) | ||||
| 			return; | ||||
| 					 | ||||
| 		if (wantChecksum == gotChecksum) | ||||
| 			_sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| 	} | ||||
|  | ||||
|     void decodeDataRecord() override | ||||
| 	{ | ||||
| 		/* Check the ID. */ | ||||
|  | ||||
| 		if (readRaw32() != VICTOR9K_DATA_RECORD) | ||||
| 			return; | ||||
|  | ||||
| 		/* Read data. */ | ||||
|  | ||||
| 		auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+4)*10)) | ||||
| 			.slice(0, VICTOR9K_SECTOR_LENGTH+4); | ||||
| 		ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->data = br.read(VICTOR9K_SECTOR_LENGTH); | ||||
| 		uint16_t gotChecksum = sumBytes(_sector->data); | ||||
| 		uint16_t wantChecksum = br.read_le16(); | ||||
| 		_sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new Victor9kDecoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
| 	_sector->clock = _fmr->seekToPattern(ANY_RECORD_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_RECORD_PATTERN) | ||||
| 		return SECTOR_RECORD; | ||||
| 	if (matcher == &DATA_RECORD_PATTERN) | ||||
| 		return DATA_RECORD; | ||||
| 	return UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void Victor9kDecoder::decodeSectorRecord() | ||||
| { | ||||
|     /* Skip the sync marker bit. */ | ||||
|     readRawBits(23); | ||||
|  | ||||
|     /* Read header. */ | ||||
|  | ||||
|     auto bytes = decode(readRawBits(4*10)).slice(0, 4); | ||||
|  | ||||
|     uint8_t rawTrack = bytes[1]; | ||||
|     _sector->logicalSector = bytes[2]; | ||||
|     uint8_t gotChecksum = bytes[3]; | ||||
|  | ||||
|     _sector->logicalTrack = rawTrack & 0x7f; | ||||
|     _sector->logicalSide = rawTrack >> 7; | ||||
|     uint8_t wantChecksum = bytes[1] + bytes[2]; | ||||
|     if ((_sector->logicalSector > 20) || (_sector->logicalTrack > 85) || (_sector->logicalSide > 1)) | ||||
|         return; | ||||
|                  | ||||
|     if (wantChecksum == gotChecksum) | ||||
|         _sector->status = Sector::DATA_MISSING; /* unintuitive but correct */ | ||||
| } | ||||
|  | ||||
| void Victor9kDecoder::decodeDataRecord() | ||||
| { | ||||
|     /* Skip the sync marker bit. */ | ||||
|     readRawBits(23); | ||||
|  | ||||
|     /* Read data. */ | ||||
|  | ||||
|     auto bytes = decode(readRawBits((VICTOR9K_SECTOR_LENGTH+5)*10)) | ||||
|         .slice(0, VICTOR9K_SECTOR_LENGTH+5); | ||||
|     ByteReader br(bytes); | ||||
|  | ||||
|     /* Check that this is actually a data record. */ | ||||
|      | ||||
|     if (br.read_8() != 8) | ||||
|         return; | ||||
|  | ||||
|     _sector->data = br.read(VICTOR9K_SECTOR_LENGTH); | ||||
|     uint16_t gotChecksum = sumBytes(_sector->data); | ||||
|     uint16_t wantChecksum = br.read_le16(); | ||||
|     _sector->status = (gotChecksum == wantChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,211 +0,0 @@ | ||||
| #include "globals.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "encoders/encoders.h" | ||||
| #include "victor9k.h" | ||||
| #include "crc.h" | ||||
| #include "sector.h" | ||||
| #include "readerwriter.h" | ||||
| #include "image.h" | ||||
| #include "fmt/format.h" | ||||
| #include "arch/victor9k/victor9k.pb.h" | ||||
| #include "lib/encoders/encoders.pb.h" | ||||
| #include "lib/layout.h" | ||||
| #include <ctype.h> | ||||
| #include "bytes.h" | ||||
|  | ||||
| static bool lastBit; | ||||
|  | ||||
| static void write_zero_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, unsigned count) | ||||
| { | ||||
|     while (count--) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             lastBit = bits[cursor++] = 0; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_one_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, unsigned count) | ||||
| { | ||||
|     while (count--) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             lastBit = bits[cursor++] = 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const std::vector<bool>& src) | ||||
| { | ||||
|     for (bool bit : src) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             lastBit = bits[cursor++] = bit; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, uint64_t data, int width) | ||||
| { | ||||
|     cursor += width; | ||||
|     lastBit = data & 1; | ||||
|     for (int i = 0; i < width; i++) | ||||
|     { | ||||
|         unsigned pos = cursor - i - 1; | ||||
|         if (pos < bits.size()) | ||||
|             bits[pos] = data & 1; | ||||
|         data >>= 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void write_bits( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes) | ||||
| { | ||||
|     ByteReader br(bytes); | ||||
|     BitReader bitr(br); | ||||
|  | ||||
|     while (!bitr.eof()) | ||||
|     { | ||||
|         if (cursor < bits.size()) | ||||
|             bits[cursor++] = bitr.get(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static int encode_data_gcr(uint8_t data) | ||||
| { | ||||
|     switch (data & 0x0f) | ||||
|     { | ||||
| #define GCR_ENTRY(gcr, data) \ | ||||
|     case data:               \ | ||||
|         return gcr; | ||||
| #include "data_gcr.h" | ||||
| #undef GCR_ENTRY | ||||
|     } | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| static void write_byte(std::vector<bool>& bits, unsigned& cursor, uint8_t b) | ||||
| { | ||||
|     write_bits(bits, cursor, encode_data_gcr(b >> 4), 5); | ||||
|     write_bits(bits, cursor, encode_data_gcr(b), 5); | ||||
| } | ||||
|  | ||||
| static void write_bytes( | ||||
|     std::vector<bool>& bits, unsigned& cursor, const Bytes& bytes) | ||||
| { | ||||
|     for (uint8_t b : bytes) | ||||
|         write_byte(bits, cursor, b); | ||||
| } | ||||
|  | ||||
| static void write_gap(std::vector<bool>& bits, unsigned& cursor, int length) | ||||
| { | ||||
|     for (int i = 0; i < length / 10; i++) | ||||
|         write_byte(bits, cursor, '0'); | ||||
| } | ||||
|  | ||||
| static void write_sector(std::vector<bool>& bits, | ||||
|     unsigned& cursor, | ||||
|     const Victor9kEncoderProto::TrackdataProto& trackdata, | ||||
|     const Sector& sector) | ||||
| { | ||||
|     write_one_bits(bits, cursor, trackdata.pre_header_sync_bits()); | ||||
|     write_bits(bits, cursor, VICTOR9K_SECTOR_RECORD, 10); | ||||
|  | ||||
|     uint8_t encodedTrack = sector.logicalTrack | (sector.logicalSide << 7); | ||||
|     uint8_t encodedSector = sector.logicalSector; | ||||
|     write_bytes(bits, | ||||
|         cursor, | ||||
|         Bytes{ | ||||
|             encodedTrack, | ||||
|             encodedSector, | ||||
|             (uint8_t)(encodedTrack + encodedSector), | ||||
|         }); | ||||
|  | ||||
|     write_gap(bits, cursor, trackdata.post_header_gap_bits()); | ||||
|  | ||||
|     write_one_bits(bits, cursor, trackdata.pre_data_sync_bits()); | ||||
|     write_bits(bits, cursor, VICTOR9K_DATA_RECORD, 10); | ||||
|  | ||||
|     write_bytes(bits, cursor, sector.data); | ||||
|  | ||||
|     Bytes checksum(2); | ||||
|     checksum.writer().write_le16(sumBytes(sector.data)); | ||||
|     write_bytes(bits, cursor, checksum); | ||||
|     write_gap(bits, cursor, trackdata.post_data_gap_bits()); | ||||
| } | ||||
|  | ||||
| class Victor9kEncoder : public Encoder | ||||
| { | ||||
| public: | ||||
|     Victor9kEncoder(const EncoderProto& config): | ||||
|         Encoder(config), | ||||
|         _config(config.victor9k()) | ||||
|     { | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     void getTrackFormat(Victor9kEncoderProto::TrackdataProto& trackdata, | ||||
|         unsigned track, | ||||
|         unsigned head) | ||||
|     { | ||||
|         trackdata.Clear(); | ||||
|         for (const auto& f : _config.trackdata()) | ||||
|         { | ||||
|             if (f.has_min_track() && (track < f.min_track())) | ||||
|                 continue; | ||||
|             if (f.has_max_track() && (track > f.max_track())) | ||||
|                 continue; | ||||
|             if (f.has_head() && (head != f.head())) | ||||
|                 continue; | ||||
|  | ||||
|             trackdata.MergeFrom(f); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| public: | ||||
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo, | ||||
|         const std::vector<std::shared_ptr<const Sector>>& sectors, | ||||
|         const Image& image) override | ||||
|     { | ||||
|         Victor9kEncoderProto::TrackdataProto trackdata; | ||||
|         getTrackFormat(trackdata, trackInfo->logicalTrack, trackInfo->logicalSide); | ||||
|  | ||||
|         unsigned bitsPerRevolution = (trackdata.rotational_period_ms() * 1e3) / | ||||
|                                      trackdata.clock_period_us(); | ||||
|         std::vector<bool> bits(bitsPerRevolution); | ||||
|         nanoseconds_t clockPeriod = calculatePhysicalClockPeriod( | ||||
|             trackdata.clock_period_us() * 1e3, | ||||
|             trackdata.rotational_period_ms() * 1e6); | ||||
|         unsigned cursor = 0; | ||||
|  | ||||
|         fillBitmapTo(bits, | ||||
|             cursor, | ||||
|             trackdata.post_index_gap_us() * 1e3 / clockPeriod, | ||||
|             {true, false}); | ||||
|         lastBit = false; | ||||
|  | ||||
|         for (const auto& sector : sectors) | ||||
|             write_sector(bits, cursor, trackdata, *sector); | ||||
|  | ||||
|         if (cursor >= bits.size()) | ||||
|             Error() << fmt::format( | ||||
|                 "track data overrun by {} bits", cursor - bits.size()); | ||||
|         fillBitmapTo(bits, cursor, bits.size(), {true, false}); | ||||
|  | ||||
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap); | ||||
|         fluxmap->appendBits(bits, clockPeriod); | ||||
|         return fluxmap; | ||||
|     } | ||||
|  | ||||
| private: | ||||
|     const Victor9kEncoderProto& _config; | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Encoder> createVictor9kEncoder( | ||||
|     const EncoderProto& config) | ||||
| { | ||||
|     return std::unique_ptr<Encoder>(new Victor9kEncoder(config)); | ||||
| } | ||||
|  | ||||
| // vim: sw=4 ts=4 et | ||||
| @@ -1,24 +1,22 @@ | ||||
| #ifndef VICTOR9K_H | ||||
| #define VICTOR9K_H | ||||
|  | ||||
| class Encoder; | ||||
| class Decoder; | ||||
| class EncoderProto; | ||||
| class DecoderProto; | ||||
|  | ||||
| /* ... 1101 0101 0111 | ||||
|  *       ^^ ^^^^ ^^^^ ten bit IO byte */ | ||||
| #define VICTOR9K_SECTOR_RECORD 0xfffffd57 | ||||
| #define VICTOR9K_HEADER_ID 0x7 | ||||
|  | ||||
| /* ... 1101 0100 1001 | ||||
|  *       ^^ ^^^^ ^^^^ ten bit IO byte */ | ||||
| #define VICTOR9K_DATA_RECORD   0xfffffd49 | ||||
| #define VICTOR9K_DATA_ID 0x8 | ||||
| #define VICTOR9K_SECTOR_RECORD 0xfffffeab | ||||
| #define VICTOR9K_DATA_RECORD   0xfffffea4 | ||||
|  | ||||
| #define VICTOR9K_SECTOR_LENGTH 512 | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createVictor9kDecoder(const DecoderProto& config); | ||||
| extern std::unique_ptr<Encoder> createVictor9kEncoder(const EncoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class Victor9kDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~Victor9kDecoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
|     void decodeDataRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,36 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| import "lib/common.proto"; | ||||
|  | ||||
| message Victor9kDecoderProto {} | ||||
|  | ||||
| // NEXT: 12 | ||||
| message Victor9kEncoderProto | ||||
| { | ||||
|     message TrackdataProto | ||||
|     { | ||||
|         optional int32 min_track = 1 | ||||
|             [ (help) = "minimum track this format applies to" ]; | ||||
|         optional int32 max_track = 2 | ||||
|             [ (help) = "maximum track this format applies to" ]; | ||||
|         optional int32 head = 3 | ||||
|             [ (help) = "which head this format applies to" ]; | ||||
|  | ||||
|         optional double rotational_period_ms = 4 | ||||
|             [ (help) = "original rotational period of this track" ]; | ||||
|         optional double clock_period_us = 5 | ||||
|             [ (help) = "original data rate of this track" ]; | ||||
|         optional double post_index_gap_us = 6 | ||||
|             [ (help) = "size of post-index gap" ]; | ||||
|         optional int32 pre_header_sync_bits = 10 | ||||
|             [ (help) = "number of sync bits before the sector header" ]; | ||||
|         optional int32 pre_data_sync_bits = 8 | ||||
|             [ (help) = "number of sync bits before the sector data" ]; | ||||
|         optional int32 post_data_gap_bits = 9 | ||||
|             [ (help) = "size of gap between data and the next header" ]; | ||||
|         optional int32 post_header_gap_bits = 11 | ||||
|             [ (help) = "size of gap between header and the data" ]; | ||||
|     } | ||||
|  | ||||
|     repeated TrackdataProto trackdata = 1; | ||||
| } | ||||
| @@ -2,6 +2,7 @@ | ||||
| #include "fluxmap.h" | ||||
| #include "decoders/fluxmapreader.h" | ||||
| #include "protocol.h" | ||||
| #include "record.h" | ||||
| #include "decoders/decoders.h" | ||||
| #include "sector.h" | ||||
| #include "zilogmcz.h" | ||||
| @@ -13,45 +14,35 @@ | ||||
|  | ||||
| static const FluxPattern SECTOR_START_PATTERN(16, 0xaaab); | ||||
|  | ||||
| class ZilogMczDecoder : public Decoder | ||||
| AbstractDecoder::RecordType ZilogMczDecoder::advanceToNextRecord() | ||||
| { | ||||
| public: | ||||
| 	ZilogMczDecoder(const DecoderProto& config): | ||||
| 		Decoder(config) | ||||
| 	{} | ||||
|  | ||||
|     nanoseconds_t advanceToNextRecord() override | ||||
| 	{ | ||||
| 		seekToIndexMark(); | ||||
| 		return seekToPattern(SECTOR_START_PATTERN); | ||||
| 	} | ||||
|  | ||||
|     void decodeSectorRecord() override | ||||
| 	{ | ||||
| 		readRawBits(14); | ||||
|  | ||||
| 		auto rawbits = readRawBits(140*16); | ||||
| 		auto bytes = decodeFmMfm(rawbits).slice(0, 140); | ||||
| 		ByteReader br(bytes); | ||||
|  | ||||
| 		_sector->logicalSector = br.read_8() & 0x1f; | ||||
| 		_sector->logicalSide = 0; | ||||
| 		_sector->logicalTrack = br.read_8() & 0x7f; | ||||
| 		if (_sector->logicalSector > 31) | ||||
| 			return; | ||||
| 		if (_sector->logicalTrack > 80) | ||||
| 			return; | ||||
|  | ||||
| 		_sector->data = br.read(132); | ||||
| 		uint16_t wantChecksum = br.read_be16(); | ||||
| 		uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134)); | ||||
|  | ||||
| 		_sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config) | ||||
| { | ||||
| 	return std::unique_ptr<Decoder>(new ZilogMczDecoder(config)); | ||||
| 	const FluxMatcher* matcher = nullptr; | ||||
|     _fmr->seekToIndexMark(); | ||||
| 	_sector->clock = _fmr->seekToPattern(SECTOR_START_PATTERN, matcher); | ||||
| 	if (matcher == &SECTOR_START_PATTERN) | ||||
| 		return SECTOR_RECORD; | ||||
| 	return UNKNOWN_RECORD; | ||||
| } | ||||
|  | ||||
| void ZilogMczDecoder::decodeSectorRecord() | ||||
| { | ||||
|     readRawBits(14); | ||||
|  | ||||
|     auto rawbits = readRawBits(140*16); | ||||
|     auto bytes = decodeFmMfm(rawbits).slice(0, 140); | ||||
|     ByteReader br(bytes); | ||||
|  | ||||
|     _sector->logicalSector = br.read_8() & 0x1f; | ||||
|     _sector->logicalSide = 0; | ||||
|     _sector->logicalTrack = br.read_8() & 0x7f; | ||||
|     if (_sector->logicalSector > 31) | ||||
|         return; | ||||
|     if (_sector->logicalTrack > 80) | ||||
|         return; | ||||
|  | ||||
|     _sector->data = br.read(132); | ||||
|     uint16_t wantChecksum = br.read_be16(); | ||||
|     uint16_t gotChecksum = crc16(MODBUS_POLY, 0x0000, bytes.slice(0, 134)); | ||||
|  | ||||
|     _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,17 @@ | ||||
| #ifndef ZILOGMCZ_H | ||||
| #define ZILOGMCZ_H | ||||
|  | ||||
| extern std::unique_ptr<Decoder> createZilogMczDecoder(const DecoderProto& config); | ||||
| class Sector; | ||||
| class Fluxmap; | ||||
|  | ||||
| class ZilogMczDecoder : public AbstractDecoder | ||||
| { | ||||
| public: | ||||
|     virtual ~ZilogMczDecoder() {} | ||||
|  | ||||
|     RecordType advanceToNextRecord(); | ||||
|     void decodeSectorRecord(); | ||||
| }; | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| syntax = "proto2"; | ||||
|  | ||||
| message ZilogMczDecoderProto {} | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user